http://www.bubuko.com/infodetail-1783943.html
最近看起spring源码,突然想知道没有web.xml的配置,spring是怎么通过一个继承于AbstractAnnotationConfigDispatcherServletInitializer的类来启动自己的。鉴于能力有限以及第一次看源码和发博客,不到之处请望谅~
我用的IDE是IntelliJ IDEA,这个比myEclipse看源码方便一点,而且黑色背景挺喜欢。然后项目是在maven下的tomcat7插件运行。spring版本是4.3.2.RELEASE。
如果写过纯注解配置的spring web,应该知道需要继承一个初始化类来装载bean,然后从这个类开始就会加载我们自定义的功能和bean了,下面是我的一个WebInitializer
1 @Order(1)
2 public class WebMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
3 protected Class>[] getRootConfigClasses() {
4 return new Class[]{RootConfig.class,WebSecurityConfig.class};
5 }
6
7 protected Class>[] getServletConfigClasses() {
8 return new Class[]{WebConfig.class};
9 }
10
11 protected String[] getServletMappings() {
12 return new String[]{"/"};
13 }
14
15 @Override
16 protected Filter[] getServletFilters() {
17 return new Filter[]{new HiddenHttpMethodFilter()};
18 }
19
20 }
首先看下AbstractAnnotationConfigDispatcherServletInitializer类的结构,这个也是IDEA的一个uml功能,在类那里右键Diagrams->show Diagrams就有啦
然后我们直接点进AbstractAnnotationConfigDispatcherServletInitializer,可以看到这个类很简单,只有四个方法,然后我们关注下createRootApplicationContext()
1 @Override
2 protected WebApplicationContext createRootApplicationContext() {
3 Class>[] configClasses = getRootConfigClasses();
4 if (!ObjectUtils.isEmpty(configClasses)) {
5 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
6 rootAppContext.register(configClasses);
7 return rootAppContext;
8 }
9 else {
10 return null;
11 }
12 }
这个方法大概意思是获取用户(程序员)传过来的RootClasses,然后注册里面的bean,这些都不是我们关注的,不过这个方法应该是要在启动后执行的,所以我们可以从这个方法往上找
IDEA下Ctrl+G可以找调用某个方法或类,然后设置寻找范围为project and library
我们找到,AbstractContextLoaderInitializer下registerContextLoaderListener(ServletContext servletContext)方法调用子类的createRootApplicationContext()获取WebApplicationContext,继续找registerContextLoaderListener(ServletContext servletContext)方法的调用者,结果发现就是该类下的onStartup(ServletContext servletContext),下面贴下AbstractContextLoaderInitializer类
1 public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
2
3 /** Logger available to subclasses */
4 protected final Log logger = LogFactory.getLog(getClass());
5
6
7 @Override
8 public void onStartup(ServletContext servletContext) throws ServletException {
9 registerContextLoaderListener(servletContext);
10 }
11
12 /**
13 * Register a {@link ContextLoaderListener} against the given servlet context. The
14 * {@code ContextLoaderListener} is initialized with the application context returned
15 * from the {@link #createRootApplicationContext()} template method.
16 * @param servletContext the servlet context to register the listener against
17 */
18 protected void registerContextLoaderListener(ServletContext servletContext) {
19 WebApplicationContext rootAppContext = createRootApplicationContext();
20 if (rootAppContext != null) {
21 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
22 listener.setContextInitializers(getRootApplicationContextInitializers());
23 servletContext.addListener(listener);
24 }
25 else {
26 logger.debug("No ContextLoaderListener registered, as " +
27 "createRootApplicationContext() did not return an application context");
28 }
29 }
30
31 /**
32 * Create the "root" application context to be provided to the
33 * {@code ContextLoaderListener}.
34 * The returned context is delegated to
35 * {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will
36 * be established as the parent context for any {@code DispatcherServlet} application
37 * contexts. As such, it typically contains middle-tier services, data sources, etc.
38 * @return the root application context, or {@code null} if a root context is not
39 * desired
40 * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
41 */
42 protected abstract WebApplicationContext createRootApplicationContext();
43
44 /**
45 * Specify application context initializers to be applied to the root application
46 * context that the {@code ContextLoaderListener} is being created with.
47 * @since 4.2
48 * @see #createRootApplicationContext()
49 * @see ContextLoaderListener#setContextInitializers
50 */
51 protected ApplicationContextInitializer>[] getRootApplicationContextInitializers() {
52 return null;
53 }
54
55 }
注意的是这里我们跳过了AbstractDispatcherServletInitializer抽象类(看uml图),这个类主要配置DispatcherServlet,这里就是spring mvc等功能的实现了。
那谁来加载AbstractContextLoaderInitializer?WebApplicationInitializer已经是接口,不会再有一个抽象类来调用了,于是我尝试性地搜WebApplicationInitializer接口,因为spring这种大项目肯定是面向接口的,所以调用的地方一般是写接口,然后我们找到了SpringServletContainerInitializer类,它实现了ServletContainerInitializer接口,这个类大概是说把所有WebApplicationInitializer都startUp一遍,可以说这个类很接近我们的目标了。下面贴下SpringServletContainerInitializer
1 @HandlesTypes(WebApplicationInitializer.class)
2 public class SpringServletContainerInitializer implements ServletContainerInitializer {
3 @Override
4 public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext)
5 throws ServletException {
6
7 List initializers = new LinkedList();
8
9 if (webAppInitializerClasses != null) {
10 for (Class> waiClass : webAppInitializerClasses) {
11 // Be defensive: Some servlet containers provide us with invalid classes,
12 // no matter what @HandlesTypes says...
13 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
14 WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
15 try {
16 initializers.add((WebApplicationInitializer) waiClass.newInstance());
17 }
18 catch (Throwable ex) {
19 throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
20 }
21 }
22 }
23 }
24
25 if (initializers.isEmpty()) {
26 servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
27 return;
28 }
29
30 servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
31 AnnotationAwareOrderComparator.sort(initializers);
32 for (WebApplicationInitializer initializer : initializers) {
33 initializer.onStartup(servletContext);
34 }
35 }
36
37 }
在最后的foreach把所有的WebApplicationInitializer都启动一遍。那么问题来了,谁来启动SpringServletContainerInitializer,spring肯定不能自己就能启动的,在
web环境下,就只有web容器了。我们可以在上面某一个地方打个断点,然后Debug一下(事实上,完全可以全程Debug = =,这样准确又快捷,不过这样少了点寻找的意味,沿路风景还是挺不错的)
可以看到包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的机制。