在《从Servlet到Spring MVC》中,介绍了基于xml配置使用的方式,但我们我现在用的更多的基于注解零配置的方式,尤其是在使用SpringBoot的时候,只需要引入web的start包即可,这边文章前面会简单介绍一下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时首先就要定义两个配置文件
定义根容器的配置类,不扫描@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{
/**
* 配置拦截器
* @return
*/
@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;
}
}
上面定义完了配置类,但是并没有配置这两个配置类该给哪个容器用,所以,接下来就是要去实现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整合Spring的时候,我们没有像执行main()方法一样去手动创建Spring的容器,那么Spring的容器又是在什么时候创建的,这就要说到Java强大的扩展机制 ——SPI(Service Provider Interface)),翻译过来就是服务提供商接口,那SPI是如何用的呢?
按照Java的SPI规范,我们只要在META-INF/services目录创建一个文件,文件名就是服务提供商提供的接口名,而文件内容就是实现这个接口的类的全限定名,然后Java在运行时候,可以通过ServiceLoader来加载这些类,这种方法提供了良好的扩展,下面代码示例SPI的使用:
定义一个接口:
public interface Search {
public List<String> searchDoc(String keyword);
}
定义两个接口的实现类:
public class FileSearch implements Search{
@Override
public List<String> searchDoc(String keyword) {
System.out.println("文件搜索 "+keyword);
return null;
}
}
public class DatabaseSearch implements Search{
@Override
public List<String> searchDoc(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) {
ServiceLoader<Search> s = ServiceLoader.load(Search.class);
Iterator<Search> iterator = s.iterator();
while (iterator.hasNext()) {
Search search = iterator.next();
search.searchDoc("hello world");
}
}
}
注:数据库连接的依赖包中,比如mysql-connector-java-5.1.44.jar,也有利用这种SPI机制来加载驱动
在Servlet3.1的规范中,明确指出Web容器需要支持对javax.servlet.ServletContainerInitailizer接口的扩展,Web容器(Tomcat)在启动的的时候,会根据META-INF/services目录中的文件内容,去加载所有ServletContainerInitailizer的实现类,然后调用它们的onStartup()方法
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> 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<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
……
}
}
SpringMVC在onStartup()方法的实现中,然后拿到了所有实现了WebApplicationInitializer接口的类和接口,但是,会把接口和抽象类过滤掉,SpringMVC自身提供了三个实现类,分别是:AbstractContextLoaderInitializer、AbstractDispatcherServletInitializer和AbstractAnnotationConfigDispatcherServletInitializer,不过这三个类都是抽象类,在启动的时候是没法使用的,这就是为什么我们在零配置使用SpringMVC的时候需要,需要添加一个类,来继承AbstractAnnotationConfigDispatcherServletInitializer抽象类
过滤完之后,就会去遍历所有过滤得到的WebApplicationInitializer类的是实现类,然后调用它们的onStartup()方法
List<WebApplicationInitializer> 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;
}
}
在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);
}
}
}