在SpringBoot的项目中, 咱们能够看到, web.xml、springmvc.xml、applicationContext.xml这样的配置文件已 经不见了, 取而代之的是各类的注解, 注解开发给咱们带来了不少的便利, 利用JavaConfig完成项目的配置会显得更 加的"高端", 本篇文章主要分析的是无XML实现SpringMVC环境搭建的原理, 若是理解了这些原理, 那么SpringBoot的 也就不会太神秘了java
众所周知, Tomcat等web容器是用来放置Servlet的, Tomcat完成对底层网络的处理, 最后才将请求打到Servlet中,
在Servlet3.0规范出来后, 引入了许多的新特性, 其中一个特性是这样的, 咱们能够在/META-INF/services文件夹
下建立一个名为javax.servlet.ServletContainerInitializer的文件, 这个文件中能够放置任何的
ServletContainerInitializer接口的实现类, ServletContainerInitializer接口是一个函数式接口, 有一个
onStartup方法, Servlet3.0规范中指定, 在web容器中启动的过程当中, 会扫描整个项目中/META-INF/services文件
夹下名为javax.servlet.ServletContainerInitializer的文件, 调用这个文件中全部
ServletContainerInitializer实现类的onStartup方法, 下面咱们来演示一下:
目录结构:
src
main
java
resources
META-INF
services
javax.servlet.ServletContainerInitializer
文件javax.servlet.ServletContainerInitializer内容:
com.test.MyWebInit
MyWebInit类代码:
public class MyWebInit implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println( "startUp......" );
}
}
分析:
启动tomcat, 会发现控制台输出了: startUp......
咱们再来看看这个onStartup方法, 有一个Set<Class<?>>的变量, 以及一个ServletContext, 后者你们应该很熟悉
了, Servlet上下文, 咱们来介绍一下前者, 有这么一个注解@HandlesTypes, Servlet3.0规范中指定, 当该注解标
注在ServletContainerInitializer上的时候, web容器启动的过程当中就会将整个项目中该注解中指定的接口的全部
实现类的Class对象放入一个set中并传入onStrartup方法中, 下面咱们来修改下上述的例子:
package com.test.startup;
public interface StartUpInterface {}
public class StartUpInterfaceImpl implements StartUpInterface {}
@HandlesTypes( StartUpInterface.class )
public class MyWebInit implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println( c );
}
}
分析:
启动tomcat, 发现控制台输出了: [class com.test.startup.StartUpInterfaceImpl]
由上面的特性, 咱们就能够了解到, web容器提供了扩展点, 在容器启动的过程当中, 容许咱们作一些事情, 利用这个扩
展点, 咱们就能实现无xml对SpringMVC的环境进行配置了
复制代码
结合以前的DispatcherServlet初始化流程源码分析, 咱们须要知道一个点, 以前咱们配置SpringMVC的环境的时候,
是在web.xml中配置了DispatcherServlet的映射关系, 而且利用监听器注册了一个Spring容器, 在xml中配置
DispatcherServlet的映射关系, 其实就是将DispatcherServlet这个Servlet加入到了Servlet上下文中而已, 因而
咱们联想上面的例子, 貌似咱们能够手动的建立DispatcherServlet对象, 而后加到ServletContext中???
其次, 没有了springmvc.xml文件, 那么xml文件中配置的annotation-driven以及component-scan这些功能就无法
启用, 无法开启注解配置以及配置扫描的包了
再一次回顾前面的DispatcherServlet初始化流程源码分析的内容, DispatcherServlet继承于FrameworkServlet,
FrameworkServlet重写了GenericServlet的空参init方法, 在这个重写的方法中完成了容器的初始化, 因而乎, 我
们再来回顾一下, 容器的获取方式, 一般状况下, 是在这个初始化的过程当中会自动建立web容器, 可是这个建立的web
容器xmlApplicationContext, 而且不方便咱们去控制其建立的过程, 可是在原来的源码分析中, 笔者也提到了, 我
们能够手动的往DispatcherServlet中set一个容器, 那就好办了, 咱们往里面set一个注解配置的容器, 而不是xml
配置的容器, 因而, 无xml配置的原理就出来了, 咱们先看一下代码:
public class MyWebInit implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
AnnotationConfigWebApplicationContext context
= new AnnotationConfigWebApplicationContext();
context.register( SpringConfig.class );
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext( context );
ServletRegistration.Dynamic registration
= ctx.addServlet( "dispatcherServlet", dispatcherServlet );
registration.addMapping( "/" );
registration.setLoadOnStartup( 1 );
// registration.setInitParameter();
}
}
分析:
<1> 手动建立一个注解配置的Web容器, 若是对Spring源码有所了解, 那么register方法应该不会陌生, 就是
注册一个配置类, 这个配置类的内容很简单:
@Configuration
@ComponentScan( "com.test" )
@EnableWebMvc
public class SpringConfig {}
<2> 咱们在独立的Spring环境下, 建立Spring环境是这样的:
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(SpringConfig.class);
能够在构造器中放入一个配置类, 构造器被执行的时候, 会调用register方法将SpringConfig配置类注册
到容器中, 同时调用refresh方法开始完成容器的初始化, 在web环境下的容器, 没有提供一个构造器能够
传递配置类的, 因而咱们才手动调用register方法来注册一个配置类, 注意了, 咱们没有调用refresh方法
以后咱们再来分析下缘由
<3> 手动建立一个DispatcherServlet, 同时将建立好的web容器设置到DispatcherServlet中, 以后
DispatcherServlet在初始化的时候就不会本身建立了, 而是采用咱们传入的这个
<4> 将DispatcherServlet添加到Servlet上下文中, 就等价于在web.xml中配置了一个DispatcherServlet,
而后利用返回值registration(ServletRegistration.Dynamic类型的)添加这个Servlet须要拦截的请求,
最后两行就等价于在web.xml中的配置:
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
到此为止, 咱们利用Java代码完成了在web.xml中配置的DispatcherServlet的功能, 与此同时, 整合了注解配置的
web容器与DispatcherServlet, 再来讲下为何咱们不主动调用refresh方法刷新容器, 回顾一下原来的
DispatcherServlet初始化流程源码分析, 原来的web容器是在FrameworkServlet的空参init方法被触发建立的, 由
于咱们手动放入了web容器, 因此就不会手动建立, 你们能够回顾下以前的文章内容, 笔者着重的讲解了在容器建立后
或者从DisatcherServlet中取到了设置进去的容器后, 会调用一个configureAndRefreshWebApplicationContext
方法, 在这个方法中的最后有这么一行代码:
wac.refresh();
因而乎, 咱们知道了, 在FrameworkServlet中就会帮咱们主动调用refresh方法, 若是在此时咱们先调用的话, 那么
可能会有些问题, 由于在configureAndRefreshWebApplicationContext方法中, 调用refresh方法以前, 会对容器
进行必定的设置, 好比说环境、监听器(用于触发SpringMVC的九大策略的初始化)等, 因此咱们不能在这个时候去调用
复制代码
实现无xml完成的SpringMVC环境搭建的原理其实很简单, 就是利用了Servlet3.0规范提供的
ServletContainerInitializer接口来完成的, 由于这个接口会传入一个ServletContext, 因此咱们能够直接对
ServletContext进行操做, 放入DispatcherServlet, 同时利用注解配置的上下文完成Spring环境的引入, 由此我
们已经能够直接将各类的xml配置文件删除了, 若是是Spring或者SpringMVC的配置, 能够利用上面的SpringConfig
这个类来完成, 而若是是web.xml的配置, 能够利用MyWebInit类中onStartUp方法来完成
复制代码
在咱们以前的分析中, 已经完成了无xml配置SpringMVC的环境了, 这里作一个扩展, 咱们知道SpringBoot是内嵌
tomcat的容器的, 咱们也能够完成这样的壮举!!
引入tomcat的jar包:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.31</version>
</dependency>
<!-- 引入JSP支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.31</version>
</dependency>
建立启动类:
public class MyApplicationStarter {
public static void main(String[] args) throws Exception{
Tomcat tomcat = new Tomcat();
tomcat.setPort(80);
tomcat.addWebapp("/","D:\Users\Desktop\testProject\springmvc\src\main\webapp");
context.addLifecycleListener(
(LifecycleListener) Class.forName(tomcat.getHost().getConfigClass())
.newInstance());
tomcat.start();
tomcat.getServer().await();
}
}
再结合以前的代码, 咱们就实现了内嵌的tomcat应用, 从而实现了相似于SpringBoot的效果!!!
复制代码
最开始分析的Servlet3.0规范的时候, 咱们发现利用3.0规范, 咱们可以在tomcat容器启动的过程当中进行插手, 只需
要在resources目录下创建/META-INF/services/javax.servlet.ServletContainerInitializer的文件就行了,
tomcat容器启动的时候会反射加载这个文件中的全部类, 并引入了@HandlesTypes注解, 在该注解标注的接口, 其所
有的子类会被传入onStartup方法中, 然而, 上面是咱们本身实现的, 其实在SpringMVC中早就已经有了实现
SpringServletContainerInitializer是Spring对Servlet3.0规范的实现, 同时在spring-web这个包中, 就有一个
/META-INF/services/javax.servlet.ServletContainerInitializer文件, 在这个文件中就定义了这个
SpringServletContainerInitializer类, 这个类被@HandlesTypes注解标注了, 标注的接口是
WebApplicationInitializer接口, SpringServletContainerInitializer类的onStartUp方法完成的功能就是
从set中取出一个个WebApplicationInitializer接口的实现类, 建立对象, 并调用这些这些实现类的方法, 因此我
们只须要在程序中建立一个WebApplicationInitializer实现类就能够完成以前咱们的全部功能了....
复制代码