Yaml의 Property를 Class로 바인딩 해보자

박진형·2022년 6월 27일
1

Spring

목록 보기
9/9

Yaml의 Property를 Class로 바인딩 해보자

.yaml 파일의 프로퍼티들을 불러와서 사용하고 싶을 때가 있다.
예를 들어 JWT의 header, 토큰 이름, 유효기간 등등... 어떻게 클래스에 바인딩을 해주는지 알아보자.

.yaml 파일 먼저 살펴보기

제가 사용할 프로퍼티들은 다음과 같이 계층구조를 이루고 있습니다.
Security 설정에 관한 프로퍼티들을 하나의 Bean에 바인딩해 사용하고 싶습니다.

getter, setter 방식

설정 중 jwt 부분만 떼어내어 getter setter 방식을 살펴보겠습니다.
최상단 클래스레벨에 @Component와 @ConfigurationProperties 어노테이션을 사용해줍니다.
그리고 jwt는 공통되는 최상위 prefix이므로 prefix 설정을 해줍니다.

그러면 jwt하위에 있는 accessToken, refreshToken, issuer, clientSecret을 바인딩하고

JwtConfigure 내부에있는 스태틱 클래스로 AccessToken, RefreshToken 클래스를 만들고 모든 곳에 getter, setter를 붙여줍니다.

package com.prgrms.devcource.configures;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfigure {

	private AccessToken accessToken;
	private RefreshToken refreshToken;
	private String issuer;
	private String clientSecret;

	public AccessToken getAccessToken() {
		return accessToken;
	}

	public RefreshToken getRefreshToken() {
		return refreshToken;
	}

	public String getIssuer() {
		return issuer;
	}

	public String getClientSecret() {
		return clientSecret;
	}

	public void setAccessToken(AccessToken accessToken) {
		this.accessToken = accessToken;
	}

	public void setRefreshToken(RefreshToken refreshToken) {
		this.refreshToken = refreshToken;
	}

	public void setIssuer(String issuer) {
		this.issuer = issuer;
	}

	public void setClientSecret(String clientSecret) {
		this.clientSecret = clientSecret;
	}

	public static class AccessToken {
		private String header;
		private int expirySeconds;

		public String getHeader() {
			return header;
		}

		public int getExpirySeconds() {
			return expirySeconds;
		}

		public void setHeader(String header) {
			this.header = header;
		}

		public void setExpirySeconds(int expirySeconds) {
			this.expirySeconds = expirySeconds;
		}

		@Override
		public String toString() {
			return "AccessTokenProperties{" +
				"header='" + header + '\'' +
				", expirySeconds=" + expirySeconds +
				'}';
		}
	}

	public static class RefreshToken {
		private String header;
		private int expirySeconds;

		public String getHeader() {
			return header;
		}

		public int getExpirySeconds() {
			return expirySeconds;
		}

		public void setHeader(String header) {
			this.header = header;
		}

		public void setExpirySeconds(int expirySeconds) {
			this.expirySeconds = expirySeconds;
		}

		@Override
		public String toString() {
			return "RefreshTokenProperties{" +
				"header='" + header + '\'' +
				", expirySeconds=" + expirySeconds +
				'}';
		}
	}
}

어떤가요? 아직 jwt부분만 떼어놓고 봤는데도 코드의 양이 상당합니다.
물론 롬복을 사용할 수 있겠습니다만 그래도 단점이 존재합니다.

Getter, Setter 방식의 단점

Getter, Setter 방식의 단점은 코드의 양이 상당한 것도 있지만 불변성이 보장되지 않는다는 것 입니다.
Property같은 것들은 서버의 설정이므로 변경되지 않아야 합니다. 하지만 setter를 열어두면 언제든지 변경될 수 있겠죠!

많은 책들을 보면 불변성, 불변 객체 들을 강조하기도 합니다! 이 문제를 생성자 바인딩 방식으로 해결할 수 있습니다.

@ConsturctorBinding 방식

JwtConfigure에 @Component를 @ContructorBinding으로 교체했습니다.
그리고 Security 설정의 클래스 레벨에 다음과 같이 @EnableConfigurationProperties를 추가해줬습니다.

