前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余
一、servlet3.0规范
1、新增的注解支持
在servlet3.0之前的话,我们要添加Servlet、Filter、Listener都需要在web.xml中注册,而在servlet3.0添加了注解支持:
@WebServlet: 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet,如:
1@WebServlet(urlPatterns = {"/simple"}, asyncSupported = true, 2loadOnStartup = -1, name = "SimpleServlet", displayName = "ss", 3initParams = {@WebInitParam(name = "username", value = "tom")} 4) 5public class SimpleServlet extends HttpServlet{ … }
@WebFilter: 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器;
@WebListener:该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现对应的监听器接口
@WebInitParam:该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 <servlet> 和 <filter> 的 <init-param> 子标签。
2、runtimes pluggability(运行时插件能力)
在使用实现了servlet3.0规范的servlet容器中,Servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现,前提是ServletContainerInitializer的实现类必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer中,文件的内容就是ServletContainerInitializer实现类的全类名
1//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来; 2//传入感兴趣的类型; 3@HandlesTypes(value={HelloService.class}) 4public class MyServletContainerInitializer implements ServletContainerInitializer { 5 6 /** 7 * 应用启动的时候,会运行onStartup方法; 8 * 9 * Set<Class<?>> arg0:感兴趣的类型的所有子类型; 10 * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext; 11 * 12 * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener) 13 * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件; 14 * 必须在项目启动的时候来添加; 15 * 1)、ServletContainerInitializer得到的ServletContext; 16 * 2)、ServletContextListener得到的ServletContext; 17 */ 18 @Override 19 public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException { 20 // TODO Auto-generated method stub 21 System.out.println("感兴趣的类型:"); 22 for (Class<?> claz : arg0) { 23 System.out.println(claz); 24 } 25 26 //注册组件 ServletRegistration 27 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); 28 //配置servlet的映射信息 29 servlet.addMapping("/user"); 30 31 32 //注册Listener 33 sc.addListener(UserListener.class); 34 35 //注册Filter FilterRegistration 36 FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class); 37 //配置Filter的映射信息 38 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); 39 40 } 41 42}

使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充(比如注册三大组件),不需要额外的配置;
二、SpringMVC注解启动
在之前使用SpringMVC时,很多时候都是在web.xml中配置的方式来启动,而从SpringMVC 3.1开始就使用了servlet3.0的插件机制,可通过配置类的方式来启动SpringMVC

1、SpringServletContainerInitializer
在spring的web模块的jar包下存在META-INF/services/javax.servlet.ServletContainerInitializer,该文件中指定ServletContainerInitializer的实现类为SpringServletContainerInitializer,可知在web容启动时会加载这个类,来看看这个类:
1@HandlesTypes(WebApplicationInitializer.class)//容器启动的时候会将WebApplicationInitializer类型下面的子类(实现类,子接口等)传递过来 2public class SpringServletContainerInitializer implements ServletContainerInitializer { //webAppInitializerClasses就是WebApplicationInitializer类型 3 @Override 4 public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) 5 throws ServletException { 6 7 List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); 8 9 if (webAppInitializerClasses != null) { 10 for (Class<?> waiClass : webAppInitializerClasses) { 11 // 将webAppInitializerClasses集合中的非抽象,不是接口类型的class实例化并添加到initializer中 12 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && 13 WebApplicationInitializer.class.isAssignableFrom(waiClass)) { 14 try { 15 initializers.add((WebApplicationInitializer) waiClass.newInstance()); 16 } 17 catch (Throwable ex) { 18 throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); 19 } 20 } 21 } 22 } 23 24 if (initializers.isEmpty()) { 25 servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); 26 return; 27 } 28 29 servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); 30 AnnotationAwareOrderComparator.sort(initializers); //遍历执行initializers集合中WebApplicationInitializer.onStartup(servletContext)方法 31 for (WebApplicationInitializer initializer : initializers) { 32 initializer.onStartup(servletContext); 33 } 34 } 35 36}
接下来看看SpringServletContainerInitializer使用@HandlesTypes引入的WebApplicationInitializer接口(只定义了一个onStartup方法)的子类:

