파란하늘의 지식창고
반응형

Spring Boot를 사용하면 내부 사용을 위한 라이브러리 모듈 제공이 편리해진다.

모듈에서 사용하는 속성 값을 설정하기 편하게 @ConfigurationProperties를 제공하기 때문이다.

보통 빌더 패턴을 이용해 기본 값을 설정하고 외부 설정 properties 값이 있는 경우 해당 값으로 merge 처리를 하는 형태로 Properties 객체를 관리하게 된다.

Spring Boot 1.x 에서는 이런 형태의 관리를 하는 경우가 몇몇 군데 있을 것으로 예상되는데 참고한 코드는 spring security의 oauth 코드이다.

1.x의 OAuth2ClientPropertiesRegistrationAdapter

private static ClientRegistration getClientRegistration(String registrationId,
		Registration properties, Map<String, Provider> providers) {
	Builder builder = getBuilder(registrationId, properties.getProvider(), providers);
	copyIfNotNull(properties::getClientId, builder::clientId);
	copyIfNotNull(properties::getClientSecret, builder::clientSecret);
	copyIfNotNull(properties::getClientAuthenticationMethod,
			builder::clientAuthenticationMethod, ClientAuthenticationMethod::new);
	copyIfNotNull(properties::getAuthorizationGrantType,
			builder::authorizationGrantType, AuthorizationGrantType::new);
	copyIfNotNull(properties::getRedirectUriTemplate, builder::redirectUriTemplate);
	copyIfNotNull(properties::getScope, builder::scope,
			(scope) -> scope.toArray(new String[scope.size()]));
	copyIfNotNull(properties::getClientName, builder::clientName);
	return builder.build();
}

//... 중간 생략

private static <T> void copyIfNotNull(Supplier<T> supplier, Consumer<T> consumer) {
	copyIfNotNull(supplier, consumer, Function.identity());
}

private static <S, C> void copyIfNotNull(Supplier<S> supplier, Consumer<C> consumer,
		Function<S, C> converter) {
	S value = supplier.get();
	if (value != null) {
		consumer.accept(converter.apply(value));
	}
}

copyIfNulNull 메소드 2개는 supplier로 전달받은 내용을 consumer로 넘겨주고 getClientRegistration에서 properties의 값을 builder로 전달 후 builder.build()를 통해 merge 된 결과 값을 반환하고 있다.

properties를 merge 하기 위해 copyIfNotNull과 같은 유틸성 메소드를 개별 선언하여 사용하는 것이 불편한 부분이었는데 Spring Boot 2.0 이후 PropertyMapper를 통해 해당 기능을 제공하게 되었다.


아래와 같이 바뀌었다.

2.0 이후 OAuth2ClientPropertiesRegistrationAdapter

private static ClientRegistration getClientRegistration(String registrationId,
		Registration properties, Map<String, Provider> providers) {
	Builder builder = getBuilder(registrationId, properties.getProvider(), providers);
	PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
	map.from(properties::getClientId).to(builder::clientId);
	map.from(properties::getClientSecret).to(builder::clientSecret);
	map.from(properties::getClientAuthenticationMethod)
			.as(ClientAuthenticationMethod::new)
			.to(builder::clientAuthenticationMethod);
	map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new)
			.to(builder::authorizationGrantType);
	map.from(properties::getRedirectUriTemplate).to(builder::redirectUriTemplate);
	map.from(properties::getScope).as((scope) -> StringUtils.toStringArray(scope))
			.to(builder::scope);
	map.from(properties::getClientName).to(builder::clientName);
	return builder.build();
}

PropertyMapper는 merge 기준에 대해서도 선언할 수 있다.

외부 설정 properties에 선언되지 않은 경우 기본 값인 null, 0, false가 전달되게 되는데 이런 경우 merge 대상이 되면 안된다.

가장 많이 쓰이는 것은 위에서도 사용된 alwaysApplyingWhenNotNull() 옵션이다.

null 값인 경우 merge 하지 않게 해주는 옵션이며 전체 기본 적용 설정이다.

개별 프로퍼티 별로 설정을 하고자 한다면 아래와 같이 whenNotNull()을 사용한다.

map.from(properties::getClientId).whenNonNull().to(builder::clientId);

boolean 값의 경우 whenFalse(), whenTrue() 를 제공하고 있다.

기본 설정 값이 true인 경우 외부 설정값이 false인 경우만 처리를 해야한다. 이 경우는 whenFalse() 를 사용해야 한다.

(물론 이 경우 외부 properties의 기본 값 설정이 true여야 한다.)

map.from(properties::getCheckNotSupportedBrowser).whenFalse().to(builder::checkNotSupportedBrowser);

숫자 값의 경우 기본 값인 0이 넘어오는 경우 해당 merge 하지 않아야 한다.

이 때는 when(Predicate<T> predicate) 를 사용하면 된다.

mapfrom(properties::getACode).when(x -> x > 0).to(builder::ACode);

이외에도 whenHasText(), whenEqualTo(Object object), whenInstanceOf(Class<R> target), whenNot(Predicate<T> predicate)이 있다.

반응형
profile

파란하늘의 지식창고

@Bluesky_

내용이 유익했다면 광고 배너를 클릭 해주세요