@EnableConfigurationProperties({JwtConfigure.class})
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
...
}
@ConstructorBinding
@ConfigurationProperties(prefix = "jwt")
public class JwtConfigure {

	private AccessToken accessToken;
	private RefreshToken refreshToken;
	private String issuer;
	private String clientSecret;

	public JwtConfigure(AccessToken accessToken, RefreshToken refreshToken, String issuer, String clientSecret) {
		this.accessToken = accessToken;
		this.refreshToken = refreshToken;
		this.issuer = issuer;
		this.clientSecret = clientSecret;
	}

	public AccessToken getAccessToken() {
		return accessToken;
	}

	public RefreshToken getRefreshToken() {
		return refreshToken;
	}

	public String getIssuer() {
		return issuer;
	}

	public String getClientSecret() {
		return clientSecret;
	}

	public static class AccessToken {
		private String header;
		private int expirySeconds;

		public AccessToken(String header, int expirySeconds) {
			this.header = header;
			this.expirySeconds = expirySeconds;
		}

		public String getHeader() {
			return header;
		}

		public int getExpirySeconds() {
			return expirySeconds;
		}

		@Override
		public String toString() {
			return "AccessTokenProperties{" +
				"header='" + header + '\'' +
				", expirySeconds=" + expirySeconds +
				'}';
		}
	}

	public static class RefreshToken {
		private String header;
		private int expirySeconds;

		public RefreshToken(String header, int expirySeconds) {
			this.header = header;
			this.expirySeconds = expirySeconds;
		}

		public String getHeader() {
			return header;
		}

		public int getExpirySeconds() {
			return expirySeconds;
		}

		@Override
		public String toString() {
			return "RefreshTokenProperties{" +
				"header='" + header + '\'' +
				", expirySeconds=" + expirySeconds +
				'}';
		}
	}

비교적 코드도 짧아지고 불변성도 보장되게 되었습니다. 하지만 여전히 코드의 양이 적다고 볼 순 없습니다. 여기에 자바 14부터 도입된 record를 사용하면 어떻게 될까요? 적용해보겠습니다.

record로 코드 줄이기

@ConstructorBinding
@ConfigurationProperties(prefix = "jwt")
public record JwtConfigure(
	AccessToken accessToken,
	RefreshToken refreshToken,
	String issuer,
	String clientSecret) {

	public record AccessToken(
		String header,
		int expirySeconds
	) {
	}

	public record RefreshToken(
		String header,
		int expirySeconds
	) {
	}
}

어떠신가요? 처음의 Getter, Setter 방식과 비교했을 때 코드의 양이 확연하게 줄었습니다. 또한 불변성도 보장 되었습니다.

이제 JWT 부분만 떼어내어 보는 것이 아닌 맨 처음 보여드렸던 yaml 파일의 security 설정 부분 전체를 보여드리겠습니다.

@ConstructorBinding
@ConfigurationProperties(prefix = "security")
public record SecurityConfigProperties(PatternsConfigures patterns, JwtConfigure jwt) {

	public SecurityConfigProperties(PatternsConfigures patterns, JwtConfigure jwt) {
		this.patterns = patterns;
		this.jwt = jwt;
	}

	public record PatternsConfigures(String[] ignoring, String[] permitAll) {
	}

	public record JwtConfigure(
		AccessTokenProperties accessToken,
		RefreshTokenProperties refreshToken,
		String issuer,
		String clientSecret
	) {
		public JwtConfigure(
			AccessTokenProperties accessToken,
			RefreshTokenProperties refreshToken,
			String issuer,
			String clientSecret
		) {
			this.accessToken = accessToken;
			this.refreshToken = refreshToken;
			this.issuer = issuer;
			this.clientSecret = clientSecret;
		}

		public record AccessTokenProperties(String header, int expirySeconds) {

		}

		public record RefreshTokenProperties(String header, int expirySeconds) {

		}
	}
}

어떠신가요? JWT 설정 외에 패턴이 추가 되었음에도 JWT만 떼어내 Getter, Setter 방식을 적용한 것보다 코드의 수가 적습니다.

생성자 바인딩 방식과 record를 사용해 불변성, 유지보수성, 생산성 모든 것이 향상되었습니다.

0개의 댓글