2、AbstractContextLoaderInitializer
1public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { 2 3 /** Logger available to subclasses */ 4 protected final Log logger = LogFactory.getLog(getClass()); 5 6 //该方法会在web容器启动时SpringServletContainerInitializer.onStartup中被调用 7 @Override 8 public void onStartup(ServletContext servletContext) throws ServletException { //注册加载上下文的监听器 9 registerContextLoaderListener(servletContext); 10 } 11 12 13 protected void registerContextLoaderListener(ServletContext servletContext) { //调用createRootApplicationContext()创建根容器,需要具体的实现类去实现该抽象方法获取根容器 14 WebApplicationContext rootAppContext = createRootApplicationContext(); 15 if (rootAppContext != null) { //创建监听器,并将根容器传入 16 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); //设置上下文初始化器 17 listener.setContextInitializers(getRootApplicationContextInitializers()); //添加监听器 18 servletContext.addListener(listener); 19 } 20 else { 21 logger.debug("No ContextLoaderListener registered, as " + 22 "createRootApplicationContext() did not return an application context"); 23 } 24 } 25 26 //抽象方法,子类必须实现 27 protected abstract WebApplicationContext createRootApplicationContext(); 28 //默认返回空 子类可重写 29 protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() { 30 return null; 31 } 32 33}
AbstractContextLoaderInitializer主要的功能:
调用创建createRootApplicationContext()创建根容器,
注册了监听器ContextLoaderListener(extends ContextLoader implements ServletContextListener)
3、AbstractDispatcherServletInitializer:
1public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { 2 3 /** 4 * The default servlet name. Can be customized by overriding {@link #getServletName}. 5 */ 6 public static final String DEFAULT_SERVLET_NAME = "dispatcher"; 7 8 //重写了AbstractContextLoaderInitializer.onStartup(ServletContext) 9 @Override 10 public void onStartup(ServletContext servletContext) throws ServletException { //维持父类的实现 11 super.onStartup(servletContext); //添加了注册DispatcherServlet的步骤 12 registerDispatcherServlet(servletContext); 13 } 14 15 16 protected void registerDispatcherServlet(ServletContext servletContext) { 17 String servletName = getServletName(); 18 Assert.hasLength(servletName, "getServletName() must not return empty or null"); 19 //调用createServletApplicationContext()创建web的ioc容器(管理Controller等springmvc的组件),需要子类去实现该抽象方法去获取web容器 20 WebApplicationContext servletAppContext = createServletApplicationContext(); 21 Assert.notNull(servletAppContext, 22 "createServletApplicationContext() did not return an application " + 23 "context for servlet [" + servletName + "]"); 24 //创建了前端控制器DispatcherServlet 25 FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); 26 dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); 27 //使用servletContext添加DispatcherServlet 28 ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); 29 Assert.notNull(registration, 30 "Failed to register servlet with name '" + servletName + "'." + 31 "Check if there is another servlet registered under the same name."); 32 33 registration.setLoadOnStartup(1); //具体的路径映射规则需要子类实现getServletMappings() 34 registration.addMapping(getServletMappings()); 35 registration.setAsyncSupported(isAsyncSupported()); 36 37 Filter[] filters = getServletFilters(); 38 if (!ObjectUtils.isEmpty(filters)) { 39 for (Filter filter : filters) { 40 registerServletFilter(servletContext, filter); 41 } 42 } 43 44 customizeRegistration(registration); 45 } 46 47 48 protected String getServletName() { 49 return DEFAULT_SERVLET_NAME; 50 } 51 52 53 protected abstract WebApplicationContext createServletApplicationContext(); 54 55 56 protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { 57 return new DispatcherServlet(servletAppContext); 58 } 59 60 61 protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() { 62 return null; 63 } 64 65 66 protected abstract String[] getServletMappings(); 67 68 69 protected Filter[] getServletFilters() { 70 return null; 71 } 72 73 74 protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) { 75 String filterName = Conventions.getVariableName(filter); 76 Dynamic registration = servletContext.addFilter(filterName, filter); 77 if (registration == null) { 78 int counter = -1; 79 while (counter == -1 || registration == null) { 80 counter++; 81 registration = servletContext.addFilter(filterName + "#" + counter, filter); 82 Assert.isTrue(counter < 100, 83 "Failed to register filter '" + filter + "'." + 84 "Could the same Filter instance have been registered already?"); 85 } 86 } 87 registration.setAsyncSupported(isAsyncSupported()); 88 registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName()); 89 return registration; 90 } 91 92 private EnumSet<DispatcherType> getDispatcherTypes() { 93 return (isAsyncSupported() ? 94 EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) : 95 EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE)); 96 } 97 98 99 protected boolean isAsyncSupported() { 100 return true; 101 } 102 103 protected void customizeRegistration(ServletRegistration.Dynamic registration) { 104 } 105 106}
AbstractDispatcherServletInitializer 的主要功能:
创建一个web的ioc容器:createServletApplicationContext();
创建了DispatcherServlet:createDispatcherServlet();
将创建的DispatcherServlet添加到ServletContext中,并设置路径映射等;
4、AbstractAnnotationConfigDispatcherServletInitializer
1public abstract class AbstractAnnotationConfigDispatcherServletInitializer 2 extends AbstractDispatcherServletInitializer { 3 //实现了AbstractContextLoaderInitializer.createRootApplicationContext(),创建根容器 4 @Override 5 protected WebApplicationContext createRootApplicationContext() { //获取根容器的配置类 6 Class<?>[] configClasses = getRootConfigClasses(); 7 if (!ObjectUtils.isEmpty(configClasses)) { //创建ioc容器 8 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); //注册组件 9 rootAppContext.register(configClasses); 10 return rootAppContext; 11 } 12 else { 13 return null; 14 } 15 } 16 17 //实现了AbstractDispatcherServletInitializer.createServletApplicationContext(),创建web的ioc容器 18 @Override 19 protected WebApplicationContext createServletApplicationContext() { 20 AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext(); //获取web ioc容器的配置类 21 Class<?>[] configClasses = getServletConfigClasses(); 22 if (!ObjectUtils.isEmpty(configClasses)) { // 23 servletAppContext.register(configClasses); 24 } 25 return servletAppContext; 26 } 27 28 //抽象方法 子类实现 返回根容器的配置类 29 protected abstract Class<?>[] getRootConfigClasses(); 30 31 //抽象方法 子类实现 返回web ioc容器的配置类 32 protected abstract Class<?>[] getServletConfigClasses(); 33 34}
AbstractAnnotationConfigDispatcherServletInitializer (注解方式配置的DispatcherServlet初始化器) 主要作用:
创建根容器:createRootApplicationContext(),调用getRootConfigClasses()获取配置类
创建web的ioc容器: createServletApplicationContext(),调用getServletConfigClasses()获取配置类
5、以注解方式来启动SpringMVC:
上面我们分析了三个抽象类的功能,最终需要我们需要继承AbstractAnnotationConfigDispatcherServletInitializer,实现对应抽象方法来指定DispatcherServlet的配置信息
1//web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器 2public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { 3 4 //获取根容器的配置类;(Spring的配置文件) 父容器; 5 @Override 6 protected Class<?>[] getRootConfigClasses() { 7 // TODO Auto-generated method stub 8 return new Class<?>[]{RootConfig.class}; 9 } 10 11 //获取web容器的配置类(SpringMVC配置文件) 子容器; 12 @Override 13 protected Class<?>[] getServletConfigClasses() { 14 // TODO Auto-generated method stub 15 return new Class<?>[]{AppConfig.class}; 16 } 17 18 //获取DispatcherServlet的映射信息 19 // /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp; 20 // /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的; 21 @Override 22 protected String[] getServletMappings() { 23 // TODO Auto-generated method stub 24 return new String[]{"/"}; 25 } 26 27}
3、定制SpringMVC
在一个配置类中添加@EnableWebMvc注解,开启SpringMVC定制配置功能,类似于使用xml的mvc:annotation-driven/标签:
@Configuration
@EnableWebMvc
public class WebConfig {
}
实现WebMvcConfigurer,配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Override configuration methods...
}
当然有时候我们不需要配置所有的组件,没必要去实现WebMvcConfigurer所有方法,而WebMvcConfigurerAdapter实现了WebMvcConfigurer接口的所有方法(空实现),我们只要继承该类重写我们需要实现的方法即可:
1@EnableWebMvc@Configuration 2public class AppConfig extends WebMvcConfigurerAdapter { 3 4 //定制 5 6 //视图解析器 7 @Override 8 public void configureViewResolvers(ViewResolverRegistry registry) { 9 // TODO Auto-generated method stub 10 //默认所有的页面都从 /WEB-INF/ xxx .jsp 11 //registry.jsp(); 12 registry.jsp("/WEB-INF/views/", ".jsp"); 13 } 14 15 //静态资源访问 16 @Override 17 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { 18 // TODO Auto-generated method stub 19 configurer.enable(); 20 } 21 22 //拦截器 23 @Override 24 public void addInterceptors(InterceptorRegistry registry) { 25 // TODO Auto-generated method stub 26 //super.addInterceptors(registry); 27 registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**"); 28 } 29 30}
注意:WebMvcConfigurer在5.0版本中已经被弃用了,spring的api文档有说明:as of 5.0 WebMvcConfigurer has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter 大概意思:从5.0开始,WebMvcConfigurer具有默认方法(从Java 8开始,接口可以有默认方法)并且可以直接实现而无需此适配器
最后@EnableWebMvc做了什么:
使用@Import(DelegatingWebMvcConfiguration.class)引入了DelegatingWebMvcConfiguration,是一个配置类,继承了WebMvcConfigurationSupport:
WebMvcConfigurationSupport:使用@Bean为我们默认添加了很多springmvc组件,同时留下了一些空方法给子类重写来添加组件(模板方法模式)
DelegatingWebMvcConfiguration:使用@Autowired(required = false)注入所有的WebMvcConfigurer的实现类,重写WebMvcConfigurationSupport添加组件的方法 ,实际上是依次调用WebMvcConfigurer对应的方法来添加组件
所以其实可以不使用@EnableWebMvc,直接继承WebMvcConfigurationSupport,自己去实现对应的添加组件的方法也是可以的,当然还是推荐使用WebMvcConfigurer