> 文章列表 > SpringMVC启动流程方式分析-三种方式

SpringMVC启动流程方式分析-三种方式

SpringMVC启动流程方式分析-三种方式

SpringMVC的启动方式

本文所叙述的是springmvc放入Tomcat servlet容器的启动方式

第一种Web.xml文件配置

使用传统的web.xml配置文件, 指定DispatchServlet ,当然如果想要父子容器的效果指定一个ContextLoaderListener 上下文加载监听器就行, 他们都要分别指定各自的配置文件。

<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>

Servlet 3.0之后就已经支持listner、servlet 这些类的配置注解化,不使用web.xml。因此我们得找到一个Tomcat 也就是web容器启动的生命周期函数扩展接口。ServletContainerInitializer 即SPI服务器发现机制

如果不知道SPI服务发现机制的请自行百度,既然学到了这些经典框架,spi是必先掌握的知识

第二种实现WebApplicationInitializer接口

第一记住整个接口时SpringMvc自己的接口, 不属于Servlet规范的接口。所以SpringMvc怎么指定了自己的接口给开发者使用,如何和Servlet规范接口绑定联系我们得找出来。
一个小提示: 当我们不熟悉整个接口可以按照SpringMvc官方文档然后实现接口。可以使用Debug查看调用栈,梳理调用关系

public class MyWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();webApplicationContext.setConfigLocation("/spring/springmvc.xml");webApplicationContext.refresh();DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);dynamic.setLoadOnStartup(1);dynamic.addMapping("/");}
}

SpringMVC启动流程方式分析-三种方式

所以去看看这个时什么玩意:

@HandlesTypes(WebApplicationInitializer.class) //这个注解告诉Servlet容器, onStartup这个方法的set集合传入实现了WebApplicationInitializer这个接口的Class
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// 应为我们要反射调用所以这里是waiClass必须是一个类,不能是接口抽象的,以及必须是WebApplicationInitializer的子类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) {//调用自己的WebApplicationInitializer 接口,这个就是留给开发者使用的。initializer.onStartup(servletContext);}}}

看到这里我们已经看到了真像,等待不是说SPI, 熟悉Java官方的SPI的都知道, 所以Spring需要遵循ServletContainerInitializer的使用规范。
SpringMVC启动流程方式分析-三种方式
到这里Spring把自己的扩展接口WebApplicationInitializer 成功与Servlet容器启动规范接口绑定起来了。回调的WebApplicationInitializer 的onStartup接口里面我们可以拿到ServletContext 应用上下文。这样我们就可以注册Servlet、Filter、Listener代替掉第一种方式的Web.xml。

WebApplicationInitializer 这个接口使用过于抽象了, 这个接口留给开发了比较多的和业务无关的工作量, 比如需要考虑使用什么ApplicationContext注册到Servlet容器,各种Servlet、Listner、Filter 都需要编写,如果要使用父子容器还需要我们自己编写逻辑。是否可以进一步具体一下,即第三种方式。

第三种 AbstractAnnotationConfigDispatcherServletInitializer

这个抽象类,实现很多大部分的通用代码逻辑,比如父子容器的赋值逻辑、注册,我们指需要实现他留下的方法指定一下一些关键配置位置、映射路径等。

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*父容器配置类*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[0];}/*子容器的配置类*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[0];}/*dipathceryServlet映射路径*/@Overrideprotected String[] getServletMappings() {return new String[0];}/*filter*/protected Filter[] getServletFilters() {return null;}
}

从这里我们就很简单的可以编写配置性的代码,无需关注如何注册DispathServlet、已经父容器、子容器等一些和业务无关的配置代码细节,具体封装细节可以自己看看,了解一下别人的设计思想,比较既然想学框架一些架构性的思想,总不能停留到会使用的层面