配置xml | 功能概述 |
---|---|
web.xml | listener,servlet等配置信息 |
spring-mvc.xml | 配置@Controller注解扫描,请求参数绑定的注解驱动,静态资源,JSON转换器,文件上传等等配置 |
applicationContext.xml | 配置spring容器初始化,譬如包扫描,数据源,事务,AOP等等 |
简单分析下web.xml 内容
org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/applicationContext.xml app org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/spring-mvc.xml 1 app /app/*
以下内容基于 springframework 4.3.11.RELEASE 版本进行分析
我们来一起分析下 web.xml
![adf1450814c3c7907bc2084a903726ed.png](https://i-blog.csdnimg.cn/blog_migrate/e86a71d9008e80c155fee93e9de794af.png)
1)配置 ContextLoaderListener
ContextLoaderListener作为一个Listener会首先启动,该监听器会去加载applicationContext.xml,用于创建WebApplicationContext,并做为spring根容器 “Root WebApplicationContext” ,用来加载除了controller等web组件外的所有bean。
那么web容器启动时,是如何自动去装配Spring applicationContext.xml呢?
这是因为ContextLoaderListener实现了ServletContextListener这个接口,并实现了 contextInitialized,contextDestroyed 这两个方法,由于tomcat遵从servlet规范, 在web容器启动时,会自动执行 ContextLoaderListener 实现的contextInitialized 方法,那么spring在该方法调用initWebApplicationContext方法进行初始化 WebApplicationContext,解析applicationContext.xml文件,进行bean的装配。xml文件解析出来的applicationContext就是XmlWebApplicationContext 这个对象。
其调用链路图:
其调用链路图:
对于springmvc构造web项目,web.xml的起到关键作用,也就是ContextLoaderListener,DispatcherServlet这两个关键类,那么我们能通过编程式的方式来构造一个功能等价于 web.xml吗?答案是可以,也就是spring官网对spring mvc编程式的概述。详情可移步到 https://spring.io/projects/spring-framework,进行深入学习。
这里我贴下官网的关于web.xml编程式代码:
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletCxt) { // Load Spring web application configuration AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); ac.refresh(); // Create and register the DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); }}
二:servlet 3.0规范第一步:创建WebApplicationContext容器,当然若是用xml配置,则创建XmlWebApplicationContext对象,若基于AnnotationConfig注解的方式,则创建AnnotationConfigWebApplicationContext。
第二步 :注入注解配置类AppConfig.class,譬如包扫描 @ComponentScan,bean 的实例化等等。 第三步 :创建DispatcherServlet,将AnnotationConfigWebApplicationContext作为父容器注入进去,同时配置加载顺序,以及访问路径扫描映射等等。 最后: 剩下个关键节点问题,就是如何让servlet容器去加载我们编写的MyWebApplicationInitializer,进行一些列初始化?这就得聊到servlet3.0新特性。
详细内容
https://jcp.org/aboutJava/communityprocess/mrel/jsr315/index.html
这里我摘取部分内容,关键介绍信息在 “8.2.4 Shared libraries / runtimes pluggability” 这一章节
大致意思是这样的:
1、Servlet容器启动时,会进行自动扫描应用里每一个jar包中的类,看看哪个类实现了 ServletContainerInitializer 2、必须在 WEB-INF/services/javax.servlet.ServletContainerInitializer这文件内,定义一个实现 ServletContainerInitializer 全类路径 3、最后,也就是比较有趣的一个注解 @HandlesTypes ,该注解实现的效果是在Servlet 容器回调我们实现 ServletContainerInitializer接口的类时,会返回我们使用了 @HandlesTypes这个注解中定义的接口所有实现类
好了, servlet3.0给我们提供的新特性讲完了,那么springmvc是如何对其进行“精装”呢?回过头来,我们看下我们的MyWebApplicationInitializer,它实现了WebApplicationInitializer ,但这个接口是spring自定义的类,那servlet容器启动时,是如何进行回调到呢?其实,在springframework的spring-web模块中,也遵从servlet3.0规范,在 WEB-INF/services/javax.servlet.ServletContainerInitializer这文件内,定义了
org.springframework.web.SpringServletContainerInitializer
该 ServletContainerInitializer实现了servlet的ServletContainerInitializer接口,实现了onStartup方法,以下就是它详细信息:
@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer { public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List initializers = new LinkedList(); if (webAppInitializerClasses != null) { for (Class> waiClass : webAppInitializerClasses) { if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) 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); // 循环调用所有实现了WebApplicationInitializer接口的实现类 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } }}
还记得上文说到的servlet3.0规范中的 @HandlesTypes 注解吗?spring-web中的 SpringServletContainerInitializer类定义了该注解,并且指明了类型为WebApplicationInitializer ,也就是说所有实现了WebApplicationInitializer接口的类,都会在 servlet 容器启动时加载到,并在回调SpringServletContainerInitializer类的onStartup方法时,设置到webAppInitializerClasses 这个set 集合参数。所以,spring-web就顺利拿到了我们自定义的MyWebApplicationInitializer ,并且调用我们实现的onStartup。
至此,我们就可以采用编程式的方式进行无配置化去创建web项目。
三:spring-boot是如何无配置化启动web容器 以下内容基于spring-boot 1.5.7.RELEASE版本进行分析那么从一开始的web.xml ,到现在编程式的方式去实现web.xml功能,以及知道了如何让servlet容器启动时回调处理我们实现的web.xml功能,但是spring-boot是直接使用springframework的spring-web模块吗?
其实不完全是的,spring-boot也遵从了servlet3.0规范,但没有直接使用spring-web中定义的WebApplicationInitializer接口,并去实现它,而是另辟蹊径,在项目启动,创建WebApplicationContext时,会调用到AbstractApplicationContext的 refresh方式中的一个比较重要的方法,那就是onRefresh方式(模板方法),该方法其实是调用EmbeddedWebApplicationContext类的onRefresh 具体实现,然后调用其createEmbeddedServletContainer方法,同时采用匿名方法实现了 ServletContextInitializer 接口,进行创建内嵌servlet 容器。
//创建内嵌的servlet容器private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext(); if (localContainer == null && localServletContext == null) { //根据当前使用容器类型,创建对应的工厂类,譬如tomcat,jetty EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); //进行实例化对应的servlet容器,譬如new个tomcat对象, //getSelfInitializer()是实例化对应的ServletContextInitializer,以便servlet容器启动时,完成回调onStartup方法进行初始化servlet容器 this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources();}
其中getSelfInitializer方法采用匿名内部类实现了ServletContextInitializer接口:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } };}
getEmbeddedServletContainer方法开始对tomcat容器实例化:
public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatEmbeddedServletContainer(tomcat);}
以下就是 spring-boot 启用内嵌 tomcat 容器代码调用链:
最后,我们简单做个总结下,基于servlet3.0的新特性,以及springframework的 spring-web模块,实现了无配置化启动web容器;而spring boot采用内嵌web容器的方式,匿名方法实现了ServletContextInitializer接口达到了无配置化启动web容器的效果。
以上只是个人一些看法,若有不对的地方,欢迎各位看官多多指教!