[강의요약] 스프링 부트 개념과 활용 - 4부 스프링 부트 활용 (1)

개인적인 학습을 위한 Inflearn - 스프링부트 개념과 활용(백기선) 강의 요약입니다.

개념과 원리 위주로 요약합니다.

이전 글 에서 이어집니다.

4부 - 스프링부트 활용


1. 스프링 부트 활용 소개

1-1. Springboot 핵심 기능

  • SpringApplication
  • 외부 설정, 프로파일
  • 로깅, 테스트, dev-tools

1-2. 기술 연동

  • spring web mvc
  • 스프링 데이터, 스프링 시큐리티
  • REST API 클라이언트

2. SpringApplication 1부

스프링 로거 기본 값은 INFO 이다.

VMOption = -Ddebug (program argument = —debug) 를 주어서 변경할 수 있다

어떤 자동 설정이 적용 되었는지 debug 로그에 찍힘.

2-1. FailureAnalyzer

2-1-1. 배너 : resources/banner.txt 로 커스텀 가능.

application.properties에서 파일 위치 변경. 인코딩, 파일 형식 변경 등 설정 가능

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

${} 로 변수가 사용 가능. 소스로 실행할 때는 MANIFEST 변수는 불러올 수 없다.

# resources/banner.txt
==============================
Cherrue Banner ${spring-boot.version}
==============================

->

==============================
Cherrue Banner 2.2.0.RELEASE
==============================

배너 소스 구현도 가능

@SpringBootApplication
public class SpringinitApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(SpringinitApplication.class);
        app.setBanner(new Banner() {
            @Override
            public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
                out.println("==================");
                out.println("HELLO");
                out.println("==================");
            }
        });
        app.run(args);
    }
}

2-1-2. SpringApplicationBuilder : builder 패턴이 적용

static method 인 SpringApplication.run을 사용하면 커스텀을 할 수 없다.

인스턴스를 만들어 run 해주거나 builder 를 사용하자

@SpringBootApplication
public class SpringinitApplication {
    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(SpringinitApplication.class)
                .run(args);
        // same as
				// SpringApplication app = new SpringApplication(SpringinitApplication.class);
        // app.run(args);
    }
}

3. SpringApplication 2부

spring 에는 다양한 시점(타이밍)이 있다. 이 시점에 트리거를 줄 수 있는 리스너를 만들어보자

3-1. Listener

3-1-1. 어플리케이션 컨텍스트 생성 이전의 이벤트

// @Component 이 리스너는 빈일 필요가 없다. 어플리케이션 컨텍스트 생성 이후의 리스너에는 필요
public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("=======================");
        System.out.println("Application is Starting");
        System.out.println("=======================");
    }
}

⚠️ 어플리케이션 컨텍스트 생성 전의 이벤트는 빈으로 된 리스너 사용이 불가능해서 직접 등록을 해주어야 한다.

@SpringBootApplication
public class SpringinitApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(SpringinitApplication.class);
        app.addListeners(new SampleListener()); // 등록
        app.run(args);
    }
}

결과

me.cherrue.SpringinitApplication --debug
=======================
Application is Starting
=======================
2022-02-03 21:10:59.341 DEBUG 27529 --- [           main] .c.l.ClasspathLoggingApplicationListener : Application started with classpath: [ ...
==============================
Cherrue Banner 2.2.0.RELEASE
==============================
2022-02-03 21:10:59.418  INFO 27529 --- [           main] me.cherrue.SpringinitApplication

2-1-2. 어플리케이션 컨텍스트 생성 이후의 이벤트

아래와 같이 @Component 를 붙여서 Bean 으로 만들어주면 알아서 등록이 된다.

@Component
public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("=======================");
        System.out.println("Application is Starting");
        System.out.println("=======================");
    }
}

3-2. WebApplicationType

REACTIVE : servlet이 있어도 web flux를 쓰겠다는 설정

app.setWebApplicationType(WebApplicationType.REACTIVE);

3-3. Application arguments

Application arguments : Program arguments 로 들어온 값

  • Bean 생성자에 Bean 하나만 받으면 스프링이 알아서 주입해준다.
@Component
public class ArgumentsPrinter {
    public ArgumentsPrinter(ApplicationArguments args) {
        System.out.println("foo : " + args.containsOption("foo"));
        System.out.println("bar : " + args.containsOption("bar"));
    }
}
$ java -jar target/springinit-1.0-SNAPSHOT.jar -Dfoo --bar

2022-02-03 21:21:21.164 DEBUG 27635 --- [           main] o.s.boot.SpringApplication               : Loading source class me.cherrue.SpringinitApplication
foo : false
bar : true

3-4. ApplicationRunner

어플리케이션을 실행한 후 무언가 실행하고 싶을 때 사용.

CommandLineRunner 이런 애들도 있다. (조금 무식하게 접근해야 해서 불편함)

@Order(숫자) 를 통해 실행 순서를 정할 수 있음

// ApplicationRunner
@Component
public class ArgumentsRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("foo : " + args.containsOption("foo"));
        System.out.println("bar : " + args.containsOption("bar"));
    }
}

// CommandLineRunner
@Component
public class ArgumentsRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
			Arrays.stream(args).forEach(System.out::println);
    }
}

