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

 更新时间:2024年11月14日 10:31:17   作者:莫失莫忘hh  
本文详细介绍了如何使用注解开发搭建Spring Web环境,包括创建Maven工程、配置web环境、设置pom.xml、创建配置类和控制器等步骤,同时,文章还探讨了注解开发中如何创建IOC容器和添加DispatcherServlet组件,并通过Servlet 3.0规范

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

创建一个maven工程

直接点next,这里不添加原型插件,(到创建后面在添加web环境)

设置groupId 和artifactid 信息。

然后next.

点击finish创建项目工程

然后进入这个页面,配置文件结构的属性,点击加号。来添加web环境

点击web,添加web环境

选中这个之后出现这个页面(设置web资源的目录,也就是我们属性的webapp所在的位置,它默认的位置并不正确,所有我们要修改一下)

修改为 src\main\webapp

下面这个是设置web.xml的因为我们现在搭建的注解版,所以就不需要,(点击-号把默认的删除)

然后点击ok.

这里就有了一个web环境的标识了,说明可以了

然后查看文件目录结构:

到这里项目的结构就搭建好了

配置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>
    <!--注意默认情况下 创建出的maven 项目并没有设置打包的类型,这里设置成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>
        <!--导入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>
                	<!--设置忽略没有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默认用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)创建一个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>Title</title>
</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.
*
* <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} &mdash;
    * 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、创建一个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.
 *
 * <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()创建一个根容器。 

(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<Class<?>> var1, ServletContext var2)var1当中

经过查看源码,以及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的本地

总结一下

画了一个uml时序图:

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java 获取mac地址的两种方法(推荐)

    java 获取mac地址的两种方法(推荐)

    下面小编就为大家带来一篇java 获取mac地址的两种方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • maven如何在tomcat8中实现自动部署

    maven如何在tomcat8中实现自动部署

    本篇文章主要介绍了maven如何在tomcat8中实现自动部署,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • Java类初始化顺序详解

    Java类初始化顺序详解

    这篇文章主要介绍了Java类初始化顺序详解,java语言在使用过程中最先开始就是初始化,在工作中如果遇到什么问题需 要定位往往到最后也可能是初始化的问题,因此掌握初始化的顺序很重要,需要的朋友可以参考下
    2023-08-08
  • Java Zip压缩之简化文件和文件夹的压缩操作

    Java Zip压缩之简化文件和文件夹的压缩操作

    这篇文章主要给大家介绍了关于Java Zip压缩之简化文件和文件夹的压缩操作,Zip压缩是一种常见的文件压缩格式,它将多个文件和文件夹打包成一个以.zip为后缀的压缩包,需要的朋友可以参考下
    2023-10-10
  • springboot项目打docker镜像实例(入门级)

    springboot项目打docker镜像实例(入门级)

    最近做个项目,我们想把自己的程序打包成镜像,并运行在docker容器中,本文主要介绍了springboot项目打docker镜像实例,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • Java程序结构与常量变量难点解析

    Java程序结构与常量变量难点解析

    JAVA的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行顺序结构是最简单的算法结构,语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构
    2021-10-10
  • 如何通过JVM角度谈谈Java的clone操作

    如何通过JVM角度谈谈Java的clone操作

    java中仅有的创建对象的两种方式:①.使用new操作符创建对象;②.使用clone方法复制对象。下面这篇文章主要通过JVM角度给大家详细谈谈Java的clone操作的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-06-06
  • Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现过程

    Spring Boot + Vue 基于 RSA 的用户身份

    RSA是一种非对称加密算法,适用于用户身份认证加密,本文介绍了基于RSA的用户身份认证加密机制的实现,包括前端Vue.js使用jsencrypt库对用户名密码进行加密,后端使用RSA私钥解密验证用户凭据,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • 在Spring Boot使用Undertow服务的方法

    在Spring Boot使用Undertow服务的方法

    Undertow是RedHAT红帽公司开源的产品,采用JAVA开发,是一款灵活,高性能的web服务器,提供了NIO的阻塞/非阻塞API,也是Wildfly的默认Web容器,这篇文章给大家介绍了在Spring Boot使用Undertow服务的方法,感兴趣的朋友跟随小编一起看看吧
    2023-05-05
  • Java中HttpServletResponse响应中文出现乱码问题

    Java中HttpServletResponse响应中文出现乱码问题

    这篇文章主要介绍了Java中HttpServletResponse响应中文出现乱码问题的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06

最新评论