传统 XML 配置

  1. <!DOCTYPE web-app PUBLIC
  2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
  4. <web-app>
  5. <display-name>Archetype Created Web Application</display-name>
  6. <listener>
  7. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  8. </listener>
  9. <context-param>
  10. <param-name>contextConfigLocation</param-name>
  11. <param-value>WEB-INF/spring.xml</param-value>
  12. </context-param>
  13. <servlet>
  14. <servlet-name>mvc</servlet-name>
  15. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  16. <init-param>
  17. <param-name>contextConfigLocation</param-name>
  18. <param-value>WEB-INF/web.xml</param-value>
  19. </init-param>
  20. <load-on-startup>1</load-on-startup>
  21. </servlet>
  22. <servlet-mapping>
  23. <servlet-name>mvc</servlet-name>
  24. <url-pattern>*.do</url-pattern>
  25. </servlet-mapping>
  26. </web-app>
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:mvc="http://www.springframework.org/schema/mvc"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. https://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/mvc
  9. https://www.springframework.org/schema/mvc/spring-mvc.xsd">
  10. <mvc:annotation-driven/>
  11. </beans>

为什么 Spring MVC 可以做到 0-XML 配置

因为 Servlet 3.0 出了一个规范,所有容器,再启动的时候,需要调用一个实现了 **ServletContainerInitializer 接口 下的 onStartup **方法,具体的实现类,需要再 META-INF.services 下新建一个 javax.servlet.ServletContainerInitializer **文件,内容为实现了 ServletContainerInitializer 接口的**完整类名,同时容器会扫描该类名上的 @HandlesTypes(XXX.class),并扫描 XXX.class 的所有实现类,并传递给 onStartup 方法,同时还会给 onStartup 方法传递一个容器上下文对象

image.png

  1. public interface ServletContainerInitializer {
  2. public void onStartup(Set<Class<?>> c, ServletContext ctx)
  3. throws ServletException;
  4. }

利用这一点,我们就可以当容器启动时调用 onStartup 方法的时候,完成 Spring 容器的初始化步骤,同时配置 DispathServlet 对象

Spring-web 的实现

  1. @HandlesTypes(WebApplicationInitializer.class)
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  3. @Override
  4. public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
  5. throws ServletException {
  6. List<WebApplicationInitializer> initializers = new LinkedList<>();
  7. if (webAppInitializerClasses != null) {
  8. for (Class<?> waiClass : webAppInitializerClasses) {
  9. if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
  10. WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
  11. try {
  12. initializers.add((WebApplicationInitializer)
  13. ReflectionUtils.accessibleConstructor(waiClass).newInstance());
  14. }
  15. catch (Throwable ex) {
  16. throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
  17. }
  18. }
  19. }
  20. }
  21. if (initializers.isEmpty()) {
  22. servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
  23. return;
  24. }
  25. servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
  26. AnnotationAwareOrderComparator.sort(initializers);
  27. for (WebApplicationInitializer initializer : initializers) {
  28. initializer.onStartup(servletContext);
  29. }
  30. }
  31. }

所以,我们只需要实现 WebApplicationInitializer 接口就可以完成原来 web.xml 中的配置了

实现 Spring MVC 0-XML 配置 - web.xml

  1. public class MyWebApplicationInitializer implements WebApplicationInitializer {
  2. /**
  3. * Spring MVC 0-XML 配置的原理:
  4. *
  5. * 因为 servlet 3.0 的一个新规范(ServletContainerInitializer),
  6. * 而 tomcat 也遵守了了这个规范,所以会调用 实现了 WebApplicationInitializer 的 onStartup 方法
  7. *
  8. * Spring 定义了一个 org.springframework.web.SpringServletContainerInitializer 类,实现了 servlet 3.0 的这个新规范
  9. *
  10. * Spring 实现方式
  11. * spring-web\META-INF\services\javax.servlet.ServletContainerInitializer,是指需要实现的接口
  12. * javax.servlet.ServletContainerInitializer 文件里面的 org.springframework.web.SpringServletContainerInitializer 是实现类
  13. * @HandlesTypes(WebApplicationInitializer.class) 会由容器进行解析里面定义的接口,找出所有实现类,并回调给 Set<Class<?>> webAppInitializerClasses
  14. * Spring 再对 webAppInitializerClasses 的子类进行遍历,分别调用其 onStartup 的方法
  15. *
  16. * @param servletContext Web上下文对象
  17. * @throws ServletException
  18. */
  19. public void onStartup(ServletContext servletContext) throws ServletException {
  20. System.out.println("-------onStartup-------");
  21. // Load Spring web application configuration
  22. AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
  23. ac.register(AppConfig.class);
  24. // ac.refresh();
  25. // Create and register the DispatcherServlet
  26. // DispatcherServlet 的 init 方法
  27. DispatcherServlet servlet = new DispatcherServlet(ac);
  28. ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
  29. registration.setLoadOnStartup(1);
  30. registration.addMapping("*.do");
  31. }
  32. }

