Spirng如何与Tomcat产生联系的

主要介绍了Spring Webxml配置原理以及父子容器关系详解 ,具有很好的参考价值 ,希望对大家有所帮助。  

起跟随小编过来看看吧

前言

在使用SpringSpringMVC的老版本进行开发时 ,我们需要配置很多的xml文件 ,非常的繁琐 ,总是让用户自行选择配置也是 非常不好的。  基于约定大于配置的规定 Spring提供了很多注解帮助我们简化了大量的xml配置 ;但是在使用SpringMVC  我们还会使用到WEB-INF/web.xml ,但实际上我们是完全可以使用Java类来取代xml配置的 ,这也是后来SpringBoott的实现   原理。  本篇就来看看Spring是如何实现完全的零XML配置。

正文

先来看一下原始的web.xml配置 

<!DOCTYPE web-app PUBLIC

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

<!--加载spring配置-->

classpath:spring.xml

</param-value>

</context-param>

<context-param>

<param-name>webAppRootKey</param-name>

<param-value>ServicePlatform.root</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

<!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>-->

</listener>

<servlet>

<servlet-name>spring-dispatcher</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<!--springmvc的配置文件-->

<param-name>contextConfigLocation</param-name>

<param-value>classpath:spring-dispatcher.xml</param-value>

</init-param>

<load-on-startup>0</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>spring-dispatcher</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>

</web-app>

这里各个配置的作用简单说下 context-param是加载我们主的sping.xml配置 ,比如一些bean的配置和开启注解扫描等 ;     listener是配置监听器 Tomcat启动会触发监听器调用 servlet则是配置我们自定义的Servlet实现 ,比如DispatcherServlet 还有其它很多配置就不一一说明了 ,在这里主要看到记住context-paramservlet配置 ,这是SpringIOC父子容器的体现。

在之前的I文章中讲过IOC容器是以父子关系组织的 ,但估计大部分人都不能理解 ,除了看到复杂的继承体系 ,并没有看到父容 器作用的体现 ,稍后来分析。

了解了配置 ,我们就需要思考如何替换掉这些繁琐的配置。  实际上Tomcat提供了一个规范 ,有一个ServletContainerInitializer 接口 

public interface ServletContainerInitializer {

void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;

}

Tomcat启动时会调用该接口实现类的onStartup方法 ,这个方法有两个参数 ,第二个不用说 ,主要是第一个参数什么?从哪里 来?另外我们自定义的实现类又怎么让Tomcat调用呢 

首先解答最后一个问题 ,这里也是利用SPI来实现的 ,因此我们实现了该接口后 ,还需要在META-INF.services下配置。         次 ,这里传入的第一个参数也是我们自定义的扩展接口的实现类 ,我们可以通过我们自定义的接口实现很多需要在启动时做的

 ,比如加载Servlet ,但是Tomcat又是怎么知道我们自定义的接口是哪个呢 

这就需要用到@HandlesTypes注解 ,该注解就是标注在ServletContainerInitializer的实现类上 ,其值就是我们扩展的接口 ,这 Tomcat就知道需要传入哪个接口实现类到这个onStartup方法了。

来看一个简单的实现 

@HandlesTypes(LoadServlet.class)

public class MyServletContainerInitializer implements ServletContainerInitializer {

@Override

public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {

Iterator var4;

if (set != null) {

var4 = set.iterator();

while (var4.hasNext()) {

Class<?> clazz = (Class<?>) var4.next();

if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && LoadServlet.class.isAssignableFrom(clazz)) {

try {

((LoadServlet) clazz.newInstance()).loadOnstarp(servletContext);

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

}

}

public interface LoadServlet {

void loadOnstarp(ServletContext servletContext);

}

public class LoadServletImpl implements LoadServlet {

@Override

public void loadOnstarp(ServletContext servletContext) {

ServletRegistration.Dynamic initServlet = servletContext.addServlet("initServlet", "org.springframework.web.servlet.DispatcherServlet"); initServlet.setLoadOnStartup(1);

initServlet.addMapping("/init");

}

}

这就是Tomcat给我们提供的规范 ,通过这个规范我们就能实现Spring的零xml配置启动 ,直接来看Spring是如何做的。  根据上 面所说我们可以在spring-web工程下找到META-INF/services/javax.servlet.ServletContainerInitializer配置 

@HandlesTypes(WebApplicationInitializer.class)

public class SpringServletContainerInitializer implements ServletContainerInitializer {

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

}

}

}

核心的实现就是WebApplicationInitializer ,先看看其继承体系

AbstractReactiveWebInitializer不用管 ,主要看另外一边 ,但是都是抽象类 ,也就是说真的实例也是由我们自己实现 ,但需要 我们实现什么呢 ?我们一般直接继承AbstractAnnotationConfigDispatcherServletInitializer ,有四个抽象方法需要我们实       

//父容器

@Override

protected Class<?>[] getRootConfigClasses() {

return new Class<?>[]{SpringContainer.class};

}

//SpringMVC配置子容器

@Override

protected Class<?>[] getServletConfigClasses() {

return new Class<?>[]{MvcContainer.class};

}

//获取DispatcherServlet的映射信息

@Override

protected String[] getServletMappings() {

return new String[]{"/"};

}

// filter配置

@Override

protected Filter[] getServletFilters() {

MyFilter myFilter = new MyFilter();

CorsFilter corsFilter = new CorsFilter();

return new Filter[]{myFilter,corsFilter};

}

这里主要注意getRootConfigClassesgetServletConfigClasses方法 ,分别加载父、 子容器 

@ComponentScan(value = "com.dark",excludeFilters = {

@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})

})

