Spring boot 초기화 코드 작성하기
서버가 켜지자마자 상태를 지정해줘야 하는 일들이 더러 있을 것이다.
몇 가지만 알아두면 요긴하게 사용할 수 있다.
EventListener와 Runner를 사용한 방법
Spring boot에서만 사용할 수 있다.
관련 이벤트와 Runner는 boot 패키지에 포함되어 있기 때문이다.
public ConfigurableApplicationContext run(String... args) {
// Do something...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// Do something...
// spring context
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
// Do something...
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// Do something...
}
// Do something...
return context;
}
처음으로 나타나는 try 문에서 context 관련 라이프사이클이 한 번 수행된 후 listeners.started(), callRunners()를 실행한다.
Runner를 사용한 방법
callRunners의 동작은 간단하다.
bean 중에서 ApplicationRunner
, CommandLineRunner
인터페이스를 상속받은 것이 있다면 모두 실행한다.
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// ApplicationRunner bean 모두 추가
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// CommandLineRunner bean 모두 추가
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// runners 정렬
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
실행 코드를 보면 실행 전에 정렬을 하고 있다.
sort 메소드를 찍고 따라가다 보면 아래의 코드가 나타난다.
private static Integer findOrder(MergedAnnotations annotations) {
MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
if (orderAnnotation.isPresent()) {
return orderAnnotation.getInt(MergedAnnotation.VALUE);
}
MergedAnnotation<?> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);
if (priorityAnnotation.isPresent()) {
return priorityAnnotation.getInt(MergedAnnotation.VALUE);
}
return null;
}
어노테이션 @Order
혹은 @Priority
(JAVAX_PRIORITY_ANNOTATION = javax.annotation.Priority)을 사용하면 그 value를 우선순위로 사용한다.
단, 두 어노테이션을 모두 사용하면 @Order
만을 사용한다.
ApplicationRunner
ApplicationRunner
인터페이스를 상속받고 run 메소드를 작성한다.
run은 ApplicationArguments
인터페이스를 인자로 사용하는데, java 실행 시 삽입하는 인자를 추상화하여 사용하는 객체이다.
@Component
public class AppRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("==========================");
System.out.println("ApplicationRunner: " + Arrays.toString(args.getSourceArgs()));
System.out.println("==========================");
}
}
위와 같이 코드를 작성하고 확인을 위해 자바 실행 시 적절한 인자를 넣어본다.
내가 넣은 인자는 Initializing Spring boot...이다.
Intellij을 사용한다면 Run Configuration에 넣어주면 된다.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.7.RELEASE)
2020-05-11 21:50:31.130 INFO 7320 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on **** with PID 7320 (****)
2020-05-11 21:50:31.132 INFO 7320 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-05-11 21:50:31.390 INFO 7320 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.428 seconds (JVM running for 0.802)
==========================
ApplicationRunner: [Initializing, spring, boot...]
==========================
Process finished with exit code 0
위에서 확인한 것처럼 Spring이 설정이 완료된 후 동작함을 알 수 있다.
CommandLineRunner
ApplicationRunner
인터페이스와 사용법이 같다.
CommandLineRunner
인터페이스를 상속받고 run 메소드를 오버라이드하면 된다.
차이점은 인자가 String... 타입이라는 것이다.
@Component
public class CommandRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("==========================");
System.out.println("CommandLineRunner: " + Arrays.toString(args));
System.out.println("==========================");
}
}
결과도 위와 마찬가지이다.
Event를 사용한 방법
이벤트는 Spring의 core에 포함된 기능 중 하나이지만 여기서 사용할 이벤트는 boot 패키지에 포함되어 있다.
- ApplicationStartedEvent
- App이 실행된 후에 발생하는 이벤트
- ApplicationStartingEvent
- App 실행 시 발생하는 이벤트
ApplicationStartedEvent
SpringApplication.run() 에서 listeners.started()가 실행되는 시점에서 이벤트가 발생한다.
Spring event publish하는 방법을 사용해주면 된다.
@Component
public class AppStartedEvent implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("==========================");
System.out.println("ApplicationStartedEvent: " + Arrays.toString(applicationStartedEvent.getArgs()));
System.out.println("==========================");
}
}
의도대로 Runner 수행 전 위의 메시지가 나타난다.
ApplicationStartingEvent
spring에서는 이벤트를 bean으로 처리하지만, ApplicationStartingEvent
는 앱 실행 초기에 발생하는 이벤트이다.
문제는 이 이벤트가 context 메타데이터 설정 전에 발생한다는 것이다.
@Component
public class AppStartingEvent implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
System.out.println("==========================");
System.out.println("ApplicationStartingEvent: " + Arrays.toString(applicationStartingEvent.getArgs()));
System.out.println("==========================");
}
}
위의 코드는 의도대로 작동하지 않는다.
이유는 위에 명시한 대로 이벤트 발생 시점 때문이다.
@Component
를 사용하여 bean으로 등록했지만 이 이벤트는 bean 설정 전에 발생하는 것이다.
정상 동작을 위해 main 코드를 수정한다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//SpringApplication.run(DemoApplication.class, args); //default 시작 구문 삭제
SpringApplication application = new SpringApplication(DemoApplication.class);
application.addListeners(new AppStartingEvent());
application.run(args);
}
}
AppStartingEvent
클래스를 인스턴스로 만들고 리스너로 등록한다.
클래스를 직접 인스턴스로 선언했으므로 @Component
어노테이션은 의미가 없으니 제거한다.
//@Component
public class AppStartingEvent implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
System.out.println("==========================");
System.out.println("ApplicationStartingEvent: " + Arrays.toString(applicationStartingEvent.getArgs()));
System.out.println("==========================");
}
}
위의 코드를 모두 포함하고 실행하면 결과는 아래처럼 나타난다.
==========================
ApplicationStartingEvent: [Initializing, spring, boot...]
==========================
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.7.RELEASE)
2020-05-11 22:55:40.619 INFO 10684 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on **** with PID 10684 (****)
2020-05-11 22:55:40.621 INFO 10684 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-05-11 22:55:40.873 INFO 10684 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.418 seconds (JVM running for 0.786)
==========================
ApplicationStartedEvent: [Initializing, spring, boot...]
==========================
==========================
CommandLineRunner: [Initializing, spring, boot...]
==========================
==========================
ApplicationRunner: [Initializing, spring, boot...]
==========================
Process finished with exit code 0
인스턴스의 라이프 사이클을 활용한 방법
@PostConstruct
을 메소드에 붙이면 생성자 호출 후 바로 실행된다.
SpringApplication.run()에서 context refresh할 때 싱글턴 타입의 bean 인스턴스를 생성하기 때문에 Spring boot 시작 로그가 끝나기 전에 동작하게 된다.
@Component
public class UsePostConstruct {
@PostConstruct
public void init() {
System.out.println("==========================");
System.out.println("PostConstruct");
System.out.println("==========================");
}
}
자매품으로 @PreDestroy
가 있다. 인스턴스 파괴 전 실행된다.
여기까지 모두 포함하면 결과는 아래처럼 나타난다.
==========================
ApplicationStartingEvent: [Initializing, spring, boot...]
==========================
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.7.RELEASE)
2020-05-11 23:08:05.399 INFO 10976 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on **** with PID 10976 (****)
2020-05-11 23:08:05.401 INFO 10976 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
==========================
PostConstruct
==========================
2020-05-11 23:08:05.661 INFO 10976 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.428 seconds (JVM running for 0.79)
==========================
ApplicationStartedEvent: [Initializing, spring, boot...]
==========================
==========================
CommandLineRunner: [Initializing, spring, boot...]
==========================
==========================
ApplicationRunner: [Initializing, spring, boot...]
==========================
Process finished with exit code 0
별거 없지만, 혹시나 풀 코드를 나중에 확인해보고 싶을까봐 업로드해두었다.
'Java > Spring framework' 카테고리의 다른 글
Spring에서 AOP를 구현하는 방법과 Transactional (0) | 2020.05.26 |
---|---|
Spring boot 자동 설정 분석 (0) | 2020.03.15 |
Spring framework 소스코드 읽어보기 - Bean 생성 원리 (3) (0) | 2020.02.23 |
Spring framework 소스 코드 읽어보기 - Bean 생성 원리 (2) (1) | 2020.01.27 |
Spring framework core (12) - Spring AOP (0) | 2020.01.24 |