IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理

现在spring开发的项目,越来越多的用到注解开发了,所以这里就记录一下,存注解开发搭建sping的web开发。

(1)创建一个maven工程,
直接点next,这里不添加原型插件,(到创建后面在添加web环境)
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第1张图片
设置groupId 和artifactid 信息。然后next.
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第2张图片
点击finish创建项目工程
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第3张图片
然后进入这个页面,配置文件结构的属性,点击加号。来添加web环境
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第4张图片
点击web,添加web环境
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第5张图片
选中这个之后出现这个页面(设置web资源的目录,也就是我们属性的webapp所在的位置,它默认的位置并不正确,所有我们要修改一下)IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第6张图片
修改为 src\main\webapp
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第7张图片
下面这个是设置web.xml的因为我们现在搭建的注解版,所以就不需要,(点击-号把默认的删除)
然后点击ok.
这里就有了一个web环境的标识了,说明可以了
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第8张图片
然后查看文件目录结构:
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第9张图片
到这里项目的结构就搭建好了,现在配置pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    
	<packaging>warpackaging>
    <groupId>com.kuakegroupId>
    <artifactId>springmvc-webartifactId>
    <version>1.0-SNAPSHOTversion>
	   
    <dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>4.3.23.RELEASEversion>
        dependency>
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.0.1version>
            <scope>providedscope>
        dependency>
        
        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>2.9.8version>
        dependency>

    dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-war-pluginartifactId>
                <version>2.4version>
                <configuration>
                	
                    <failOnMissingWebXml>falsefailOnMissingWebXml>
                configuration>
            plugin>
            
            <plugin>
                <groupId>org.apache.tomcat.mavengroupId>
                <artifactId>tomcat7-maven-pluginartifactId>
                <version>2.2version>
                <configuration>
                    <port>8080port>
                    <path>/path>
                    <uriEncoding>UTF-8uriEncoding>
                    <server>tomcat7server>
                configuration>
            plugin>
                        
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.2version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <encoding>UTF-8encoding>
                configuration>
            plugin>
        plugins>
    build>
project>

(1)创建一个RootConfig根容器

package com.kuake.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

/**
 * @author hao
 * @create 2019-06-14 ${TIM}
 */

/**
 * 这个是跟容器,相当于ApplicationContxt.xml,包扫描的时候要排除@Controller注解,避免重复扫描
 */
@ComponentScan(value ={"com.kuake"},
        excludeFilters={@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
@Configuration//声明这是一个配置类
public class RootConfig {
}

(2)创建一个AppConfig

package com.kuake.config;

/**
 * @author hao
 * @create 2019-06-14 ${TIM}
 */

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * Springweb应用的配置 相当于配置文件SpringMVC.xml
 *
 */
@Configuration
@EnableWebMvc//开启全面接管spingmvc
//配置只会扫描Controller注解
@ComponentScan(value = {"com.kuake"},includeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value={Controller.class})},useDefaultFilters = false)
public class AppConfig extends WebMvcConfigurerAdapter {

    //配置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认前缀 /WEB-INF/   后缀.jsp
        registry.jsp();
    }

    //可以自定义添加各种组件 通过重写方法
}

(3)创建一个容器的初始化器MyWebAppInitializer

package com.kuake.config;

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

/**
 * @author hao
 * @create 2019-06-14 ${TIM}
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//加载容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }
//加载webapp容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { AppConfig.class };
    }
//设置DispatcherServlet的拦截规则  / 代表拦截所有但是不包括jsp
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

(4)创建一个helloController

	package com.kuake.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author hao
 * @create 2019-06-14 ${TIM}
 */
