在《从Servlet到Spring MVC》中,介绍了基于xml配置使用的方式,但我们我现在用的更多的基于注解零配置的方式,尤其是在使用SpringBoot的时候,只需要引入web的start包即可,这边文章前面会简单介绍一下Spring MVC零配置的的使用,然后详细分析Spring MVC启动的原理,可以更加深入理解为什么只需要简单的配置,就可以提供强大的功能
一、零配置Spring MVC实现在之前,先简单介绍一下Spring MVC是如何整合Spring的,在Spring MVC的官网,提供了一张父子容器的图:
从上面这张图可以清晰的看到,在Spring MVC整合Spring中,其实是由两个容器组成的,其中下面的根容器就是Spring自身的容器,而上面的容器,是Spring MVC特有的容器,那为什么要这么设计呢?只是用一个容器不行吗
其实这种设计方法最大的考量兼容第三方MVC框架,比如以前常用的Struts框架,MVC容器用于存放Controller、视图解析器、处理器映射器这样的Bean,而提供服务的Bean由下层的Spring容器来管理,实现了解耦。但在SpringBoot中就不再使用父子容器,SpringBoot作为一个集成的解决方法,就是使用SpringMVC来作为Web框架,不需要再兼容其他第三方框架,那么直接使用是一个容器就可以了。
既然由两个容器,那么两个容器再启动时,就会用到不同的配置来加载Bean,所有使用零配置实现SpringMVC时首先就要定义两个配置文件
1.1 定义配置文件定义根容器的配置类,不扫描@Controller注解的类和子容器的配置类
@Configuration @ComponentScan(basePackages = "com.lizhi",excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}), @ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ), }) public class RootConfig { }
定义子容器的配置类,只扫描有@RestController和@Controller注解的类,同时需要添加@EnableWebMvc注解
子容器的配置类,可以实现WebMvcConfigurer接口,该接口中提供了添加拦截器、资源处理器、参数解析器等的扩展,下面我们只配置添加一个拦截器
@Configuration @ComponentScan(basePackages = {"com.lizhi"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class}) },useDefaultFilters =false) @EnableWebMvc public class WebAppConfig implements WebMvcConfigurer{ @Bean public LizhiInterceptor lizhiInterceptor() { return new LizhiInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(lizhiInterceptor()).addPathPatterns("/*"); } } public class LizhiInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor....preHandle"); return true; } }1.2 实现初始化器接口
上面定义完了配置类,但是并没有配置这两个配置类该给哪个容器用,所以,接下来就是要去实现AbstractAnnotationConfigDispatcherServletInitializer抽象类,定义每个容器启动时加载的类
在创建容器时,可以通过下面的方法,来获取配置类进行解析
public class LizhiStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // IOC 父容器的启动类 @Override protected Class>[] getRootConfigClasses() { return new Class[]{RootConfig.class}; } // IOC子容器配置 web容器配置 @Override protected Class>[] getServletConfigClasses() { return new Class[]{WebAppConfig.class}; } // 我们前端控制器DispatcherServlet的拦截路径 @Override protected String[] getServletMappings() { return new String[]{"/"}; } }二、Spring MVC容器启动灵魂 —SPI
在使用Spring MVC整合Spring的时候,我们没有像执行main()方法一样去手动创建Spring的容器,那么Spring的容器又是在什么时候创建的,这就要说到Java强大的扩展机制 ——SPI(Service Provider Interface)),翻译过来就是服务提供商接口,那SPI是如何用的呢?
2.1 Java扩展机制SPI按照Java的SPI规范,我们只要在meta-INF/services目录创建一个文件,文件名就是服务提供商提供的接口名,而文件内容就是实现这个接口的类的全限定名,然后Java在运行时候,可以通过ServiceLoader来加载这些类,这种方法提供了良好的扩展,下面代码示例SPI的使用:
定义一个接口:
public interface Search { public ListsearchDoc(String keyword); }
定义两个接口的实现类:
public class FileSearch implements Search{ @Override public ListsearchDoc(String keyword) { System.out.println("文件搜索 "+keyword); return null; } }
public class DatabaseSearch implements Search{ @Override public ListsearchDoc(String keyword) { System.out.println("数据搜索 "+keyword); return null; } }
然后在meta-INF/services目录下创建一个接口权限名的文件:
文件内容如下:
com.lizhi.FileSearch com.lizhi.DatabaseSearch
测试方法:ServiceLoader的load()方法会拿到接口对应的文件里面的实现类,然后在iterator的next()方法中,会去实例化这些实现类,这就是Java SPI的使用
public class TestCase { public static void main(String[] args) { ServiceLoaders = ServiceLoader.load(Search.class); Iterator iterator = s.iterator(); while (iterator.hasNext()) { Search search = iterator.next(); search.searchDoc("hello world"); } } }
注:数据库连接的依赖包中,比如mysql-connector-java-5.1.44.jar,也有利用这种SPI机制来加载驱动
2.2 Servlet规范中的SPI在Servlet3.1的规范中,明确指出Web容器需要支持对javax.servlet.ServletContainerInitailizer接口的扩展,Web容器(Tomcat)在启动的的时候,会根据meta-INF/services目录中的文件内容,去加载所有ServletContainerInitailizer的实现类,然后调用它们的onStartup()方法
public interface ServletContainerInitializer { public void onStartup(Set> c, ServletContext ctx) throws ServletException; }
而Spring MVC中就定义了一个实现该接口的类SpringServletContainerInitializer
该类上面的注解@HandlesTypes指定了,在调用onStartup()方法时,第一个参数需要传入什么类型的实现类
SpringMVC指定了需要传入WebApplicationInitializer的实现类或接口
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { …… } }
SpringMVC在onStartup()方法的实现中,然后拿到了所有实现了WebApplicationInitializer接口的类和接口,但是,会把接口和抽象类过滤掉,SpringMVC自身提供了三个实现类,分别是:AbstractContextLoaderInitializer、AbstractDispatcherServletInitializer和AbstractAnnotationConfigDispatcherServletInitializer,不过这三个类都是抽象类,在启动的时候是没法使用的,这就是为什么我们在零配置使用SpringMVC的时候需要,需要添加一个类,来继承AbstractAnnotationConfigDispatcherServletInitializer抽象类
过滤完之后,就会去遍历所有过滤得到的WebApplicationInitializer类的是实现类,然后调用它们的onStartup()方法
List三、创建父子容器 3.1 创建父容器initializers = Collections.emptyList(); if (webAppInitializerClasses != null) { initializers = new ArrayList<>(webAppInitializerClasses.size()); for (Class> waiClass : webAppInitializerClasses) { // 接口和抽象类servlet容器也会给我们,但是我们不要 // 排除接口和容器 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { // 实例化,然后添加到集合中 initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } } } AnnotationAwareOrderComparator.sort(initializers); // 调用initializer.onStartup 进行扩展 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }
在前面我们定义的AbstractAnnotationConfigDispatcherServletInitializer的实现类LizhiStarterInitializer中,并没有实现onStartup()方法,所以会去调用父类的onStartup()方法
AbstractAnnotationConfigDispatcherServletInitializer中onStartup()方法的定义如下:
因为其继承自AbstractContextLoaderInitializer抽象类,所以又会去调用父类的onStartup()方法
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { //registerContextLoaderListener ok super.onStartup(servletContext); // registerDispatcherServlet registerDispatcherServlet(servletContext); } …… }
在AbstractContextLoaderInitializer类的onStartup()方法中,就会去创建Spring的父容器,然后再创建一个ContextLoaderListener类型的监听器,这个监听器实现了ServletContextListener接口,可以监听Servlet上下文信息,这个我们在使用xml开发时,是要固定配置的,后面会详细讲到这个监听器的用处
最后把这个监听器添加到Servlet容器中
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { // 创建父容器 , WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); // 设置初始化器 listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } } …… }
创建父容器的方法实现在AbstractAnnotationConfigDispatcherServletInitializer类中,看到这里就很熟悉了,根据getRootConfigClasses()方法来获取父容器的配置类,然后注册该配置类,到这里,Spring容器还并没有启动,只是创建完成了而已
protected WebApplicationContext createRootApplicationContext() { Class>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(configClasses); return context; } else { return null; } }3.2 创建子容器
在AbstractDispatcherServletInitializer中,调用父类的onStartup()创建完父容器之后,接着就会去调用registerDispatcherServlet()方法来创建子容器,以及创建DispatcherServlet实例
public void onStartup(ServletContext servletContext) throws ServletException { //registerContextLoaderListener ok super.onStartup(servletContext); // registerDispatcherServlet registerDispatcherServlet(servletContext); }
registerDispatcherServlet()方法中,会调用createServletApplicationContext()方法创建子容器,逻辑与创建父容器一样
然后调用createDispatcherServlet()方法来创建DispatcherServlet,会把子容器设置到DispatcherServlet实例中,这个需要注意,在DispatcherServlet初始化的时候,会使用到子容器的
然后就是把DispatcherServlet添加到Servlet上下文中,返回一个ServletRegistration.Dynamic的对象,然后设置一些DispatcherServlet的基础信息,这些信息都是在使用xml时需要手动配置的
protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return null or empty"); // 创建子容器 WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); // 创建DispatcherServlet frameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); // 启动时加载 registration.setLoadOnStartup(1); // 映射 registration.addMapping(getServletMappings()); // 是否异步支持 registration.setAsyncSupported(isAsyncSupported()); // 设置DispatcherServlet的过滤器 Filter[] filters = getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)