[Java 1.8] 두개의 리스트 조인(병합)하기

자바에서 타입이 다른 두개의 리스트를 조인하고 싶다. 데이터베이스에서 조인을 해서 가져오는 것이 최선이지만 어플에서 조인을 해야하는 경우도 종종 생기는 것 같다. 마침 자바 1.8 에서 제공하는 람다 표현식을 써보고 싶어서 테스트 겸해서 하나 만들어 보았다.

예제

게시판 리스트 화면을 예로 들겠다. 게시글 리스트에서 회원이름을 출력하고 싶다. 게시판인 Article 클래스에는 memberNo를 가지고 있고, Member클래스에는 memberNo와 memberName이 있다. List<Article> 와 List<Member>를 조인해보자.




List<Article>안에 memberName이 전부 null이 였는데, Member 클래스에서 회원번호를 찾아서 이름을 넣어주는 코드다.


List<Article> articles;
List<Member> member;

for (Article article : articles) {
    for (Member member : members) {
        if (article.getMemberNo() == member.getMemberNo()) {
            article.setMemberName(member.getMemberName());
        }
    }
}

이중 루프를 썼더니 잘 된다. 이제 같은 동작을 1.8 의 람다 표현식을 사용해서 구현해보자. CollectionJoiner 라는 클래스를 만들어서 안에서 루프를 돌리고, 외부에서는 조건문과 할당해주는 부분만 넘기면 되게 해보자.

먼저 테스트할 코드부터 작성해보자.


List<Article> newList = CollectionJoiner.collections(articles, members)
    .when((article, member) -> article.getMemberNo() == member.getMemberNo())
    .then((article, member) -> {
        article.setMemberName(member.getMemberName());
        return article;
    });

2중루프와 같은 동작을 하는 CollectionJoiner 클래스를 사용할 때의 코드이다. 위의 코드가 작동하게 하기 위해서는 두가지의@FunctionalInterface를 사용해야 한다.

  • BiPredicate<T, U> 클래스의 boolean test(T, U);
  • BiFunction<T, U, R> 클래스의 R apply(T, U);

이제 아래 클래스를 생성해보자.

CollectionJoiner.java

package com.test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;

public class CollectionJoiner{

    private CollectionJoiner() {
    }

    public static <T, U> CollectionJoinerHelper<T, U> collections(Collection<T> t, Collection<U> u) {
        return new CollectionJoinerHelper<T, U>(t, u);
    }

    public static class CollectionJoinerHelper<T, U> {

        private Collection<T> t;
        private Collection<U> u;
        private BiPredicate<T, U> condition;

        private CollectionJoinerHelper(Collection<T> t, Collection<U> u) {
            this.t = t;
            this.u = u;
        }

        public CollectionJoinerHelper<T, U> when(BiPredicate<T, U> condition) {
            this.condition = condition;
            return this;
        }

        public <R> List<R> then(BiFunction<T, U, R> function) {

            List<R> list = new ArrayList<>();

            for (T tOne : t) {
                for (U uOne : u) {
                    if (condition.test(tOne, uOne)) {
                        list.add(function.apply(tOne, uOne));
                    }
                }
            }

            return list;
        }

    }

}

클래스를 추가하고 실행해보면, 결과는 이중루프를 돌린 것과 같다.

처음에는 CollectionJoiner 같은 클래스로 만들면 사용할 때, 코드 양이 줄어들 줄 알았는데, 비슷한 것 같다. 그래도 자바 1.8의 람다를 연습하는 데에는 도움이 되었다.
지금은 then을 호출하자마자 List<R>를 리턴하는데, 나중에는 리턴타입도 Collectors 클래스를 통해서 정할 수 있도록 만들어 봐야겠다.

[maven] 메이븐 기본 properties

pom/project

빌드 : ${project.build.directory} = ${pom.build.directory} = ‘target’;
빌드 : ${project.build.outputDirectory} = ‘target/classes’;
프로젝트 이름 : ${project.name} = ${pom.name} = / 엘리먼트 설정 값;
프로젝트 버전 : ${project.version} = ${pom.version} = ${version} = / 엘리먼트 설정 값;
최종 파일 이름 : ${project.build.finalName}= // 엘리먼트 설정 값