@Controller
public class UserController {
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

在WEB-INF下创建一个hello.jsp

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/6/14
  Time: 18:48
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
hello world!
body>
html>

(5)启动web应用,使用maven的tomcat插件
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第10张图片
控制台打印日志:

[INFO] --- tomcat7-maven-plugin:2.2:run (default-cli) @ springmvc ---
[INFO] Running war on http://localhost:8080/
[INFO] Using existing Tomcat server configuration at F:\ideaworkplace\spring-test\springmvc\target\tomcat
[INFO] create webapp with contextPath: 
六月 14, 2019 9:09:40 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-bio-8080"]
六月 14, 2019 9:09:40 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service Tomcat
六月 14, 2019 9:09:40 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/7.0.47
六月 14, 2019 9:09:44 下午 org.apache.catalina.core.ApplicationContext log
信息: 1 Spring WebApplicationInitializers detected on classpath
六月 14, 2019 9:09:44 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
六月 14, 2019 9:09:44 下午 org.springframework.web.context.ContextLoader initWebApplicationContext
信息: Root WebApplicationContext: initialization started
六月 14, 2019 9:09:44 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext prepareRefresh
信息: Refreshing Root WebApplicationContext: startup date [Fri Jun 14 21:09:44 CST 2019]; root of context hierarchy
六月 14, 2019 9:09:44 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext loadBeanDefinitions
信息: Registering annotated classes: [class com.kuake.config.RootConfig]
六月 14, 2019 9:09:45 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init>
信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
六月 14, 2019 9:09:45 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register
信息: Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.kuake.controller.UserController.hello()
六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: Root WebApplicationContext: startup date [Fri Jun 14 21:09:44 CST 2019]; root of context hierarchy
六月 14, 2019 9:09:49 下午 org.springframework.web.context.ContextLoader initWebApplicationContext
信息: Root WebApplicationContext: initialization completed in 5098 ms
六月 14, 2019 9:09:49 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring FrameworkServlet 'dispatcher'
六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.DispatcherServlet initServletBean
信息: FrameworkServlet 'dispatcher': initialization started
六月 14, 2019 9:09:49 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext prepareRefresh
信息: Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Fri Jun 14 21:09:49 CST 2019]; parent: Root WebApplicationContext
六月 14, 2019 9:09:49 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext loadBeanDefinitions
信息: Registering annotated classes: [class com.kuake.config.AppConfig]
六月 14, 2019 9:09:49 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init>
信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register
信息: Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.kuake.controller.UserController.hello()
六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Fri Jun 14 21:09:49 CST 2019]; parent: Root WebApplicationContext
六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.DispatcherServlet initServletBean
信息: FrameworkServlet 'dispatcher': initialization completed in 359 ms
六月 14, 2019 9:09:49 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-bio-8080"]

进入浏览器:
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第11张图片
到这里说明我们的环境搭建成功


相比传统搭建传统的web环境,要在web.xml当中配置前端控制器组件DispatcherServlet,需要在springmvc.xml当中配置视图解析器,…等等,那么在这种纯注解的情况下,他是如何创建出ioc容器的呢,如何添加DispatcherServlet这个组件的呢。现在来一探究竟。
我们MyWebAppInitializer这个类继承了一个AbstractAnnotationConfigDispatcherServletInitializer就从这个类入手。
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第12张图片
1、AbstractAnnotationConfigDispatcherServletInitializer

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.support;

import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;



public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {

	@Override
	protected WebApplicationContext createRootApplicationContext() {
		//获得rootConfig.class
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			//创建一个跟容器
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			rootAppContext.register(configClasses);
			return rootAppContext;
		}
		else {
			return null;
		}
	}
	
	@Override
	protected WebApplicationContext createServletApplicationContext() {
		//创建一个web的ioc容器
		AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			servletAppContext.register(configClasses);
		}
		return servletAppContext;
	}

	
	protected abstract Class<?>[] getRootConfigClasses();

	protected abstract Class<?>[] getServletConfigClasses();

}

这个类 有两个主要的方法createRootApplicationContext()和createServletApplicationContext(),分别用来创建根容器子容器。因为定义了两个抽象方法getRootConfigClasses()和getServletConfigClasses(),在创建的过程中也都调用了这俩个方法,如果我们的子类重写了这两个抽象方法,那么父类在创建的时候,就会回调子类的方法 (这其实符合一个设计模式,模板方法)

(2)接着看他的父类:AbstractDispatcherServletInitializer

/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.web.servlet.support;

import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;

