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