settings

로컬 저장소 경로 : ${settings.localRepository}

environment variable

시스템 PATH : {env.PATH}
시스템 JAVA_HOME : {env.JAVA_HOME}

etc

pom.xml이 위치하는 디렉토리 : ${basedir}

[spring security + uaa] 로그인용 OAuth 서버 사용하기

목표

로그인 전용 서버를 따로 두고, 여러 어플리케이션에서 로그인을 처리하지 않고, 로그인 전용 서버에 역할을 위임해서 처리하고 싶다. 이 글에서는 OAuth를 사용하여 로그인 전용 서버로 사용했다.





App 이라는 웹 어플리케이션에서 로그인 서버의 인증을 사용하는 케이스의 sequence diagram 이다. 인증 방법은 authorization_code 방법이다.

App에서는 사용자의 정보를 가지고 있지 않고, 로그인 서버에서 사용자 정보를 가져온 후, 세션에 저장한다.

코드 관점에서 보면 App이 Spring Security를 사용한다면, SecurityContext에 외부에서 가져온 데이터를 채워 넣어야 한다.

방법

CloudFoundry의 User Account and Authentication Service(UAA) 에 이러한 서비스를 제공하는 라이브러리가 있다.

<dependency>
    <groupId>org.cloudfoundry.identity</groupId>
    <artifactId>cloudfoundry-identity-common</artifactId>
    <version>1.4.3</version>
</dependency>

이 라이브러리 안에는 ClientAuthenticationFilter가 있는데, UsernamePasswordAuthenticationFilter 대신 사용하면 된다.

샘플용 소스를 하나 만들어서 github에 올렸다. https://github.com/jekalmin/samples 에서 uaa 폴더 안에 프로젝트가 두개 있는데, 아래처럼 둘 다 받아서 서버에 올리면 된다.

여기서는 oauth_consumer가 위에서 설명한 App이고, oauth_provider가 Login Server이다.

서버를 실행하고, 위의 Authentication Sequence를 보면서 테스트해보자.

먼저 http://localhost:8080/oauth_consumer를 호출하면, Authentication Sequence의 1,2,3,4 번에 해당하는 작업을 수행한다. 그래서 아래 이미지와 같이 4번에 해당하는 로그인 페이지에 오게 되었다.

min / min 계정으로 로그인을 해보면 아래 이미지와 같이 권한 승인 페이지로 가게 되는데, 이 부분은 Authentication Sequence의 6번에 해당하는 권한 승인 페이지이다.

승인을 하게 되면, Authentication Sequence의 7번부터 15번까지 실행되고 아래와 같이 http://localhost:8080/oauth_consumer 에 해당하는 페이지가 출력된다.

User Account and Authentication Service(UAA)

위의 예제에서는 App 부분인 oauth_consumer에만 UAA의 라이브러리를 사용했고, 로그인 서버부분인 oauth_provider는 예전에 포스팅했던 http://jekalmin.tistory.com/entry/spring-bootoauth-%EC%84%B8%ED%8C%85-%ED%85%8C%EC%8A%A4%ED%8A%B8 글의 코드와 같다.

oauth_consumer에서 UAA의 ClientAuthenticationFilter가 Authentication Sequence의 핵심인 2,10,12,14 번에 해당하는 부분들을 담당하고 있다.

UAA에서는 클라이언트 사이드 뿐만 아니라 서버사이드도 많은 기능을 제공하는데(revoke token, RemoteTokenStore, etc..), 사실 서버쪽의 기능들이 더 핵심인 것 같다.

p.s.) ClientAuthenticationFilter에서 redirect와 /oauth/token을 요청하는 부분은 https://github.com/dsyer/uaa/blob/feature/bootify/common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java 의 99번째 줄인 restTemplate에 프록시로 걸려있다.

Sample Code

https://github.com/jekalmin/samples

Reference