/**
* Base class for {@link org.springframework.web.WebApplicationInitializer}
* implementations that register a {@link DispatcherServlet} in the servlet context.
*
* 

Concrete implementations are required to implement * {@link #createServletApplicationContext()}, as well as {@link #getServletMappings()}, * both of which get invoked from {@link #registerDispatcherServlet(ServletContext)}. * Further customization can be achieved by overriding * {@link #customizeRegistration(ServletRegistration.Dynamic)}. * *

Because this class extends from {@link AbstractContextLoaderInitializer}, concrete * implementations are also required to implement {@link #createRootApplicationContext()} * to set up a parent "root" application context. If a root context is * not desired, implementations can simply return {@code null} in the * {@code createRootApplicationContext()} implementation. * * @author Arjen Poutsma * @author Chris Beams * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Stephane Nicoll * @since 3.2 */ public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { /** * The default servlet name. Can be customized by overriding {@link #getServletName}. */ public static final String DEFAULT_SERVLET_NAME = "dispatcher"; @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); } /** * Register a {@link DispatcherServlet} against the given servlet context. *

This method will create a {@code DispatcherServlet} with the name returned by * {@link #getServletName()}, initializing it with the application context returned * from {@link #createServletApplicationContext()}, and mapping it to the patterns * returned from {@link #getServletMappings()}. *

Further customization can be achieved by overriding {@link * #customizeRegistration(ServletRegistration.Dynamic)} or * {@link #createDispatcherServlet(WebApplicationContext)}. * @param servletContext the context to register the servlet against */ protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return empty or null"); WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() did not return an application " + "context for servlet [" + servletName + "]"); FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'." + "Check if there is another servlet registered under the same name."); 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); } /** * Return the name under which the {@link DispatcherServlet} will be registered. * Defaults to {@link #DEFAULT_SERVLET_NAME}. * @see #registerDispatcherServlet(ServletContext) */ protected String getServletName() { return DEFAULT_SERVLET_NAME; } /** * Create a servlet application context to be provided to the {@code DispatcherServlet}. *

The returned context is delegated to Spring's * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such, * it typically contains controllers, view resolvers, locale resolvers, and other * web-related beans. * @see #registerDispatcherServlet(ServletContext) */ protected abstract WebApplicationContext createServletApplicationContext(); /** * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived * dispatcher) with the specified {@link WebApplicationContext}. *

Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3. * Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof. */ protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { return new DispatcherServlet(servletAppContext); } /** * Specify application context initializers to be applied to the servlet-specific * application context that the {@code DispatcherServlet} is being created with. * @since 4.2 * @see #createServletApplicationContext() * @see DispatcherServlet#setContextInitializers * @see #getRootApplicationContextInitializers() */ protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() { return null; } /** * Specify the servlet mapping(s) for the {@code DispatcherServlet} — * for example {@code "/"}, {@code "/app"}, etc. * @see #registerDispatcherServlet(ServletContext) */ protected abstract String[] getServletMappings(); /** * Specify filters to add and map to the {@code DispatcherServlet}. * @return an array of filters or {@code null} * @see #registerServletFilter(ServletContext, Filter) */ protected Filter[] getServletFilters() { return null; } /** * Add the given filter to the ServletContext and map it to the * {@code DispatcherServlet} as follows: *

    *
  • a default filter name is chosen based on its concrete type *
  • the {@code asyncSupported} flag is set depending on the * return value of {@link #isAsyncSupported() asyncSupported} *
  • a filter mapping is created with dispatcher types {@code REQUEST}, * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending * on the return value of {@link #isAsyncSupported() asyncSupported} *
*

If the above defaults are not suitable or insufficient, override this * method and register filters directly with the {@code ServletContext}. * @param servletContext the servlet context to register filters with * @param filter the filter to be registered * @return the filter registration */ protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) { String filterName = Conventions.getVariableName(filter); Dynamic registration = servletContext.addFilter(filterName, filter); if (registration == null) { int counter = -1; while (counter == -1 || registration == null) { counter++; registration = servletContext.addFilter(filterName + "#" + counter, filter); Assert.isTrue(counter < 100, "Failed to register filter '" + filter + "'." + "Could the same Filter instance have been registered already?"); } } registration.setAsyncSupported(isAsyncSupported()); registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName()); return registration; } private EnumSet<DispatcherType> getDispatcherTypes() { return (isAsyncSupported() ? EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) : EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE)); } /** * A single place to control the {@code asyncSupported} flag for the * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}. *

The default value is "true". */ protected boolean isAsyncSupported() { return true; } /** * Optionally perform further registration customization once * {@link #registerDispatcherServlet(ServletContext)} has completed. * @param registration the {@code DispatcherServlet} registration to be customized * @see #registerDispatcherServlet(ServletContext) */ protected void customizeRegistration(ServletRegistration.Dynamic registration) { } }

