2020-3-27 다시 시작하는 스프링(3) - 컴포넌트스캔

컴포넌트스캔

  • @SpringBootApplication만 붙여도 빈생성과 주입이 되는건 내부적으로 @ComponentScan이 붙어있기때문이다. @SpringBootApplication이 붙어있는 클래스가 탐색의 시작 지점이 된다. 이때 해당 클래스와 같은 패키지 내에 있는 빈(Bean)들을 찾아서 등록한다.
  • @ComponentScan 인터페이스에는 String[] basePacakges()라는 메서드가 정의되어있다.
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })


@SpringBootApplication
public class DemoApplication {
	@Autowired
	private MyService myService;

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(DemoApplication.class);
		app.addInitializers((ApplicationContextInitializer< GenericApplicationContext>) ctx -> {
			ctx.registerBean(MyService.class);
			ctx.registerBean(ApplicationRunner.class, () -> args1 -> System.out.println("Functional Bean Definition"));
		});
		app.run(args);
	}
}
  • 기본적으로 @Component가 붙은것들은 다 스캔을 한다.
  • @Component : 아래항목들도 내부적으로는 @Component가 붙어있다.
    • @Repository
    • @Service
    • @Controller
    • @Configuration
  • 싱글톤은 빈들은 ApplicationContext가 처음 실행될때 다 만들어진다. 초기에 등록을 하고 생성을 하기 때문에 실행하는데 오래걸린다. 구동중에는 빈을 생성하는데 오래걸리지 않는다.
  • 위의 코드에서 MyService는 DemoApplication과 다른 패키지에 존재한다. 이를 해결하기 위해 ApplicationContextInitializer의 registerBean을 이용해서 패키지 밖에 있는 빈들도 등록을 했다.

빈의 스코프

  • 싱글톤
  • 프로토타입
    • Request
    • Session
    • WebSocket
@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    private Single single;

    @Autowired
    private Proto proto;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(single);
        System.out.println(proto);
        System.out.println("result : " + proto.equals(single.getProto())); // result true
    }
}

@Component
public class Proto {
}

@Component
public class Single {
    @Autowired
    private Proto proto;

    public Proto getProto() {
        return proto;
    }
}

// case2
@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    ApplicationContext ctx;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("proto");
        System.out.println(ctx.getBean(Proto.class));
        System.out.println(ctx.getBean(Proto.class));
        System.out.println(ctx.getBean(Proto.class));
        System.out.println("single");
        System.out.println(ctx.getBean(Single.class));
        System.out.println(ctx.getBean(Single.class));
        System.out.println(ctx.getBean(Single.class));
    }
}

@Component
@Scope("prototype")
public class Proto {
}
  • 기본적인 빈이 싱글톤이기 때문에 proto빈과 single에 주입받은 proto는 같은 빈이다.
  • case2처럼 Proto에 Scope을 prototype으로 하면 다 다른 빈이 생성이 된다.
  • 문제는 Proto->Single을 참조하면 크게 문제가 없지만, Single->Proto을 참조하면 다른 빈이 참조가 되서 문제가 발생한다.
  • 싱글톤 객체를 사용하면 프로퍼티가 공유되고 스레드세이프 하지 않다. ApplicationContext 초기 구동시 인스턴스가 생성된다.
해결책 proxyMode를 설정
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
}
  • 해당빈을 class기반의 프록시로 감싸는걸로 설정을 한것이다. 이 프록시 인스턴스가 빈으로 만들어진다. 프록시빈이 Proto빈을 상속을 받았기때문에 타입은 같다.
  • 이유는 싱글톤의 빈들이 직접적으로 프로토타입의 빈을 참조하기전에 프록시를 먼저 참조하라는 의미이다. 왜 프록시를 거쳐야되나? 직접쓰면 프로토를 바꿔줄 여지가 없다.
  • 다른방법 : Proto를 참조하는 싱글톤빈에서 ObjectProvider를 통해 Proto를 참조한다.

내가 궁금한거

public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

public final class Class<T> implements java.io.Serializable
  • 여기서 Class<?>… primaySource의미가 뭘까? Class의 구현체는 Class<T>이다.
  • Class타입은 생성자가 없고 JVM안에 있는 ClassLoader에 의해 생성된다.
  • Class타입은 meta정보를 포함하는 class를 의미한다.
  • [참조
Written on March 27, 2020