GenericApplicationContext를 사용할 수 있다.public class HellobootApplication {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(HelloController.class);
applicationContext.refresh();
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(context -> {
context.addServlet("front-controller", new HttpServlet() {
@Override
protected void service(HttpServletRequest req,
HttpServletResponse res) throws IOException {
// 공통 기능 처리 ...
if (req.getRequestURI().equals("/hello") && req.getMethod().equals(HttpMethod.GET.name())) {
String name = req.getParameter("name");
HelloController helloController = applicationContext.getBean(HelloController.class);
String response = helloController.hello(name);
res.setContentType(MediaType.TEXT_PLAIN_VALUE);
res.getWriter().println(response);
} else if (req.getRequestURI().equals("/user")) {
// ...
} else {
res.setStatus(HttpStatus.NOT_FOUND.value());
}
}
}).addMapping("/*");
});
webServer.start();
}
}
HelloController를 new로 생성하지 않고 applicationContext에 등록하고 refresh()를 호출하면 빈 등록과 활성화가 끝난다.HelloController를 불러와 사용할 수 있다.HelloController가 의존하는 SimpleHelloService를 만들어보자.public class SimpleHelloService {
String sayHello(String name) {
return "Hello" + name;
}
}
public class HelloController {
public String hello(String name) {
SimpleHelloService helloService = new SimpleHelloService();
return helloService.sayHello(Objects.requireNonNull(name));
}
}
HelloController는 파라미터 name이 null인지만 체크하고 로직 처리를 위임한다.SimpleHelloService를 빈으로 등록해 스프링 컨테이너가 DI할 수 있도록 수정해보자HelloService 인터페이스를 추가하고 SimpleHelloService를 구현체로 만들고 HelloController에서 생성자 주입을 사용한다.public interface HelloService {
String sayHello(String name);
}
public class SimpleHelloService implements HelloService {
@Override
public String sayHello(String name) {
return "Hello" + name;
}
}
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
public String hello(String name) {
return helloService.sayHello(Objects.requireNonNull(name));
}
}
applicationContext에 SimpleHelloService를 등록하면 스프링 컨테이너가 의존 관계를 조립한다.GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(HelloController.class);
applicationContext.registerBean(SimpleHelloService.class);
applicationContext.refresh();
DispatcherServlet이 필요하다.
public class HellobootApplication {
public static void main(String[] args) {
GenericWebApplicationContext applicationContext = new GenericWebApplicationContext();
applicationContext.registerBean(HelloController.class);
applicationContext.registerBean(SimpleHelloService.class);
applicationContext.refresh();
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(context -> {
context.addServlet("dispatcherServlet",
new DispatcherServlet(applicationContext)
).addMapping("/*");
});
webServer.start();
}
}
DispatcherServlet을 사용하도록 변경하니 코드가 많이 깔끔해졌지만 매핑 정보가 없어 /hello 요청을 받을 수가 없다.@RestController
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
@GetMapping("/hello")
public String hello(String name) {
return helloService.sayHello(Objects.requireNonNull(name));
}
}
DispatcherServlet은 빈을 모두 뒤져서 매핑 정보를 모두 추출해 알맞은 웹 요청에 호출할 빈을 선택한다.GenericWebApplicationContext는 onRefresh 메서드로 refresh 시점에 동작 시킬 작업을 추가할 수 있다.
public class HellobootApplication {
public static void main(String[] args) {
GenericWebApplicationContext applicationContext = new GenericWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(context ->
context.addServlet("dispatcherServlet",
new DispatcherServlet(this)
).addMapping("/*"));
webServer.start();
}
};
applicationContext.registerBean(HelloController.class);
applicationContext.registerBean(SimpleHelloService.class);
applicationContext.refresh();
}
}
@Configuration
public class HellobootApplication {
@Bean
public HelloController helloController(HelloService helloService) {
return new HelloController(helloService);
}
@Bean
public HelloService helloService() {
return new SimpleHelloService();
}
public static void main(String[] args) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(context ->
context.addServlet("dispatcherServlet",
new DispatcherServlet(this)
).addMapping("/*"));
webServer.start();
}
};
applicationContext.register(HellobootApplication.class);
applicationContext.refresh();
}
}
GenericWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 사용해야 한다.@Bean 어노테이션을 붙이면 스프링 컨테이너가 읽을 수 있다.@Configuration 어노테이션을 붙여야 스프링 컨테이너가 빈 구성 정보가 있는 팩토리 메서드가 있겠구나 판단할 수 있다.HellobootApplication 클래스를 applicatiojnContext에 등록하면 빈들이 등록된다.@Component 어노테이션 또는 @Component 어노테이션을 가지는 메타 어노테이션을 클리스 레벨에 붙이면 빈으로 등록할 수 있다.HellobootApplication에 @ComponentScan 어노테이션을 붙이면 클래스를 뒤져 @Component 어노테이션을 찾아 스프링 컨테이너에 빈으로 등록한다.@RestController
public class HelloController {
@Service
public class SimpleHelloService implements HelloService {
@Configuration
@ComponentScan
public class HellobootApplication {
public static void main(String[] args) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(context ->
context.addServlet("dispatcherServlet",
new DispatcherServlet(this)
).addMapping("/*"));
webServer.start();
}
};
applicationContext.register(HellobootApplication.class);
applicationContext.refresh();
}
}
TomcatServletWebServerFactory와 DispatcherServlet은 기능을 담당하는 객체는 아니지만 웹 서버를 실행할 때 꼭 필요한 객체들이다.
@Configuration
@ComponentScan
public class HellobootApplication {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
public static void main(String[] args) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = this.getBean(ServletWebServerFactory.class);
DispatcherServlet dispatcherServlet = this.getBean(DispatcherServlet.class);
WebServer webServer = serverFactory.getWebServer(context ->
context.addServlet("dispatcherServlet", dispatcherServlet
).addMapping("/*"));
webServer.start();
}
};
applicationContext.register(HellobootApplication.class);
applicationContext.refresh();
}
}
TomcatServletWebServerFactory와 DispatcherServlet을 @Bean 어노테이션과 팩토리 메서드로 빈 등록 한다.onRefresh() 메서드 내부에서 생성하는 것이 아닌 context에서 getBean 메서드로 받아올 수 있다.DispatcherServlet에 appcliationContext를 생성자에 전달하지도 않았는데 어떻게 동작하는걸까?
ApplicationContextAware 인터페이스에 비밀이 있다.public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
DispatcherServlet은 ApplicationContextAware 인터페이스를 구현하고 있다.
ApplicationContext를 가지고 있어야 하는 빈이라면 이 인터페이스를 구현하면 자신이 빈으로 등록되는 시점에 스프링 컨테이너가 ApplicationCnotext를 setApplicationContext()로 전달해준다.ApplicationContext 타입을 받도록 하면 스프링 컨테이너가 알아서 넣어준다.applicationContext에 WebServerFactory와 DispahtcherServlet 설정하는 코드를 별도 클래스로 분리해보자.public class MySpringApplication {
public static void run(Class<?> applicationClass, String... args) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = this.getBean(ServletWebServerFactory.class);
DispatcherServlet dispatcherServlet = this.getBean(DispatcherServlet.class);
WebServer webServer = serverFactory.getWebServer(context ->
context.addServlet("dispatcherServlet", dispatcherServlet
).addMapping("/*"));
webServer.start();
}
};
applicationContext.register(applicationClass);
applicationContext.refresh();
}
}
@Configuration
@ComponentScan
public class HellobootApplication {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
public static void main(String[] args) {
MySpringApplication.run(HellobootApplication.class, args);
}
}
MySpringApplication 대신 원래 스프링 부트의 클래스인 SpringApplication으로 대체해도 잘 동작한다. public static void main(String[] args) {
SpringApplication.run(HellobootApplication.class, args);
}