主要方法是registerDispatcherServlet(ServletContext servletContext)他的作用是:
1、创建一个web的ioc容器【createServletApplicationContext()】
2、创建了DispatcherServlet【createDispatcherServlet()】
3、将创建的DispatcherServlet添加到ServletContext中;
(3)接着再上一个父类:AbstractContextLoaderInitializer

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.context;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.web.WebApplicationInitializer;

/**
 * Convenient base class for {@link WebApplicationInitializer} implementations
 * that register a {@link ContextLoaderListener} in the servlet context.
 *
 * 

The only method required to be implemented by subclasses is * {@link #createRootApplicationContext()}, which gets invoked from * {@link #registerContextLoaderListener(ServletContext)}. * * @author Arjen Poutsma * @author Chris Beams * @author Juergen Hoeller * @since 3.2 */ 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 "root" application context to be provided to the * {@code ContextLoaderListener}. *

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

主要的方法createRootApplicationContext()创建一个根容器。
(4)最顶层接口WebApplicationInitializer

public interface WebApplicationInitializer {

	/**
	 * Configure the given {@link ServletContext} with any servlets, filters, listeners
	 * context-params and attributes necessary for initializing this web application. See
	 * examples {@linkplain WebApplicationInitializer above}.
	 * @param servletContext the {@code ServletContext} to initialize
	 * @throws ServletException if any call against the given {@code ServletContext}
	 * throws a {@code ServletException}
	 */
	void onStartup(ServletContext servletContext) throws ServletException;

}

那么容器启动时候,为什么这么MyWebAppInitializer这个类会被加载呢,进而创建根容器,创建web的ioc容器呢
来看这个类的介绍:

WebApplicationInitializer是Spring MVC提供的一个接口,它确保检测到您的实现并自动用于初始化Servlet 3容器。WebApplicationInitializer的抽象基类实现AbstractDispatcherServletInitializer通过重写方法来指定servlet映射和DispatcherServlet配置的位置,使得注册DispatcherServlet更加容易。

这里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一个初始化规范,根据官方文档的解释,用自己的话总结一下有关的重要几点,大概意思就是:

web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,加载这个文件制定的类,并且可以通过@HandlesTypes注解,把感兴趣的类的信息,注入到onStartup(Set> var1, ServletContext var2)var1当中。

经过查看源码,以及debug调试发现,这个ServletContainerInitializer其实就是SpringServletContainerInitializer,来看SpringServletContainerInitializer所在在包下的目录结构。
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第13张图片
根据上面介绍的servlet3.0规范,当servlet容器启动的时候,就会加载javax.servlet.ServletContainerInitializer文件中指定的类
其内容就是org.springframework.web.SpringServletContainerInitializer,所有也就会在容器启动的时候,加载SpringServletContainerInitializer
SpringServletContainerInitializer代码如下:

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@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);
		//遍历容器中所有的WebApplicationInitializer#onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

这段代码的简单的介绍一下:因为这个类上标注了@HandlesTypes(WebApplicationInitializer.class),所以会加载所有WebApplicationInitializer信息都会被注入到onStartup()方法的形参webAppInitializerClasses上,然后遍历,判断如果不是接口【!waiClass.isInterface()】,不是抽象类【!Modifier.isAbstract(waiClass.getModifiers()】,那么就实例化,并且添加在集合当中。最后遍历集合initializers,代用每一个对象的#onStartup(servletContext)方法。在这个遍历上,打上一个断点,看一下initializers中有哪些对象。

		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}

