본문 바로가기

spring/boot

2장 스프링 부트 공통 작업 - 실전 스프링 부트

1. 어플리케이션 설정 관리

스프링 부트는 프로퍼티 파일, YAML 파일, 환경 변수, 명령행 인자 등 여려 가지 방법으로
설정 정보를 외부화해서 소스 코드 변경 없이 환경마다 다르게 적용할 수 있다.

1.1 SpringApplication 클래스 사용


@SpringBootApplication
public class SpringBootAppDemoApplication {

    public static void main(String[] args) {
        var properties = new Properties();
        // 해당 설정은 spring.config.import에 명시한 파일이 존재하지 않는 경우 전략을 소개 설정한 것이다.
        properties.setProperty("spring.config.on-not-found", "ignore");

        var springApp = new SpringApplication(SpringBootAppDemoApplication.class);
    }
}

1.2 @PropertySource 사용

프로퍼티 파일의 위치를 @PropertySource 애너테이션을 사용해서 지정할 수 있다.
프로퍼티 경로 설정


@Configuration
@PropertySource("classpath:dbConfig.properties")
public class DbConfiguration {

    @Autowired
    private Environment environment;

    @Override
    public String toString() {
        return "Username : " + environment.getProperty("user") +
                ",  Password : " + environment.getProperty("password");
    }
}

프로퍼티 설정

user=sa
password=p@ssOrd

스프링 부트 앱


@SpringBootApplication
public class SpringBootAppDemoApplication {

    private static final Logger log = LoggerFactory.getLogger(SpringBootAppDemoApplication.class);

    public static void main(String[] args) {
        var springApp = new SpringApplication(SpringBootAppDemoApplication.class);

        var applicationContext = springApp.run(args);

        var dbConfiguration = applicationContext.getBean(DbConfiguration.class);
        log.info(dbConfiguration.toString());
    }
}

1.3 환경 설정 파일

프로퍼티 파일이나, YAML 파일을 통해서 환경 설정 정보를 관리하는 방법이다. 개인적으로는 yml 파일을 통해서 설정을 관리하는 것을 선호한다.

1.4 명령행 인자

컴파일된 jar파일을 실행할 때 명령행에서 환경을 설정할 수 있다.

example

java -jar spring-boot-app.jar --spring.config.name=sample_name

1.5 운영 체제 환경 변수

프로퍼티 파일에 환경 변수를 통해서 설정 정보를 입력할 수 있다.

example : ENV_VAR 환경 변수의 값을 사용하는 프로퍼티 예제

app.timeout=${ENV_VAR}

2. @ConfigurationProperties로 커스텀 프로퍼티 만들기

프로퍼티를 사용할 때 타입 안정성을 보장하거나 입력 형태를 검증할 때 커스텀 프로퍼티를 사용하면 좋다.

2.1 @ConfigurationProperties를 사용한 커스텀 프퍼티 정의

@ConfigurationProperties 애너테이션을 통해 설정 정보를 외부화하고 타입 안정성을 확보할 수 잇으며 구조화된 방식으로 관리가 가능하다.

의존성

annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

프로퍼티 설정

app.sbip.ct.name=CourseTracker
app.sbip.ct.ip=127.0.0.1
app.sbip.ct.port=9090
app.sbip.ct.security.enabled=true
app.sbip.ct.security.token=asddf998hhyqthgtYYtggghg9908jjh7ttr
app.sbip.ct.security.roles=USER,ADMIN

AppProperties class
@ConfigurationProperties에 프로퍼티 접두어를 설정한다.
@DefaultValue를 사용하면 디폴트 값을 설정할 수 있다.


@ConfigurationProperties("app.sbip.ct")
public class AppProperties {

    private final String name;

    private final String ip;

    private final int port;

    private final Security security;

    public String getName() {
        return name;
    }

    public String getIp() {
        return ip;
    }

    public int getPort() {
        return port;
    }

    public Security getSecurity() {
        return security;
    }

