乐趣区

关于java:Spring-MVC-三-基于注解配置

Servlet3.0

Servlet3.0 是基于注解配置的实践根底。

Servlet3.0 引入了基于注解配置 Servlet 的标准,提出了可拔插的 ServletContext 初始化形式,引入了一个叫 ServletContainerInitializer 的接口。

An instance of the ServletContainerInitializer is looked up via the jar services API by the container at container / application startup time. The framework providing an implementation of the ServletContainerInitializer MUST bundle in the META-INF/services directory of the jar file a file called javax.servlet.ServletContainerInitializer, as per the jar services API, that points to the implementation class of the ServletContainerInitializer.

Servlet3.0 标准约定:WEB 容器(比方 tomcat)要通过 SPI 的形式查看利用 jar 包的 META-INF/services 目录下的 Servlet 容器的初始化类(ServletContainerInitializer 接口的实现类),通过调用该实现类的 onStartup 办法实现 Servlet 容器的初始化。

此外,Servlet3.0 还引入了 @HandlesTypes 注解,用来指定 Servlet 容器初始化过程中,WEB 容器会认为利用中的哪些类(由 @HandlesTypes 指定)会参加到 Servlet 容器的初始化过程中来。

SpringMVC 正是通过以上形式实现 Servlet 容器的初始化的!!!

SpringMVC Servlet 容器初始化过程

依照上述实践的指引,探索注解形式配置 Spring MVC 的原理就没那么艰难了。

先看下图:

很容易的,咱们发现 SpringMVC 指定的 Servlet 容器初始化的实现类为 org.springframework.web.SpringServletContainerInitializer。

所以咱们找到他看看:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {}

的确是 ServletContainerInitializer 接口的实现类,@HandlesTypes 指定的是 WebApplicationInitializer,这个 WebApplicationInitializer 到底是个啥东东,咱们先放放,咱们先来钻研一下 SpringServletContainerInitializer 类的 onStartup 办法。

@Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                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) {initializer.onStartup(servletContext);
        }
    }

}

代码逻辑也不算简单,查看传入的参数 webAppInitializerClasses,如果是实现类的话(不是接口或抽象类)则通过反射机制实例化 webAppInitializerClasses 并强转为 WebApplicationInitializer,而后调用 WebApplicationInitializer 的 onStartup 办法。

这里有一个小问题:参数 webAppInitializerClasses 实例化之后,为什么能强转为 WebApplicationInitializer?

其实这也是 Servlet3.0 标准约定的,WEB 容器会依据 @HandlesTypes 的设置,从以后类加载器中查找符合条件的类,以后 @HandlesTypes 指定的正是 WebApplicationInitializer。

之后的操作就都交给 WebApplicationInitializer 了。

WebApplicationInitializer

WebApplicationInitializer 是承当起 Servlet3.0 标准约定的初始化 Servlet 容器的那个人:

Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically — as opposed to (or possibly in conjunction with) the traditional web.xml-based approach.
Implementations of this SPI will be detected automatically by SpringServletContainerInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. See its Javadoc for details on this bootstrapping mechanism.

咱们先看一下他的类构造:

Spring 框架实现了 WebApplicationInitializer 接口的 3 个抽象类,最初一个抽象类 AbstractAnnotationConfigDispatcherServletInitializer 没有 onStartup 办法,onStartup 办法是他的父类实现的。

咱们看一下他的父类 AbstractDispatcherServletInitializer 的 onStartup 办法:

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {super.onStartup(servletContext);
        registerDispatcherServlet(servletContext);
    }

能够发现他其实是一个模板办法,首先调用了父类的 onStartup,之后调用 registerDispatcherServlet 办法。父类的 onStartup 办法是实现 rootApplicationContext 调用的,至于什么是 rootApplicationContext 咱们临时不论,咱们先看一下 registerDispatcherServlet 办法:

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");

        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

   ... 省略代码 

首先调用 createServletApplicationContext 创立 ServletApplicationContext(Servlet 容器), 之后创立 DispathcerServlet 并且把创立好的 Servlet 容器传递给 DispatcherServlet(DispatcherServlet 要记录他所在的 ServletApplicationContext)。

要害代码呈现了,Servlet 容器的创立过程应该就在这个 createServletApplicationContext 办法中,是在 AbstractAnnotationConfigDispatcherServletInitializer 中实现的:

    @Override
    protected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {context.register(configClasses);
        }
        return context;
    }

创立了一个新的 AnnotationConfigWebApplicationContext 对象,而后调用 getServletConfigClasses() 办法获取配置类,之后把配置类注册到了新创建的 AnnotationConfigWebApplicationContext 对象中。 这个 getServletConfigClasses() 办法是没有实现的(应该是咱们的实现类须要实现的)。

至此,Spring 通过 Servlet3.0 标准进行初始化的过程应该曾经很清晰了:
1.Spring Framework 通过 WebApplicationInitializer 接口的 onStartup 办法实现 Servlet 上下文的初始化。
2.Spring Framework 曾经实现了 WebApplicationInitializer 接口的大部分实现(提供了 3 个抽象类),曾经通过模板办法实现了大部分的初始化操作。
3. 猜想:应用层只须要扩大 AbstractAnnotationConfigDispatcherServletInitializer 类实现 getServletConfigClasses() 办法、返回 Servlet 的配置类,即可实现初始化。

接下来咱们就验证一下上述猜想。

创立基于注解的 Spring MVC 利用

依照上述猜想,咱们只须要扩大 AbstractAnnotationConfigDispatcherServletInitializer、实现 getServletConfigClasses() 办法即可。

咱们还是用上篇文章中用过的例子来验证,在入手之前,因为注解形式和 web.xml 配置形式是抵触的(配置形式会笼罩掉注解形式),所以咱们须要删掉 web.xml 文件(copy 进来即可)。

创立一个 configuration 包,并创立配置类(只有配置 Controller 的扫描门路即可):

package org.example.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("org.example.controller")
public class MvcConfiguration {}

而后再创立 initializer 的实现类:

package org.example.configuration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {return new Class[] {MvcConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {return new String[] {"/"};
    }
}

其中 RootConfigClasse() 办法是为 RootApplicationContext 服务的,咱们后面说过了,展现不论这个 RootApplicationContext 是什么,当初仍然也不论他。

getServletConfigClasses 办法,返回咱们创立的初始化类。

还有一个 getServletMappings 办法,下面没有提到过,其实是起到 web.xml 中的 servlet-mapping 配置的作用的,所以咱们间接返回 ”/” – 默认匹配规定。

启动我的项目,拜访:

功败垂成!

上一篇 Spring MVC 二:基于 xml 配置

退出移动版