springframework

o.s.w IoC 핵심기술. @Component 와 @ComponentScan

Jungsoomin :) 2020. 8. 12. 01:30

@Component 은 스프링이 관리하는 객체로 등록해달라는 어노테이션 이다.

  1. @Repository

  2. @Service

  3. @Controller

  4. @Configuration

 

@ComponentScan 은 @Component 에노테이션들을 스캔해 등록하는데, 구동시에 등록하기 떄문에 스캔하여 등록할 Bean 다수라면 구동시간이 걸릴 수 있다. 하지만 이것은 구동시간에만 국한된 이야기다.

  • @SpringBootApplication 에노테이션은 이미 @ComponentScan을 포함한다, 속성으로는 basePackageClasses가 basePackage 속성보다 type-safe 하며, 똑같이 해당 위치에서 스캔하여 하위 패키지 까지 스캔해낸다.

@SpringBootApplication(scanBasePackageClasses = InfeanspringApplication.class)
//@ComponentScan(basePackageClasses = InfeanspringApplication.class)
  • 중요한 속성은 IncludeFilters 와 ExcludeFilter 가 있다. 해당 속성은 @Filter 의 배열을 가지며, 이를 이용해 스캔대상에 포함시키거나, 제외시킨다.

@ComponentScan(includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {ControllerAdvice.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyService.class})
}
,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {ExceptionHandler.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyRunner.class})
})
@SpringBootApplication(exclude = {Repository.class}, excludeName = {"이름1","이름2"})

AutoConfigurationExcluedFiler 는 스프링 부트가 제공하는 오토 컨피규어 레이션 관련 필터이며, 자동 설정 클래스에 대한 스캔을 제외하는 것으로 보인다.


스프링 5버전으로 부터 지원하는 Functional 한 Bean 등록방법이 있어, 구동타임에 성능을 증가시켜줄 수 있다.

 

그 전에, 일단 스프링 부트 구동방식은 2가지이다.

  1. SpringApplication의 정적 메서드 run( 구동클래스 , args );

  2. SpringApplication 객체를 만들어( 매개값은 구동클래스 ) run( args ); 

		SpringApplication.run(InfeanspringApplication.class, args);


        SpringApplication app = new SpringApplication(InfeanspringApplication.class);
        app.run(args);

 

이를 이용해 구동 사이에 Funtinal한 Bean등록을 한다. 

  1. SpringApplication 객체addInitializers( ApplicationInitializer ) 메서드를 호출하며 매개값으로

  2. ApplicationContextInitializer 의 익명객체 주어  즉, ApplicationInitializerinitialize(ApplicationContext) 메서드의 재정의 코드에 ApplicationContextregitserBean( BeanClass ); 를 호출한다. 

  3. 참고로 ApplicationInitializer <> 제네릭에 선언된 ApplicationContext 를 주입받을 수 있게 해준다.

		SpringApplication.run(InfeanspringApplication.class, args);
	
		app.addInitializers(new ApplicationContextInitializer<GenericApplicationContext>() {
            @Override
            public void initialize(GenericApplicationContext ctx) {
                ctx.registerBean(MyService.class);
            }
        })

ApplicationContextInitializer 익명객체를 만들때 해당 인터페이스는 함수형인터페이스 이므로 lamda 를 이용할 수 있다.

  1. 장점으로는 조건문이나 흐름제어를 이용하거나 추가적인 코드를 Bean 객체 등록 시에 설정할수 있다는 점.

  2. ApplicationContext 객체의 registerBean()은 매개값으로 Supplier<BeanClass> 를 주어 해당 빈의 설정을 만들어내어 리턴해줄 수도 있다.

  3. 그냥 빈을 만들때는 ctx.registerBean( BeanClass ) , 

  4. 빈을 만들때 주입이나 설정이 필요한 경우 ctx.registerBean( BeanClass , new Suplier(get 메서드 재정의) ); 를 사용하면 될 듯 하다. Suplier의 get 메서드설정한 빈을 받아 해당 클래스의 빈을 등록해주는 방법이다.

		SpringApplication app = new SprinApplication(InfeanspringApplication.class);
        
		app.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) ctx
                -> {ctx.registerBean(MyService.class);
                    ctx.registerBean(ApplicationRunner.class, new Supplier<ApplicationRunner>() {
                        @Override
                        public ApplicationRunner get() {
                            return new ApplicationRunner() {
                                @Override
                                public void run(ApplicationArguments args) throws Exception {
                                    System.out.println("Functional Bean Definition !!");
                                }
                            };
                        }
                    });
        });
        app.run(args);

Supplier<>의 get()함수형 인터페이스의 메서드 이므로 lamda 로 표현할 수 있다.

		SpringApplication app = new SprinApplication(InfeanspringApplication.class);
        
		app.addInitializers(
        
       	(ApplicationContextInitializer<GenericApplicationContext>) ctx ->
        {

                ctx.registerBean(MyService.class);

            ctx.registerBean(ApplicationRunner.class,
                    (Supplier<ApplicationRunner>) () -> 
                    	runner1 
                        	-> System.out.println("Initialize Bean Definition!!"));
        });
        
        app.run(args);
  1. SpringApplication 객체를 구동클래스를 매개 값으로 생성
  2. .addInitializer( ApplicationInitializer ) 메서드로 스프링 컨테이너 초기화 
  3. ApplicationContextInitializer 객체의 .registerBean( BeanClass , [Supplier( BeanClass )]) 메서드로 빈 등록
  4. 빈 등록 과정에서 SpringBoot 구동후 실행되는 ApplicationRunner 클래스의 익명객체를 등록, 
  5. 마지막 출력문은 ApplicationRunner의 run() 메서드의 재정의 문.

BeanFactoryPostProcessor를 구현ConfigrationClassPostPocessor에 의해 함수적 Bean 등록이 적용된다.

  1. 다른 모든 빈들을 만들기 전 BeanFactoryPostProcessor 구현체들을 적용시킨다.

  2. 즉, 다른 빈들을 등록하기 전ComponentScan 을 하여 해당 Bean을 등록한다.


@Bean 등 직접적으로 스프링 빈을 등록하는 법을 대처할 때에는 Functional한 빈 등록 방법이 좋겠으나, @Component 스캔의 대안으로는 적합하지 않다.