现在spring开发的项目,越来越多的用到注解开发了,所以这里就记录一下,存注解开发搭建sping的web开发。
(1)创建一个maven工程,
直接点next,这里不添加原型插件,(到创建后面在添加web环境)
设置groupId 和artifactid 信息。然后next.
点击finish创建项目工程
然后进入这个页面,配置文件结构的属性,点击加号。来添加web环境
点击web,添加web环境
选中这个之后出现这个页面(设置web资源的目录,也就是我们属性的webapp所在的位置,它默认的位置并不正确,所有我们要修改一下)
修改为 src\main\webapp
下面这个是设置web.xml的因为我们现在搭建的注解版,所以就不需要,(点击-号把默认的删除)
然后点击ok.
这里就有了一个web环境的标识了,说明可以了
然后查看文件目录结构:
到这里项目的结构就搭建好了,现在配置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插件
控制台打印日志:
[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"]
相比传统搭建传统的web环境,要在web.xml当中配置前端控制器组件DispatcherServlet
,需要在springmvc.xml当中配置视图解析器,…等等,那么在这种纯注解的情况下,他是如何创建出ioc容器的呢,如何添加DispatcherServlet这个组件的呢。现在来一探究竟。
我们MyWebAppInitializer
这个类继承了一个AbstractAnnotationConfigDispatcherServletInitializer
就从这个类入手。
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当中。> var1, ServletContext var2)
经过查看源码,以及debug调试发现,这个ServletContainerInitializer其实就是SpringServletContainerInitializer
,来看SpringServletContainerInitializer所在在包下的目录结构。
根据上面介绍的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
)
经过上面的分析,可以大概小结一下:
因为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容器其实是一个父子关系),每一个容器,装载一些特定的组件。
DispatcherServlet需要一个WebApplicationContext(一个普通ApplicationContext的扩展)来进行自己的配置。WebApplicationContext有一个指向它关联的ServletContext和Servlet的链接。它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找需要访问的WebApplicationContext。
对于许多只有一个WebApplicationContext的应用程序来说,这是简单而充分的。还可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息,
根WebApplicationContext通常包含基础设施bean,比如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean是有效继承的,可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地