public class SpringContainer {

}

@ComponentScan(value = "com.dark",includeFilters = {

@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})

},useDefaultFilters = false)

public class MvcContainer {

}

看到这两个类上的注解应该不陌生了吧 ,父容器扫描装载了所有不带@Controller注解的类 ,子容器则相反 ,但需要对象时首 先从当前容器中找 ,如果没有则从父容器中获取 ,为什么要这么设计呢 

直接放到一个容器中不行么?先思考下  稍后解答。  回到onStartup方法中 ,直接回调用到

AbstractDispatcherServletInitializer 

public void onStartup(ServletContext servletContext) throws ServletException {

super.onStartup(servletContext);

//注册DispatcherServlet

registerDispatcherServlet(servletContext);

}

先是调用父类 

public void onStartup(ServletContext servletContext) throws ServletException {

registerContextLoaderListener(servletContext);

}

protected void registerContextLoaderListener(ServletContext servletContext) {

//创建spring上下文 ,注册了SpringContainer

WebApplicationContext rootAppContext = createRootApplicationContext();

if (rootAppContext != null) {

//创建监听器

ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);

listener.setContextInitializers(getRootApplicationContextInitializers());

servletContext.addListener(listener);

}

}

然后调用createRootApplicationContext创建父容器 

protected WebApplicationContext createRootApplicationContext() {

Class<?>[] configClasses = getRootConfigClasses();

if (!ObjectUtils.isEmpty(configClasses)) {

AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();

context.register(configClasses);

return context;

}

else {

return null;

}

}

可以看到就是创建了一个AnnotationConfigWebApplicationContext对象 ,并将我们的配置类SpringContainer注册了进去。   着创建Tomcat启动加载监听器ContextLoaderListener ,该监听器有一个contextInitialized方法 ,会在Tomcat启动时调用。

public void contextInitialized(ServletContextEvent event) {

initWebApplicationContext(event.getServletContext());

}

*/

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

long startTime = System.currentTimeMillis();

try {

// Store context in local instance variable, to guarantee that

// it is available on ServletContext shutdown.

if (this.context == null) {

this.context = createWebApplicationContext(servletContext);

}

if (this.context instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;

if (!cwac.isActive()) {

// The context has not yet been refreshed -> provide services such as

// setting the parent context, setting the application context id, etc

if (cwac.getParent() == null) {

// The context instance was injected without an explicit parent ->

// determine parent for root web application context, if any.

ApplicationContext parent = loadParentContext(servletContext);

cwac.setParent(parent);

}

configureAndRefreshWebApplicationContext(cwac, servletContext);

}

}

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();

if (ccl == ContextLoader.class.getClassLoader()) {

currentContext = this.context;

}

else if (ccl != null) {

currentContextPerThread.put(ccl, this.context);

}

return this.context;

}

}

可以看到就是去初始化容器 ,这个和之前分析xml解析是一样的 ,主要注意这里封装了ServletContext对象 ,并将父容器设置 到了该对象中。

父容器创建完成后自然就是子容器的创建 ,来到registerDispatcherServlet方法 

protected void registerDispatcherServlet(ServletContext servletContext) {

String servletName = getServletName();

Assert.hasLength(servletName, "getServletName() must not return null or empty");

//创建springmvc的上下文 ,注册了MvcContainer

WebApplicationContext servletAppContext = createServletApplicationContext();

Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

//创建DispatcherServlet

FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);

Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");

dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);

if (registration == null) {

throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +

"Check if there is another servlet registered under the same name.");

}

/*

* 如果该元素的值为负数或者没有设置 ,则容器会当Servlet被请求时再加载。

如果值为正整数或者0 ,表示容器在应用启动时就加载并初始化这个servlet 

值越小 servlet的优先级越高 ,就越先被加载

* */

registration.setLoadOnStartup(1);

registration.addMapping(getServletMappings());

registration.setAsyncSupported(isAsyncSupported());

Filter[] filters = getServletFilters();

if (!ObjectUtils.isEmpty(filters)) {

for (Filter filter : filters) {

registerServletFilter(servletContext, filter);

}

}

customizeRegistration(registration);

}

protected WebApplicationContext createServletApplicationContext() {

AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();

Class<?>[] configClasses = getServletConfigClasses();

if (!ObjectUtils.isEmpty(configClasses)) {

context.register(configClasses);

}

return context;

}

这里也是创建了一个AnnotationConfigWebApplicationContext对象 ,不同的只是这里注册的配置类就是我们的Servlet配置 了。  然后创建了DispatcherServlet对象 ,并将上下文对象设置了进去。

看到这你可能会疑惑 ,既然父子容器创建的都是相同类的对象 ,何来的父子容器之说 

别急 ,这个在初始化该上文时就明白了。  但是这里的初始化入口在哪呢 ?没有看到任何监听器的创建和调用。

实际上这里的上下文对象初始化是在Servlet初始化时实现的 ,即init方法 ,直接来到HttpServletBeaninit方法(分析 SpringMVC源码时讲过  

public final void init() throws ServletException {

...省略

// Let subclasses do whatever initialization they like.

initServletBean();

}

protected final void initServletBean() throws ServletException {

try {

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

}

}

protected WebApplicationContext initWebApplicationContext() {

//这里会从servletContext中获取到父容器 ,就是通过监听器加载的容器

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;

if (this.webApplicationContext != null) {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值