Servlet 정리하고 넘어가자!
스프링 부트를 쓰다보면 잘 모르고 넘어가는 Spring MVC Flow! 이번기회에 한번 정리해보려고 합니다~
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
RequestMappingHandlerMapping
이라는 Handler Mapping 사용 → Controller에 @RequestMapping
으로 사용가능 ( 우리가 사용하는 @GetMapping등은 다 포함하고 있는 것이다)RequestMappingHandlerAdapter
이라는 Handler Adapter 사용 → 위와 같이 @RequestMapping
으로 사용가능View name을 Controller가 넘겨주면 Dispatcher Servlet에 내장되어있는 여러 ViewResolver
가 적절한 view를 찾아서 연결해준다.
view Name
반환해주면@GetMapping("/customers/{customerId}")
public String findCustomer(@PathVariable("customerId") UUID customerId, Model model){ // Model로
Optional<Customer> customer = customerService.getACustomer(customerId);
if(customer.isPresent()){
model.addAttribute("customer", customer.get());
return "views/customer-details"; // View Name
} else {
return "views/404";
}
}
configureViewResolvers
에 여러가지 등록static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// jsp
registry.jsp().viewNames("jsp/*");
// thymeleaf
SpringResourceTemplateResolver springResourceTemplateResolver = new SpringResourceTemplateResolver();
springResourceTemplateResolver.setApplicationContext(applicationContext);
springResourceTemplateResolver.setPrefix("/WEB-INF/");
springResourceTemplateResolver.setSuffix(".html");
SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.setTemplateResolver(springResourceTemplateResolver);
ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();
thymeleafViewResolver.setTemplateEngine(springTemplateEngine);
thymeleafViewResolver.setOrder(1);
thymeleafViewResolver.setViewNames(new String[]{"views/*"}); // view하위에 모든 template는 thymeleaf사용
registry.viewResolver(thymeleafViewResolver);
}
// Controller만
@EnableWebMvc // spring mvc가 필요한 bean들 자동등록
@Configuration
@ComponentScan(basePackages = "com.programmers.demo.customer",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class),
useDefaultFilters = false)
static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { // WebMvcConfigurer: MVC에 대해 특정한 설정을 해주기 위해
...
}
// Service, Repository만
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.programmers.demo.customer",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class)
)
static class RootConfig {
@Override
public void onStartup(ServletContext servletContext) {
// root applicationContext
logger.info("Starting Server...");
AnnotationConfigWebApplicationContext rootApplicationContext = new AnnotationConfigWebApplicationContext();
rootApplicationContext.register(RootConfig.class);
ContextLoaderListener loaderListener = new ContextLoaderListener(
rootApplicationContext);
servletContext.addListener(loaderListener);
// servlet applicationContext
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.register(ServletConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
Dynamic servletRegistration = servletContext.addServlet("test", dispatcherServlet); // 서블릿추가
servletRegistration.addMapping("/"); // 모든 요청이 dispatcherServlet이 하게된다
servletRegistration.setLoadOnStartup(-1); //default = -1 기다렸다가
}
→ 두 기능을 분리해서 WAS의 부담을 줄여준다 → 시용감이 좋아짐
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
헷갈리던 개념이었는데 잘 정리하고 가요!