- 编写根容器配置类
@Configuration @ComponentScan("com.yicj.study.mvc.hello.service") public class RootConfig { }
- 编写web容器配置类
@EnableWebMvc @Configuration @ComponentScan("com.yicj.study.mvc.hello.controller") public class WebConfig { @Bean public InternalResourceViewResolver viewResolver(){ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver() ; viewResolver.setPrefix("/WEB-INF/jsp/"); viewResolver.setSuffix(".jsp"); return viewResolver ; } }
- 编写WebApplicationInitializer实现类,一般直接继承AbstractAnnotationConfigDispatcherServletInitializer类
// SpringServletContainerInitializer实现了ServletContainerInitializer接口 // 在SpringServletContainerInitializer的onStartup方法中会 public class StarterWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // 根容器的配置类 @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{RootConfig.class}; } // web容器的配置类 @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } // DispatcherServlet拦截地址 @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 指定DispatcherServlet中的filter @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("UTF-8", true) ; return new Filter[]{encodingFilter}; } }
- 编写业务代码,以及控制层代码
@Data public class User { private Integer id ; private String name ; } // @Service public class UserServiceImpl implements UserService { @Override public User saveUser(User user) { user.setId(1); return user ; } } // @Controller public class HomeController { @ResponseBody @GetMapping("/hello") public String hello(){ return "hello world" ; } @GetMapping("/helloworld") public String helloworld(){ return "helloworld" ; } }
- 在webapp/WEB-INF/jsp/ 目录中创建helloworld.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>hello test</title> </head> <body> Hello World!<br/> </body> </html>
- 添加依赖
<!-- 注意这里要写war否则项目可能无法运行--> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> </dependencies>
- Servlet3.x规范利用spi机制,自动加载实现ServletContainerInitializer接口的类,并调用onStartup方法。
- SpringServletContainerInitializer根据@HandlesTypes(WebApplicationInitializer.class)进行class筛选。
- 在onStartup中调用所有WebApplicationInitializer对象的onStart方法。
- SpringServletContainerInitializer代码实现
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
补充说明
- 在嵌入式容器中@WebServlet不会被自动扫描,替代方案可使用@ServletComponentScan解决
@ServletComponentScan("com.yicj.web.servlet") @SpringBootApplication public class WebMvcApplication { }
- 在嵌入式容器中ServletContainerInitializer将不会被自动加载,替代方案可使用ServletContextInitializer解决
@Bean public ServletContextInitializer servletContextInitializer(){ return servletContext ->{ CharacterEncodingFilter filter = new CharacterEncodingFilter() ; FilterRegistration.Dynamic dynamic = servletContext.addFilter("c-filter", filter); dynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false,"/"); } ; }
- 在嵌入式容器中@WebServlet不会被自动扫描,替代方案可使用@ServletRegistrationBean解决
// ServletRegistrationBean 继承ServletContextInitializer,所以这里与2中效果等同 @Bean public ServletRegistrationBean servletRegistrationBean(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new AsyncServlet(), "/async-servlet") ; return registrationBean ; }
- ServletContextInitializer加载的流程
2.1 ServletWebServerApplicationContext容器刷新方法onRefresh() 调用 createWebServer() 创建Web服务 2.2 创建容器时,通过getSelfInitializer() => getServletContextInitializerBeans() 获取所有的ServletContextInitializer 2.3 遍历ServletContextInitializer实例并调用onStartup(servletContext)方法