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 {
@Override
public 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("/**");
}
}
所以去看看这个时什么玩意:
@HandlesTypes(WebApplicationInitializer.class) //这个注解告诉Servlet容器, onStartup这个方法的set集合传入实现了WebApplicationInitializer这个接口的Class
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public 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的使用规范。
到这里Spring把自己的扩展接口WebApplicationInitializer 成功与Servlet容器启动规范接口绑定起来了。回调的WebApplicationInitializer 的onStartup接口里面我们可以拿到ServletContext 应用上下文。这样我们就可以注册Servlet、Filter、Listener代替掉第一种方式的Web.xml。
WebApplicationInitializer 这个接口使用过于抽象了, 这个接口留给开发了比较多的和业务无关的工作量, 比如需要考虑使用什么ApplicationContext注册到Servlet容器,各种Servlet、Listner、Filter 都需要编写,如果要使用父子容器还需要我们自己编写逻辑。是否可以进一步具体一下,即第三种方式。
第三种 AbstractAnnotationConfigDispatcherServletInitializer
这个抽象类,实现很多大部分的通用代码逻辑,比如父子容器的赋值逻辑、注册,我们指需要实现他留下的方法指定一下一些关键配置位置、映射路径等。
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/*父容器配置类*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/*子容器的配置类*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[0];
}
/*dipathceryServlet映射路径*/
@Override
protected String[] getServletMappings() {
return new String[0];
}
/*filter*/
protected Filter[] getServletFilters() {
return null;
}
}
从这里我们就很简单的可以编写配置性的代码,无需关注如何注册DispathServlet、已经父容器、子容器等一些和业务无关的配置代码细节,具体封装细节可以自己看看,了解一下别人的设计思想,比较既然想学框架一些架构性的思想,总不能停留到会使用的层面。