实现 spring.xml 的 Java Configuration

通过 @EnableWebMvc 就可以完美的替代 spring.xml 中的 ,就可以真正的做到 0-XML 配置了,实现 WebMvcConfigurer 接口,可以提供丰富的配置信息(视图解析器,消息转换器)

  1. @Configuration
  2. @ComponentScan("org.wesoft.mvc")
  3. @EnableWebMvc // 相当于 XML 中的 <mvc:annotation-driven/>
  4. public class AppConfig implements WebMvcConfigurer {
  5. /*
  6. Apache Commons FileUpload
  7. To use Apache Commons FileUpload,
  8. you can configure a bean of type CommonsMultipartResolver with a name of multipartResolver.
  9. You also need to have commons-fileupload as a dependency on your classpath.
  10. */
  11. @Bean
  12. public CommonsMultipartResolver multipartResolver() {
  13. CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
  14. return commonsMultipartResolver;
  15. }
  16. // 会在 Spring mvc 环境初始化完成之后,加载这个方法
  17. @Autowired
  18. public void iniArgumentsResolvers(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
  19. List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(requestMappingHandlerAdapter.getArgumentResolvers());
  20. List<HandlerMethodArgumentResolver> customArgumentResolvers = requestMappingHandlerAdapter.getCustomArgumentResolvers();
  21. argumentResolvers.removeAll(customArgumentResolvers);
  22. argumentResolvers.addAll(0, customArgumentResolvers);
  23. requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
  24. }
  25. /**
  26. * 视图处理
  27. *
  28. * @param registry
  29. */
  30. @Override
  31. public void configureViewResolvers(ViewResolverRegistry registry) {
  32. registry.jsp("/page/", ".html");
  33. }
  34. /**
  35. * 添加消息解析器
  36. *
  37. * @param converters
  38. */
  39. @Override
  40. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  41. FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
  42. converters.add(fastJsonHttpMessageConverter);
  43. }
  44. /**
  45. * 添加参数解析器
  46. *
  47. * @param resolvers initially an empty list
  48. */
  49. @Override
  50. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
  51. // 这里其实是传过来一个空集合,对应着 customArgumentResolvers
  52. // 也就是我们其实是在 customArgumentResolvers 里添加自定义方法
  53. // 然后 spring mvc 会将这个集合合并到 argumentResolvers 中,所以并不能保证执行顺序
  54. resolvers.add(new UserParamResolver());
  55. }
  56. }

内嵌 Tomcat

  1. compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.56'
  2. Xcompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '8.5.56'
  1. public class App {
  2. public static void main(String[] args) throws Exception {
  3. Tomcat tomcat = new Tomcat();
  4. tomcat.setPort(8080);
  5. // Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
  6. // context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
  7. String sourcePath = App.class.getResource("/").getPath();
  8. Context ctx = tomcat.addWebapp("/", new File("research-webmvc/src/webapp").getAbsolutePath());
  9. WebResourceRoot resources = new StandardRoot(ctx);
  10. resources.addPreResources(new DirResourceSet(resources, "/production/classes", sourcePath, "/"));
  11. ctx.setResources(resources);
  12. tomcat.start();
  13. tomcat.getServer().await();
  14. }
  15. }