IDEA搭建純注解版本SpringMVC的web開發(fā)環(huán)境全過程并分析啟動(dòng)原理
現(xiàn)在spring開發(fā)的項(xiàng)目,越來越多的用到注解開發(fā)了,所以這里就記錄一下,存注解開發(fā)搭建sping的web開發(fā)。
創(chuàng)建一個(gè)maven工程
直接點(diǎn)next,這里不添加原型插件,(到創(chuàng)建后面在添加web環(huán)境)

設(shè)置groupId 和artifactid 信息。
然后next.

點(diǎn)擊finish創(chuàng)建項(xiàng)目工程

然后進(jìn)入這個(gè)頁(yè)面,配置文件結(jié)構(gòu)的屬性,點(diǎn)擊加號(hào)。來添加web環(huán)境

點(diǎn)擊web,添加web環(huán)境

選中這個(gè)之后出現(xiàn)這個(gè)頁(yè)面(設(shè)置web資源的目錄,也就是我們屬性的webapp所在的位置,它默認(rèn)的位置并不正確,所有我們要修改一下)

修改為 src\main\webapp

下面這個(gè)是設(shè)置web.xml的因?yàn)槲覀儸F(xiàn)在搭建的注解版,所以就不需要,(點(diǎn)擊-號(hào)把默認(rèn)的刪除)
然后點(diǎn)擊ok.
這里就有了一個(gè)web環(huán)境的標(biāo)識(shí)了,說明可以了

然后查看文件目錄結(jié)構(gòu):

到這里項(xiàng)目的結(jié)構(gòu)就搭建好了
配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<!--注意默認(rèn)情況下 創(chuàng)建出的maven 項(xiàng)目并沒有設(shè)置打包的類型,這里設(shè)置成war包-->
<packaging>war</packaging>
<groupId>com.kuake</groupId>
<artifactId>springmvc-web</artifactId>
<version>1.0-SNAPSHOT</version>
<!--配置依賴 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!--導(dǎo)入jackson包,使用@responseBody與@requestBody所需要-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<!--設(shè)置忽略沒有web.xml文件-->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!--這里就不使用外部的tomcat,使用maven的tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
</configuration>
</plugin>
<!--如果不指定,maven指定maven編譯的jdk版本,如果不指定,maven3默認(rèn)用jdk 1.5 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>(1)創(chuàng)建一個(gè)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}
*/
/**
* 這個(gè)是跟容器,相當(dāng)于ApplicationContxt.xml,包掃描的時(shí)候要排除@Controller注解,避免重復(fù)掃描
*/
@ComponentScan(value ={"com.kuake"},
excludeFilters={@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
@Configuration//聲明這是一個(gè)配置類
public class RootConfig {
}
(2)創(chuàng)建一個(gè)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應(yīng)用的配置 相當(dāng)于配置文件SpringMVC.xml
*
*/
@Configuration
@EnableWebMvc//開啟全面接管spingmvc
//配置只會(huì)掃描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) {
//默認(rèn)前綴 /WEB-INF/ 后綴.jsp
registry.jsp();
}
//可以自定義添加各種組件 通過重寫方法
}
(3)創(chuàng)建一個(gè)容器的初始化器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 };
}
//設(shè)置DispatcherServlet的攔截規(guī)則 / 代表攔截所有但是不包括jsp
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
(4)創(chuàng)建一個(gè)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下創(chuàng)建一個(gè)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>Title</title>
</head>
<body>
hello world!
</body>
</html>
(5)啟動(dòng)web應(yīng)用,使用maven的tomcat插件