4. 외부 설정 1부

4-1. 설정 값 우선순위

application.properties의 우선순위는 15위 (총 17위까지 있음)

커맨드 라인 아규먼트는 4위 (java -jar target/aa.jar —myArguments)

test properties : test/resources 생성 + Project Structure > Modules > Test Resources 지정하면 기존 프로퍼티 덮어씀

  • main의 빌드 결과를 클래스패스에 넣은 후 테스트의 빌드 결과를 부어버리기 때문
  • 하지만 test/resources/application.properties를 쓰면 원래 프로퍼티와 필드를 같게 가져야 하니 주의
  • @TestPropertySource와 @SpringBootTest(properties=…) 가 같은 역할이 가능하니 활용할 것

⚠️ application.properties 의 위치도 우선 순위가 다르다. (./config, ./, classpath:/, classpath:/config)

4-2. 설정 값을 불러오는 방법

  1. @Value 어노테이션 사용

     // application.properties
     cherrue.name = "Cherrue"
        
     // SampleRunner.java
     @Component
     public class SampleRunner implements ApplicationRunner {
         @Value("${cherrue.name")
         private String name;
        
         @Override
         public void run(ApplicationArguments args) throws Exception {
             System.out.println("======================");
             System.out.println(name);
             System.out.println("======================");
         }
     }
    

5. 외부 설정 2부

5-1. 설정 값을 한 번에 불러오자

  1. @ConfigurationProperties에 관련 설정 값 선언
  2. getter / setter 구현
  3. SpringbootApplication에 @EnableConfigurationProperties(CherrueProperties.class) : 빈 등록과 어노테이션 처리 - 안 해도 이미 포함되어 있음
  4. ConfigurationProperties에 @Component 붙이기

     // application.properties
     cherrue.name = Cherrue
     cherrue.age = ${random.int(0,100)}
     cherrue.fullName = ${cherrue.name} Lee
        
     // CherrueProperties.java
     @Component
     @ConfigurationProperties("cherrue")
     public class CherrueProperties {
         private String name;
         private int age;
         private String fullName;
        
         public String getName() {
             return name;
         }
        
         public void setName(String name) {
             this.name = name;
         } 
        
         public int getAge() {
             return age;
         }
        
         public void setAge(int age) {
             this.age = age;
         }
        
         public String getFullName() {
             return fullName;
         }
        
         public void setFullName(String fullName) {
             this.fullName = fullName;
         }
     }
    
  5. 설정값 사용처에서 @Autowired로 불러온 프로퍼티 호출 = getter/setter 반환형 명시로 type safe

     @Component
     public class SampleRunner implements ApplicationRunner {
         @Autowired
         CherrueProperties cherrueProperties;
         @Override
         public void run(ApplicationArguments args) throws Exception {
             System.out.println("======================");
             System.out.println(cherrueProperties.getName());
             System.out.println("======================");
         }
     }
    

📌 Tips. dependency에 spring-boot-configuration-processor를 추가하면 properties 파일에서 자동 완성이 된다

6. 외부 설정 3부

6-1. 융통성 있는 바인딩

full-name, full_name, fullName 모두 바인딩 알아서 됨

6-2. 프로퍼티 타입 컨버전

cherrue.age = 100 ⇒ @Value(”${cherrue.age}”) int age;

Properties에는 자료형이 모두 문자열이지만, spring이 제공하는 기본적인 타입 컨버전을 통해 알아서 붙여준다.

  1. 특수한 컨버전 : @DurationUnit

     // application.properties
     cherrue.name = Cherrue
     cherrue.age = ${random.int(0,100)}
     cherrue.fullName = ${cherrue.name} Lee
     cherrue.sessionTimeout=25
     # 또는 cherrue.sessionTimeout=25s
     # s suffix  쓰면 어노테이션 없이도  duration으로 설정 가능
        
     // CherrueProperties.java
     @Component
     @ConfigurationProperties("cherrue")
     public class CherrueProperties {
         String name;
         int age;
         String fullName;
            
         @DurationUnit(ChronoUnit.SECONDS)
         private Duration sessionTimeout = Duration.ofSeconds(30);
        
         public Duration getSessionTimeout() {
             return sessionTimeout;
         }
        
         public void setSessionTimeout(Duration sessionTimeout) {
             this.sessionTimeout = sessionTimeout;
         }
     ...
     }
    

    실행결과

     ======================
     Cherrue
     81
     PT25S
     ======================
    

6-3. 프로퍼티 값 검증

JSR-303 Bean validation 1.0의 검증이 가능(@NotEmpty, @Size 등)

// application.properties
cherrue.name = 
cherrue.age = ${random.int(0,100)}
cherrue.fullName = ${cherrue.name} Lee
cherrue.sessionTimeout=25

// CherrueProperties.java
@Component
@ConfigurationProperties("cherrue")
@Validated
public class CherrueProperties {
    @NotEmpty
    String name;
    int age;
    String fullName;
...
}

실행결과

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'cherrue' to me.cherrue.CherrueProperties failed:

    Property: cherrue.name
    Value: 
    Origin: class path resource [application.properties]:2:0
    Reason: 반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.

댓글남기기