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);
}