    public AppProperties(String name, String ip, @DefaultValue("8080") int port, Security security) {
        this.name = name;
        this.ip = ip;
        this.port = port;
        this.security = security;
    }

    @Override
    public String toString() {
        return "AppProperties{" +
                "name='" + name + '\'' +
                ", ip='" + ip + '\'' +
                ", port='" + port + '\'' +
                ", security=" + security +
                '}';
    }

    public static class Security {

        private boolean enabled;

        private final String token;

        private final List<String> roles;

        public Security(boolean enabled, String token, List<String> roles) {
            this.enabled = enabled;
            this.token = token;
            this.roles = roles;
        }

        public boolean isEnabled() {
            return enabled;
        }

        public String getToken() {
            return token;
        }

        public List<String> getRoles() {
            return roles;
        }

        @Override
        public String toString() {
            return "Security{" +
                    "enabled=" + enabled +
                    ", token='" + token + '\'' +
                    ", roles=" + roles +
                    '}';
        }
    }
}

단건으로 프로퍼티 설정한다면 @EnableConfigurationProperties를 사용하고
컴포넌트 스캔으로 커스텀 프로퍼티을 빈으로 만드는 설정은 @ConfigurationPropertiesScan를 사용한다.

@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
@ConfigurationPropertiesScan

3. 스프링 부트 애플리케이션 시작 시 사전 작업 실행하기

CommandLineRunnerApplicationRunner 인터페이스를 사용해서 스프링 부트의 시작시 코드를 실행시킬 수 있다.

3.1 SpringBootApplication에 적용하기

스프링 앱을 시작하는 클래스가 존재할 것이다. 이 클래스에 CommandLineRunnerApplicationRunner를 상속하여 run 메소드를 구현하면 코드가 실행된다.

3.2 Bean에 등록하기

CommandLineRunnerApplicationRunner를 구현한 객체를 빈으로 등록하면 정의한 작업이 실행된다.

3.3 Component 어노테이션 사용하여 빈 등록하기

CommandLineRunnerApplicationRunner를 구현한 클래스에 @Component를 적용하면 등록한 작업이 실행된다.
이때 @Order 어노테이션을 통해서 실행 순서를 지정할 수 있다는 것이 차별점이다.


4. 스프링 부트 로깅 커스터 마이징

4.1 로깅 출력 레이아웃 변경하기

스프링 부트가 출력하는 로그의 레이아웃의 형태나 색을 커스텀 할 수 있다.
logback docs

logging.pattern.console=%clr(%d{dd-MM-yyyy HH:mm:ss.SSS}){yellow} %clr(${PID:- }){green} %magenta([%thread]) %highlight([%-5level]) %clr(%-40.40logger{39}){cyan} %msg%n

5. 빈 벨리데이션으로 유효성 검증

어노테이션을 사용해서 특정 필드의 값에 대한 유효성을 검증하는 로직을 작성할 수 있다.

5.1 하이버 네이트 밸리데이터 애너테이션

애너테이션 설명
@NotBlank null이 아니고 공백이 아닌가?
@NotEmpty 객체가 비어있지 않은가?
@NotNull null이 아닌가?
@Min(value) 최소 value 값은 되어야한다.
@Max(value) 최대 value 이다.
@Pattern(regex, flags) 정규표현식을 통한 검증
@Size 최대, 최소값 검사
@Email 이메일 주소인가?

5.2 커스텀 벨리데이터를 통한 POJO 유효성 검증

  1. 커스텀 벨리데이터 어노테이션 만들기
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordRuleValidator.class)
public @interface Password {
    String message() default "Password do not adhere to the specified rule";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}
  1. 커스텀 밸리데이터 처리 클래스 만들기

ConstraintValidator인터페이스를 통해서 처리 클래스를 구현한다.

public class PasswordRuleValidator implements ConstraintValidator<Password, String> {

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        ...
    }
}

5.3 jakarta bean validation docs

 

Jakarta Bean Validation specification

BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and

jakarta.ee

REFERENCE

도서 - 실전 스프링 부트