控制臺(tái)打印日志:
[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"]
進(jìn)入瀏覽器:

到這里說明我們的環(huán)境搭建成功
相比傳統(tǒng)搭建傳統(tǒng)的web環(huán)境,要在web.xml當(dāng)中配置前端控制器組件DispatcherServlet,需要在springmvc.xml當(dāng)中配置視圖解析器,…等等,那么在這種純注解的情況下,他是如何創(chuàng)建出ioc容器的呢,如何添加DispatcherServlet這個(gè)組件的呢?,F(xiàn)在來一探究竟。
我們MyWebAppInitializer這個(gè)類繼承了一個(gè)AbstractAnnotationConfigDispatcherServletInitializer就從這個(gè)類入手。

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)) {
//創(chuàng)建一個(gè)跟容器
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
//創(chuàng)建一個(gè)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();
}這個(gè)類 有兩個(gè)主要的方法createRootApplicationContext()和createServletApplicationContext(),分別用來創(chuàng)建根容器和子容器。
因?yàn)槎x了兩個(gè)抽象方法getRootConfigClasses()和getServletConfigClasses(),在創(chuàng)建的過程中也都調(diào)用了這倆個(gè)方法,如果我們的子類重寫了這兩個(gè)抽象方法,那么父類在創(chuàng)建的時(shí)候,就會(huì)回調(diào)子類的方法 (這其實(shí)符合一個(gè)設(shè)計(jì)模式,模板方法)
(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.
*
* <p>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)}.
*
* <p>Because this class extends from {@link AbstractContextLoaderInitializer}, concrete
* implementations are also required to implement {@link #createRootApplicationContext()}
* to set up a parent "<strong>root</strong>" 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.
* <p>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()}.
* <p>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}.
* <p>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}.
* <p>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:
* <ul>
* <li>a default filter name is chosen based on its concrete type
* <li>the {@code asyncSupported} flag is set depending on the
* return value of {@link #isAsyncSupported() asyncSupported}
* <li>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}
* </ul>
* <p>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()}.
* <p>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、創(chuàng)建一個(gè)web的ioc容器【createServletApplicationContext()】
- 2、創(chuàng)建了DispatcherServlet【createDispatcherServlet()】
- 3、將創(chuàng)建的DispatcherServlet添加到ServletContext中;
(3)接著再上一個(gè)父類: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.
*
* <p>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 "<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;
}
}
主要的方法createRootApplicationContext()創(chuàng)建一個(gè)根容器。
(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;
}那么容器啟動(dòng)時(shí)候,為什么這么MyWebAppInitializer這個(gè)類會(huì)被加載呢,進(jìn)而創(chuàng)建根容器,創(chuàng)建web的ioc容器呢
來看這個(gè)類的介紹:
- WebApplicationInitializer是Spring MVC提供的一個(gè)接口,它確保檢測(cè)到您的實(shí)現(xiàn)并自動(dòng)用于初始化Servlet 3容器。
- WebApplicationInitializer的抽象基類實(shí)現(xiàn)AbstractDispatcherServletInitializer通過重寫方法來指定servlet映射和DispatcherServlet配置的位置,使得注冊(cè)DispatcherServlet更加容易。
這里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一個(gè)初始化規(guī)范,根據(jù)官方文檔的解釋,用自己的話總結(jié)一下有關(guān)的重要幾點(diǎn),大概意思就是:
- web容器在啟動(dòng)的時(shí)候,會(huì)掃描每個(gè)jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- 加載這個(gè)文件制定的類,并且可以通過@HandlesTypes注解,
- 把感興趣的類的信息,注入到
onStartup(Set<Class<?>> var1, ServletContext var2)var1當(dāng)中
經(jīng)過查看源碼,以及debug調(diào)試發(fā)現(xiàn),這個(gè)ServletContainerInitializer其實(shí)就是SpringServletContainerInitializer,來看SpringServletContainerInitializer所在在包下的目錄結(jié)構(gòu)。

