欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解spring注解配置启动过程

程序员文章站 2024-03-12 11:00:20
       最近看起spring源码,突然想知道没有web.xml的配置,spring是怎么通过一个继承于ab...

       最近看起spring源码,突然想知道没有web.xml的配置,spring是怎么通过一个继承于abstractannotationconfigdispatcherservletinitializer的类来启动自己的。鉴于能力有限以及第一次看源码和发博客,不到之处请望谅~

  我用的ide是intellij idea,这个比myeclipse看源码方便一点,而且黑色背景挺喜欢。然后项目是在maven下的tomcat7插件运行。spring版本是4.3.2.release。 

  如果写过纯注解配置的spring web,应该知道需要继承一个初始化类来装载bean,然后从这个类开始就会加载我们自定义的功能和bean了,下面是我的一个webinitializer

@order(1)
public class webmvcinit extends abstractannotationconfigdispatcherservletinitializer {
 protected class<?>[] getrootconfigclasses() {
  return new class[]{rootconfig.class,websecurityconfig.class};
 }

 protected class<?>[] getservletconfigclasses() {
  return new class[]{webconfig.class};
 }

 protected string[] getservletmappings() {
  return new string[]{"/"};
 }

 @override
 protected filter[] getservletfilters() {
  return new filter[]{new hiddenhttpmethodfilter()};
 }

}

  首先看下abstractannotationconfigdispatcherservletinitializer类的结构,这个也是idea的一个uml功能,在类那里右键diagrams->show diagrams就有啦

详解spring注解配置启动过程

  然后我们直接点进abstractannotationconfigdispatcherservletinitializer,可以看到这个类很简单,只有四个方法,然后我们关注下createrootapplicationcontext()

@override
 protected webapplicationcontext createrootapplicationcontext() {
  class<?>[] configclasses = getrootconfigclasses();
  if (!objectutils.isempty(configclasses)) {
   annotationconfigwebapplicationcontext rootappcontext = new annotationconfigwebapplicationcontext();
   rootappcontext.register(configclasses);
   return rootappcontext;
  }
  else {
   return null;
  }
 } 

  这个方法大概意思是获取用户(程序员)传过来的rootclasses,然后注册里面的bean,这些都不是我们关注的,不过这个方法应该是要在启动后执行的,所以我们可以从这个方法往上找

  idea下ctrl+g可以找调用某个方法或类,然后设置寻找范围为project and library

  我们找到,abstractcontextloaderinitializer下registercontextloaderlistener(servletcontext servletcontext)方法调用子类的createrootapplicationcontext()获取webapplicationcontext,继续找registercontextloaderlistener(servletcontext servletcontext)方法的调用者,结果发现就是该类下的onstartup(servletcontext servletcontext),下面贴下abstractcontextloaderinitializer类

public abstract class abstractcontextloaderinitializer implements webapplicationinitializer {

 /** logger available to subclasses */
 protected final log logger = logfactory.getlog(getclass());


 @override
 public void onstartup(servletcontext servletcontext) throws servletexception {
  registercontextloaderlistener(servletcontext);
 }

 /**
  * register a {@link contextloaderlistener} against the given servlet context. the
  * {@code contextloaderlistener} is initialized with the application context returned
  * from the {@link #createrootapplicationcontext()} template method.
  * @param servletcontext the servlet context to register the listener against
  */
 protected void registercontextloaderlistener(servletcontext servletcontext) {
  webapplicationcontext rootappcontext = createrootapplicationcontext();
  if (rootappcontext != null) {
   contextloaderlistener listener = new contextloaderlistener(rootappcontext);
   listener.setcontextinitializers(getrootapplicationcontextinitializers());
   servletcontext.addlistener(listener);
  }
  else {
   logger.debug("no contextloaderlistener registered, as " +
     "createrootapplicationcontext() did not return an application context");
  }
 }

 /**
  * create the "<strong>root</strong>" application context to be provided to the
  * {@code contextloaderlistener}.
  * <p>the returned context is delegated to
  * {@link contextloaderlistener#contextloaderlistener(webapplicationcontext)} and will
  * be established as the parent context for any {@code dispatcherservlet} application
  * contexts. as such, it typically contains middle-tier services, data sources, etc.
  * @return the root application context, or {@code null} if a root context is not
  * desired
  * @see org.springframework.web.servlet.support.abstractdispatcherservletinitializer
  */
 protected abstract webapplicationcontext createrootapplicationcontext();

 /**
  * specify application context initializers to be applied to the root application
  * context that the {@code contextloaderlistener} is being created with.
  * @since 4.2
  * @see #createrootapplicationcontext()
  * @see contextloaderlistener#setcontextinitializers
  */
 protected applicationcontextinitializer<?>[] getrootapplicationcontextinitializers() {
  return null;
 }

}

  注意的是这里我们跳过了abstractdispatcherservletinitializer抽象类(看uml图),这个类主要配置dispatcherservlet,这里就是spring mvc等功能的实现了。 

  那谁来加载abstractcontextloaderinitializer?webapplicationinitializer已经是接口,不会再有一个抽象类来调用了,于是我尝试性地搜webapplicationinitializer接口,因为spring这种大项目肯定是面向接口的,所以调用的地方一般是写接口,然后我们找到了springservletcontainerinitializer类,它实现了servletcontainerinitializer接口,这个类大概是说把所有webapplicationinitializer都startup一遍,可以说这个类很接近我们的目标了。下面贴下springservletcontainerinitializer

@handlestypes(webapplicationinitializer.class)
public class springservletcontainerinitializer implements servletcontainerinitializer {
 @override
 public void onstartup(set<class<?>> webappinitializerclasses, servletcontext servletcontext)
   throws servletexception {

  list<webapplicationinitializer> initializers = new linkedlist<webapplicationinitializer>();

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

}
  

         在最后的foreach把所有的webapplicationinitializer都启动一遍。那么问题来了,谁来启动springservletcontainerinitializer,spring肯定不能自己就能启动的,在

         web环境下,就只有web容器了。我们可以在上面某一个地方打个断点,然后debug一下(事实上,完全可以全程debug = =,这样准确又快捷,不过这样少了点寻找的意味,沿路风景还是挺不错的) 

详解spring注解配置启动过程

  可以看到包org.apache.catalina.core下的standardcontext类的startinternal方法,这个已经是tomcat的范围了,所以我们的目标算是达到了。注意的是servletcontainerinitializer接口并不是spring包下的,而是javax.servlet

  我猜测,tomcat通过javax.servlet的servletcontainerinitializer接口来找容器下实现这个接口的类,然后调用它们的onstartup,然后spring的springservletcontainerinitializer就可以把所有webapplicationinitializer都启动一遍,其中就有我们自己写的webinitializer,另外spring security用注解配置也是实现webapplicationinitializer启动的,所以这样spring的扩展性很强。这几天再看下tomcat源码,了解下tomcat的机制。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。