查看结果如下:(加载的感兴趣的类信息一共有4个,因为其他三个都是抽象类,不符合实例化的条件,所有集合中也就一个类,就是我们定义的MyWebAppInitializer
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第14张图片
经过上面的分析,可以大概小结一下:
因为servlet3.0容器加载规范,会加载特定位置的文件中指定的类,在这里也就是SpringServletContainerInitializer,,然后加载@HandleType注解标注的感兴趣的类,然后根据条件实例化这些类,添加到集合中,遍历集合然后调用他们的onStartup方法


有了这些基础,那么就可以来看一下,他的执行调用过程,来看一下容器如何被创建的,核心控制器DispatcherServlet是如何被添加到容器中…
执行initializer.onStartup(servletContext),所以来到MyWebAppInitializer#onStartup的方法,因为他本身没有重写这个方法,所以往上找他的父类AbstractAnnotationConfigDispatcherServletInitializer,但是这里也没有那么就接着再找父类AbstractDispatcherServletInitializer,在这个类中onStartup方法代码如下:

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

他分为两步,先执行super.onStartup(servletContext),所以接着来到他的父类AbstractContextLoaderInitializer中这个方法的实现,

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
	
		protected void registerContextLoaderListener(ServletContext servletContext) {
		//调用#createRootApplicationContext()方法,因为自己没有实现,调用子类的方法
		//执行AbstractAnnotationConfigDispatcherServletInitializer这个类的createRootApplicationContext()
		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");
		}
	}

进入AbstractAnnotationConfigDispatcherServletInitializer

	@Override
	protected WebApplicationContext createRootApplicationContext() {
	//getRootConfigClasses()方法,这个类自己没有实现,调用的其实是MyWebAppInitializer#getRootConfigClasses方法
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			//创建一个根容器
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			//把我们的配置类注册进去
			rootAppContext.register(configClasses);
			return rootAppContext;
		}
		else {
			return null;
		}
	}

执行到这里,super.onStartup(servletContext),方法执行完,根容器已经被创建出来了,接着调用registerDispatcherServlet(servletContext)这个方法,方法如下:

protected void registerDispatcherServlet(ServletContext servletContext) {
		//获得名字 默认是dispatcher
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return empty or null");
		// 因为本类没有实现,调动子类AbstractAnnotationConfigDispatcherServletInitializer的
		//createServletApplicationContext方法,创建出web的ioc容器  这个方法的代码与createRootApplicationContext()的执行过程类似
		/*调用子类MyWebAppInitializer 的getServletConfigClasses方法,加载AppConfig.class 然后创建出一个容器。
		*/
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext,
				"createServletApplicationContext() did not return an application " +
				"context for servlet [" + servletName + "]");
		//其实就是new DispatcherServlet(servletAppContext),创建出一个DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
		// 将创建出来的DispatcherServlet添加到容器中
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		Assert.notNull(registration,
				"Failed to register servlet with name '" + servletName + "'." +
				"Check if there is another servlet registered under the same name.");
		//容器加载,就创建
		registration.setLoadOnStartup(1);
		//设置拦截的mapping  回调子类的getServletMappings()方法 也就是MyWebAppInitializer#getServletMappings方法
		registration.addMapping(getServletMappings());
		//设置异步支持 默认是true
		registration.setAsyncSupported(isAsyncSupported());
		//添加过滤器,可以通过重写ServletFilters方法 ,来添加过滤器
		Filter[] filters = get	ServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}

执行到这里,作用是创建出web的ioc容器,并且创建出DispatcherServlet,设置了他的启动机制,设置了ServletMapping,所有就不需要的web.xml配置,就能成功启动容器。
(其实根容器和web容器其实是一个父子关系),每一个容器,装载一些特定的组件。
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第15张图片

DispatcherServlet需要一个WebApplicationContext(一个普通ApplicationContext的扩展)来进行自己的配置。WebApplicationContext有一个指向它关联的ServletContext和Servlet的链接。它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找需要访问的WebApplicationContext。
对于许多只有一个WebApplicationContext的应用程序来说,这是简单而充分的。还可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息,
根WebApplicationContext通常包含基础设施bean,比如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean是有效继承的,可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地

总结一下,画了一个uml时序图:
IDEA搭建纯注解版本SpringMVC的web开发环境并分析启动原理_第16张图片

  • web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
  • spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
  • 并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)

你可能感兴趣的:(spring,springmvc)