根據(jù)上面介紹的servlet3.0規(guī)范,當(dāng)servlet容器啟動(dòng)的時(shí)候,就會(huì)加載javax.servlet.ServletContainerInitializer文件中指定的類
其內(nèi)容就是org.springframework.web.SpringServletContainerInitializer,所有也就會(huì)在容器啟動(dòng)的時(shí)候,加載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);
}
}
}這段代碼的簡(jiǎn)單的介紹一下:因?yàn)檫@個(gè)類上標(biāo)注了@HandlesTypes(WebApplicationInitializer.class),所以會(huì)加載所有WebApplicationInitializer信息都會(huì)被注入到onStartup()方法的形參webAppInitializerClasses上,然后遍歷,判斷如果不是接口【!waiClass.isInterface()】,不是抽象類【!Modifier.isAbstract(waiClass.getModifiers()】,那么就實(shí)例化,并且添加在集合當(dāng)中。最后遍歷集合initializers,代用每一個(gè)對(duì)象的#onStartup(servletContext)方法。在這個(gè)遍歷上,打上一個(gè)斷點(diǎn),看一下initializers中有哪些對(duì)象。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}查看結(jié)果如下:(加載的感興趣的類信息一共有4個(gè),因?yàn)槠渌齻€(gè)都是抽象類,不符合實(shí)例化的條件,所有集合中也就一個(gè)類,就是我們定義的MyWebAppInitializer)

經(jīng)過上面的分析,可以大概小結(jié)一下:
因?yàn)閟ervlet3.0容器加載規(guī)范,會(huì)加載特定位置的文件中指定的類,在這里也就是SpringServletContainerInitializer,然后加載@HandleType注解標(biāo)注的感興趣的類,然后根據(jù)條件實(shí)例化這些類,添加到集合中,遍歷集合然后調(diào)用他們的onStartup方法
有了這些基礎(chǔ),那么就可以來看一下,他的執(zhí)行調(diào)用過程,來看一下容器如何被創(chuàng)建的,核心控制器DispatcherServlet是如何被添加到容器中…
執(zhí)行initializer.onStartup(servletContext),所以來到MyWebAppInitializer#onStartup的方法,因?yàn)樗旧頉]有重寫這個(gè)方法,所以往上找他的父類AbstractAnnotationConfigDispatcherServletInitializer,但是這里也
沒有那么就接著再找父類AbstractDispatcherServletInitializer,在這個(gè)類中onStartup方法代碼如下:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}他分為兩步,先執(zhí)行super.onStartup(servletContext),所以接著來到他的父類AbstractContextLoaderInitializer中這個(gè)方法的實(shí)現(xiàn),
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
//調(diào)用#createRootApplicationContext()方法,因?yàn)樽约簺]有實(shí)現(xiàn),調(diào)用子類的方法
//執(zhí)行AbstractAnnotationConfigDispatcherServletInitializer這個(gè)類的createRootApplicationContext()
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//添加容器監(jiān)聽事件
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");
}
}進(jìn)入AbstractAnnotationConfigDispatcherServletInitializer
@Override
protected WebApplicationContext createRootApplicationContext() {
//getRootConfigClasses()方法,這個(gè)類自己沒有實(shí)現(xiàn),調(diào)用的其實(shí)是MyWebAppInitializer#getRootConfigClasses方法
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//創(chuàng)建一個(gè)根容器
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
//把我們的配置類注冊(cè)進(jìn)去
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}執(zhí)行到這里,super.onStartup(servletContext),方法執(zhí)行完,根容器已經(jīng)被創(chuàng)建出來了,接著調(diào)用registerDispatcherServlet(servletContext)這個(gè)方法,方法如下:
protected void registerDispatcherServlet(ServletContext servletContext) {
//獲得名字 默認(rèn)是dispatcher
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
// 因?yàn)楸绢悰]有實(shí)現(xiàn),調(diào)動(dòng)子類AbstractAnnotationConfigDispatcherServletInitializer的
//createServletApplicationContext方法,創(chuàng)建出web的ioc容器 這個(gè)方法的代碼與createRootApplicationContext()的執(zhí)行過程類似
/*調(diào)用子類MyWebAppInitializer 的getServletConfigClasses方法,加載AppConfig.class 然后創(chuàng)建出一個(gè)容器。
*/
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
//其實(shí)就是new DispatcherServlet(servletAppContext),創(chuàng)建出一個(gè)DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 將創(chuàng)建出來的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.");
//容器加載,就創(chuàng)建
registration.setLoadOnStartup(1);
//設(shè)置攔截的mapping 回調(diào)子類的getServletMappings()方法 也就是MyWebAppInitializer#getServletMappings方法
registration.addMapping(getServletMappings());
//設(shè)置異步支持 默認(rèn)是true
registration.setAsyncSupported(isAsyncSupported());
//添加過濾器,可以通過重寫ServletFilters方法 ,來添加過濾器
Filter[] filters = get ServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
執(zhí)行到這里,作用是創(chuàng)建出web的ioc容器,并且創(chuàng)建出DispatcherServlet,設(shè)置了他的啟動(dòng)機(jī)制,設(shè)置了ServletMapping,所有就不需要的web.xml配置,就能成功啟動(dòng)容器。
(其實(shí)根容器和web容器其實(shí)是一個(gè)父子關(guān)系),每一個(gè)容器,裝載一些特定的組件。

DispatcherServlet需要一個(gè)WebApplicationContext(一個(gè)普通ApplicationContext的擴(kuò)展)來進(jìn)行自己的配置。
WebApplicationContext有一個(gè)指向它關(guān)聯(lián)的ServletContext和Servlet的鏈接。
它還綁定到ServletContext,以便應(yīng)用程序可以在requestcontext tutils上使用靜態(tài)方法來查找需要訪問的WebApplicationContext。
對(duì)于許多只有一個(gè)WebApplicationContext的應(yīng)用程序來說,這是簡(jiǎn)單而充分的。
還可以有一個(gè)上下文層次結(jié)構(gòu),其中一個(gè)根WebApplicationContext在多個(gè)DispatcherServlet(或其他Servlet)實(shí)例之間共享,每個(gè)實(shí)例都有自己的子WebApplicationContext配置。
有關(guān)上下文層次結(jié)構(gòu)特性的更多信息,根WebApplicationContext通常包含基礎(chǔ)設(shè)施bean,比如需要跨多個(gè)Servlet實(shí)例共享的數(shù)據(jù)存儲(chǔ)庫(kù)和業(yè)務(wù)服務(wù)。
這些bean是有效繼承的,可以在特定于Servlet的子WebApplicationContext中重寫(即重新聲明),該上下文通常包含給定Servlet的本地
總結(jié)一下
畫了一個(gè)uml時(shí)序圖:

- web容器在啟動(dòng)的時(shí)候,會(huì)掃描每個(gè)jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- spring的應(yīng)用一啟動(dòng)會(huì)加載感興趣的WebApplicationInitializer接口的下的所有組件;
- 并且為WebApplicationInitializer組件創(chuàng)建對(duì)象(組件不是接口,不是抽象類)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 如何從配置文件讀取值到對(duì)象中
這篇文章主要介紹了SpringBoot 如何從配置文件讀取值到對(duì)象中,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式
這篇文章主要介紹了詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Java學(xué)習(xí)筆記:基本輸入、輸出數(shù)據(jù)操作實(shí)例分析
這篇文章主要介紹了Java學(xué)習(xí)筆記:基本輸入、輸出數(shù)據(jù)操作,結(jié)合實(shí)例形式分析了Java輸入、輸出數(shù)據(jù)相關(guān)函數(shù)使用技巧與操作注意事項(xiàng),需要的朋友可以參考下2020-04-04
java利用java.net.URLConnection發(fā)送HTTP請(qǐng)求的方法詳解
如何通過Java(模擬瀏覽器)發(fā)送HTTP請(qǐng)求是我們?cè)谌粘=?jīng)常會(huì)遇到的問題,下面這篇文章主要給大家介紹了關(guān)于java利用java.net.URLConnection發(fā)送HTTP請(qǐng)求的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05
springboot異步@Async的使用及失效場(chǎng)景介紹
本文主要介紹了springboot異步@Async的使用及失效場(chǎng)景介紹,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Java編程實(shí)現(xiàn)時(shí)間和時(shí)間戳相互轉(zhuǎn)換實(shí)例
這篇文章主要介紹了什么是時(shí)間戳,以及Java編程實(shí)現(xiàn)時(shí)間和時(shí)間戳相互轉(zhuǎn)換實(shí)例,具有一定的參考價(jià)值,需要的朋友可以了解下。2017-09-09

