IDEA搭建純注解版本SpringMVC的web開發(fā)環(huán)境全過程并分析啟動原理
現(xiàn)在spring開發(fā)的項目,越來越多的用到注解開發(fā)了,所以這里就記錄一下,存注解開發(fā)搭建sping的web開發(fā)。
創(chuàng)建一個maven工程
直接點next,這里不添加原型插件,(到創(chuàng)建后面在添加web環(huán)境)
設(shè)置groupId 和artifactid 信息。
然后next.
點擊finish創(chuàng)建項目工程
然后進(jìn)入這個頁面,配置文件結(jié)構(gòu)的屬性,點擊加號。來添加web環(huán)境
點擊web,添加web環(huán)境
選中這個之后出現(xiàn)這個頁面(設(shè)置web資源的目錄,也就是我們屬性的webapp所在的位置,它默認(rèn)的位置并不正確,所有我們要修改一下)
修改為 src\main\webapp
下面這個是設(shè)置web.xml的因為我們現(xiàn)在搭建的注解版,所以就不需要,(點擊-號把默認(rèn)的刪除)
然后點擊ok.
這里就有了一個web環(huán)境的標(biāo)識了,說明可以了
然后查看文件目錄結(jié)構(gòu):
到這里項目的結(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 項目并沒有設(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)建一個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} */ /** * 這個是跟容器,相當(dāng)于ApplicationContxt.xml,包掃描的時候要排除@Controller注解,避免重復(fù)掃描 */ @ComponentScan(value ={"com.kuake"}, excludeFilters={@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})}) @Configuration//聲明這是一個配置類 public class RootConfig { }
(2)創(chuàng)建一個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 //配置只會掃描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)建一個容器的初始化器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)建一個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)建一個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應(yīng)用,使用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"]
進(jìn)入瀏覽器:
到這里說明我們的環(huán)境搭建成功
相比傳統(tǒng)搭建傳統(tǒng)的web環(huán)境,要在web.xml當(dāng)中配置前端控制器組件DispatcherServlet
,需要在springmvc.xml當(dāng)中配置視圖解析器,…等等,那么在這種純注解的情況下,他是如何創(chuàng)建出ioc容器的呢,如何添加DispatcherServlet這個組件的呢?,F(xiàn)在來一探究竟。
我們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)) { //創(chuàng)建一個跟容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configClasses); return rootAppContext; } else { return null; } } @Override protected WebApplicationContext createServletApplicationContext() { //創(chuàng)建一個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(),分別用來創(chuàng)建根容器
和子容器
。
因為定義了兩個抽象方法getRootConfigClasses()和getServletConfigClasses(),在創(chuàng)建的過程中也都調(diào)用了這倆個方法,如果我們的子類重寫了這兩個抽象方法,那么父類在創(chuàng)建的時候,就會回調(diào)子類的方法 (這其實符合一個設(shè)計模式,模板方法)
(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)建一個web的ioc容器【createServletApplicationContext()】
- 2、創(chuàng)建了DispatcherServlet【createDispatcherServlet()】
- 3、將創(chuàng)建的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()
創(chuàng)建一個根容器。
(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
這個類會被加載呢,進(jìn)而創(chuàng)建根容器,創(chuàng)建web的ioc容器呢
來看這個類的介紹:
- WebApplicationInitializer是Spring MVC提供的一個接口,它確保檢測到您的實現(xiàn)并自動用于初始化Servlet 3容器。
- WebApplicationInitializer的抽象基類實現(xiàn)AbstractDispatcherServletInitializer通過重寫方法來指定servlet映射和DispatcherServlet配置的位置,使得注冊DispatcherServlet更加容易。
這里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一個初始化規(guī)范,根據(jù)官方文檔的解釋,用自己的話總結(jié)一下有關(guān)的重要幾點,大概意思就是:
- web容器在啟動的時候,會掃描每個jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- 加載這個文件制定的類,并且可以通過@HandlesTypes注解,
- 把感興趣的類的信息,注入到
onStartup(Set<Class<?>> var1, ServletContext var2)
var1當(dāng)中
經(jīng)過查看源碼,以及debug調(diào)試發(fā)現(xiàn),這個ServletContainerInitializer其實就是SpringServletContainerInitializer
,來看SpringServletContainerInitializer所在在包下的目錄結(jié)構(gòu)。
根據(jù)上面介紹的servlet3.0規(guī)范,當(dāng)servlet容器啟動的時候,就會加載javax.servlet.ServletContainerInitializer文件中指定的類
其內(nèi)容就是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); } } }
這段代碼的簡單的介紹一下:因為這個類上標(biāo)注了@HandlesTypes(WebApplicationInitializer.class)
,所以會加載所有WebApplicationInitializer
信息都會被注入到onStartup()
方法的形參webAppInitializerClasses
上,然后遍歷,判斷如果不是接口【!waiClass.isInterface()】,不是抽象類【!Modifier.isAbstract(waiClass.getModifiers()】,那么就實例化,并且添加在集合當(dāng)中。最后遍歷集合initializers
,代用每一個對象的#onStartup(servletContext)
方法。在這個遍歷上,打上一個斷點,看一下initializers
中有哪些對象。
for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }
查看結(jié)果如下:(加載的感興趣的類信息一共有4個,因為其他三個都是抽象類,不符合實例化的條件,所有集合中也就一個類,就是我們定義的MyWebAppInitializer
)
經(jīng)過上面的分析,可以大概小結(jié)一下:
因為servlet3.0容器加載規(guī)范,會加載特定位置的文件中指定的類,在這里也就是SpringServletContainerInitializer,然后加載@HandleType注解標(biāo)注的感興趣的類,然后根據(jù)條件實例化這些類,添加到集合中,遍歷集合然后調(diào)用他們的onStartup方法
有了這些基礎(chǔ),那么就可以來看一下,他的執(zhí)行調(diào)用過程,來看一下容器如何被創(chuàng)建的,核心控制器DispatcherServlet是如何被添加到容器中…
執(zhí)行initializer.onStartup(servletContext)
,所以來到MyWebAppInitializer#onStartup
的方法,因為他本身沒有重寫這個方法,所以往上找他的父類AbstractAnnotationConfigDispatcherServletInitializer
,但是這里也
沒有那么就接著再找父類AbstractDispatcherServletInitializer
,在這個類中onStartup
方法代碼如下:
@Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); }
他分為兩步,先執(zhí)行super.onStartup(servletContext)
,所以接著來到他的父類AbstractContextLoaderInitializer
中這個方法的實現(xiàn),
@Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { //調(diào)用#createRootApplicationContext()方法,因為自己沒有實現(xiàn),調(diào)用子類的方法 //執(zhí)行AbstractAnnotationConfigDispatcherServletInitializer這個類的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()方法,這個類自己沒有實現(xiàn),調(diào)用的其實是MyWebAppInitializer#getRootConfigClasses方法 Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { //創(chuàng)建一個根容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); //把我們的配置類注冊進(jìn)去 rootAppContext.register(configClasses); return rootAppContext; } else { return null; } }
執(zhí)行到這里,super.onStartup(servletContext),方法執(zhí)行完,根容器已經(jīng)被創(chuàng)建出來了,接著調(diào)用registerDispatcherServlet(servletContext)這個方法,方法如下:
protected void registerDispatcherServlet(ServletContext servletContext) { //獲得名字 默認(rèn)是dispatcher String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return empty or null"); // 因為本類沒有實現(xiàn),調(diào)動子類AbstractAnnotationConfigDispatcherServletInitializer的 //createServletApplicationContext方法,創(chuàng)建出web的ioc容器 這個方法的代碼與createRootApplicationContext()的執(zhí)行過程類似 /*調(diào)用子類MyWebAppInitializer 的getServletConfigClasses方法,加載AppConfig.class 然后創(chuàng)建出一個容器。 */ WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() did not return an application " + "context for servlet [" + servletName + "]"); //其實就是new DispatcherServlet(servletAppContext),創(chuàng)建出一個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è)置了他的啟動機(jī)制,設(shè)置了ServletMapping,所有就不需要的web.xml配置,就能成功啟動容器。
(其實根容器和web容器其實是一個父子關(guān)系),每一個容器,裝載一些特定的組件。
DispatcherServlet需要一個WebApplicationContext(一個普通ApplicationContext的擴(kuò)展)來進(jìn)行自己的配置。
WebApplicationContext有一個指向它關(guān)聯(lián)的ServletContext和Servlet的鏈接。
它還綁定到ServletContext,以便應(yīng)用程序可以在requestcontext tutils上使用靜態(tài)方法來查找需要訪問的WebApplicationContext。
對于許多只有一個WebApplicationContext的應(yīng)用程序來說,這是簡單而充分的。
還可以有一個上下文層次結(jié)構(gòu),其中一個根WebApplicationContext在多個DispatcherServlet(或其他Servlet)實例之間共享,每個實例都有自己的子WebApplicationContext配置。
有關(guān)上下文層次結(jié)構(gòu)特性的更多信息,根WebApplicationContext通常包含基礎(chǔ)設(shè)施bean,比如需要跨多個Servlet實例共享的數(shù)據(jù)存儲庫和業(yè)務(wù)服務(wù)。
這些bean是有效繼承的,可以在特定于Servlet的子WebApplicationContext中重寫(即重新聲明),該上下文通常包含給定Servlet的本地
總結(jié)一下
畫了一個uml時序圖:
- web容器在啟動的時候,會掃描每個jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- spring的應(yīng)用一啟動會加載感興趣的WebApplicationInitializer接口的下的所有組件;
- 并且為WebApplicationInitializer組件創(chuàng)建對象(組件不是接口,不是抽象類)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式
這篇文章主要介紹了詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java學(xué)習(xí)筆記:基本輸入、輸出數(shù)據(jù)操作實例分析
這篇文章主要介紹了Java學(xué)習(xí)筆記:基本輸入、輸出數(shù)據(jù)操作,結(jié)合實例形式分析了Java輸入、輸出數(shù)據(jù)相關(guān)函數(shù)使用技巧與操作注意事項,需要的朋友可以參考下2020-04-04java利用java.net.URLConnection發(fā)送HTTP請求的方法詳解
如何通過Java(模擬瀏覽器)發(fā)送HTTP請求是我們在日常經(jīng)常會遇到的問題,下面這篇文章主要給大家介紹了關(guān)于java利用java.net.URLConnection發(fā)送HTTP請求的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05Java編程實現(xiàn)時間和時間戳相互轉(zhuǎn)換實例
這篇文章主要介紹了什么是時間戳,以及Java編程實現(xiàn)時間和時間戳相互轉(zhuǎn)換實例,具有一定的參考價值,需要的朋友可以了解下。2017-09-09