본문 바로가기
카테고리 없음

스프링 애플리케이션이 다뜨고나서 간단한 메세지를 보고싶다면 어떻게 해야될까?

by simplify-len 2021. 3. 2.

유튜브 토비의 스프링 부트-1 

 해당 강의에서 말하는 "토비의 스프링 부트 1 - 스프링 부트 앱에 초기화 코드를 넣는 방법 3가지"

들으면서, 궁금했던 내용들을 함께 풀어가면서 해결해봅시다.

ApplicationRunner 와 CommandRunner의 차이는 무엇일까?

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see ApplicationRunner
 */
@FunctionalInterface
public interface CommandLineRunner {

	/**
	 * Callback used to run the bean.
	 * @param args incoming main method arguments
	 * @throws Exception on error
	 */
	void run(String... args) throws Exception;

}
/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see CommandLineRunner
 */
@FunctionalInterface
public interface ApplicationRunner {

	/**
	 * Callback used to run the bean.
	 * @param args incoming application arguments
	 * @throws Exception on error
	 */
	void run(ApplicationArguments args) throws Exception;

}

 

run Method에서 매개변수로 받는 부분이 다르다. CommandLineRunner는 Raw한 String으로 받는 반면, ApplicationRunner는 ApplicationArguments 로 받는다.

 사용방법은 아래와 같다.

package com.like.len.springbootstudy;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class SpringbootStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootStudyApplication.class, args);

    }

    @Bean
    public CommandLineRunner myCliRunner(){
        return new MyCliRunner();
    }
}

@Component
class MyCliRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Hello CommandLineRunner");
    }
}

 

ApplicationRunner 도 마찬가지로 동작된다.

마지막 3번째 방법은 EventListener 을 사용하는 방법이다.

이벤트 리스너

간단한 예시는 아래와 같다.

@SpringBootApplication
public class SpringbootStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootStudyApplication.class, args);

    }

    @EventListener(ApplicationReadyEvent.class)
    public void init(){
        System.out.println("Hello ApplicationReadyEvent~!");
    }
}

 

 

Springboot 안에서 이벤트를 사용할 수 있는 방법은 다양하다. 

@SpringBootApplication
public class SpringbootStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootStudyApplication.class, args);
        ConfigurableApplicationContext ac = SpringApplication.run(SpringbootStudyApplication.class, args);
        ac.addApplicationListener(new ApplicationListener<MyEvent>() {
            @Override
            public void onApplicationEvent(MyEvent event) {
                System.out.println("Hello MyEvent: " + event.message);
            }
        });
        ac.publishEvent(new MyEvent(ac, "LenSpringBoot Event"));
    }
}

여기서 SpringApplication.run() 을 동작하면 ConfigurableApplicationContext 나온다는 사실을 처음 알았다. 

ApplicationContext 의 conext를 이어서 가져오는 객체

static class MyEvent extends ApplicationEvent {

        private final String message;

        public MyEvent(Object source, String message){
            super(source);
            this.message = message;
        }
    }
}

또 다른 방법으로. 

public static void main(String[] args) {
        ConfigurableApplicationContext ac = SpringApplication.run(SpringbootStudyApplication.class, args);
        ac.publishEvent(new MyEvent(ac, "LenSpringBoot Event"));
    }


    @EventListener(MyEvent.class)
    public void onMyEvent(MyEvent myEvent){
        System.out.println("Hello My Event");
    }
    ...
}

dnl위 코드에서는 ac.publishEvent에서 이벤트를 퍼블리쉬하는데, 이 때 MyEvent는 아래 EventListener에 의해서 동작됩니다.

또는 아래와 같이할수도 있습니다.

@EventListener
public void onMyEvent2(MyEvent event){
	System.out.println("Hello My Event" + event.message);
}

만약 어노테이션을 활용하는 방법이라면? 아래와같이 작업할 수 있습니다.

    @MyEventListener
    public void onMyEvent(MyEvent event){
        System.out.println("Hello My Annotation Event");
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @EventListener
    @interface MyEventListener {

    }

ApplicationReadyEvent 의 정체는 무엇인가?

@EventListener(ApplicationReadyEvent.class)
public void onMyEvent(){
	System.out.println("Application Ready!!!");
}

먼저 ApplicationReadyEvent 의 Javadoc를 살펴보면 아래와 같다.

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Event published as late as conceivably possible to indicate that the application is
 * ready to service requests. The source of the event is the {@link SpringApplication}
 * itself, but beware of modifying its internal state since all initialization steps will
 * have been completed by then.
 *
 * @author Stephane Nicoll
 * @since 1.3.0
 * @see ApplicationFailedEvent
 */
@SuppressWarnings("serial")
public class ApplicationReadyEvent extends SpringApplicationEvent {

	private final ConfigurableApplicationContext context;

	/**
	 * Create a new {@link ApplicationReadyEvent} instance.
	 * @param application the current application
	 * @param args the arguments the application is running with
	 * @param context the context that was being created
	 */
	public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) {
		super(application, args);
		this.context = context;
	}

	/**
	 * Return the application context.
	 * @return the context
	 */
	public ConfigurableApplicationContext getApplicationContext() {
		return this.context;
	}

}

 

먼저 boot 안에 있는 클래스라는 사실과 함께, SpringApplicationEvent를 상속받습니다. 비슷한 성격을 클래스로는 

 

이렇게 있습니다.

 

그럼 각각의 이벤트는 언제 일어나는가? 이는 SpringApplication.class 을 잘 살펴보면 찾을 수 있다.

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

 

댓글