springboot之IOC容器ServletWebServerApplicationContext分析

1:web.xml是怎么没的?

1.1:Servlet3.0之前

在servlet3.0以前,编写web程序的流程大概如下:

1:编写servlet,并通过<servlet>标签注册到web.xml文件中
2:编写filter,并通过<filter>标签注册到web.xml文件中
3:编写lisner,并通过<listener>标签注册到web.xml文件中

用的比较多,这里就不再提供具体实例了。
web.xml文件是web容器和应用程序的纽带,相关的信息都需要注册在这里。web容器通过web.xml读取信息。但是这个过程比较繁琐,作为开发的我们,如果能通过编程的方式来完成这个过程的话就能实现不依赖于web.xml了。所幸,我们最终迎来了servlet3.0时代,让我们这个需求能够得到满足。

1.2:Servlet3.0

目前有两种非web.xml方式注册servlet,filter等组件到容器中,第一种是使用@WebServlet@WebFilter等注解,第二种方式是使用ServletContext(这是web容器封装servlet,filter,listener等组件的上下文对象),主要API如下:

  • 添加servlet相关API
public ServletRegistration.Dynamic addServlet(String servletName,
            Class<? extends Servlet> servletClass);
public ServletRegistration.Dynamic addServlet(String servletName, String className);
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);

调用以上方法,相当于在web.xml中配置<servlet>标签。

  • 添加filter相关API
public FilterRegistration.Dynamic addFilter(String filterName,
            Class<? extends Filter> filterClass);
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
public FilterRegistration.Dynamic addFilter(String filterName, String className);

调用以上方法相当于在web.xml中配置<filter>标签。

  • 添加listener相关API
public void addListener(String className);
public <T extends EventListener> void addListener(T t);
public void addListener(Class<? extends EventListener> listenerClass);

调用以上方法相当于在web.xml中配置<listener>标签。

现在,向web容器注册各种组件的API有了,想要完成注册,该怎么做呢?此时,就需要接口来提供规范了,这个接口是javax.servlet.ServletContainerInitializer,源码如下:

// 通过在META-INF/services/文件夹下创建javax.servlet.ServletContainerInitializer文件
// 然后将自己提供的实现类按照一行一个的格式配置到文件中,web容器在启动的时候会通过SPI来加载,并执行
// onStartup方法,执行自定义的注册逻辑,完成相关组件的注册
public interface ServletContainerInitializer {
	// 参数c:期望处理的class类型集合
	// 参数ctx:servlet上线文对象,我们通过该对象中来完成各种web组件注册工作
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

接下来我们新建一个servlet项目,当然也可以直接下载源码。来测试一下。

  • servlet
public class MyServletContainerInitializerServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello from servlet register by ServerContainerInitializer!!!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // post也走get
        this.doGet(req, resp);
    }
}
  • filter
public class MyServletContainerInitializerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyServletContainerInitializerFilter.init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyServletContainerInitializerFilter.doFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("MyServletContainerInitializerFilter.destroy");
    }
}
  • ServletContainerInitializer实现类
public class MyServletContainerInitializer implements ServletContainerInitializer {
    private final static String JAR_HELLO_URL = "/hello";

    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("注册servlet到web容器开始!");
        ServletRegistration.Dynamic dynamicServlet = servletContext.addServlet(MyServletContainerInitializerServlet.class.getSimpleName(), MyServletContainerInitializerServlet.class);
        // 添加映射
        dynamicServlet.addMapping(JAR_HELLO_URL);
        System.out.println("注册servlet到web容器结束!");

        System.out.println("注册filter到web容器开始!");
        FilterRegistration.Dynamic dynamicFilter = servletContext.addFilter(MyServletContainerInitializerFilter.class.getSimpleName(), MyServletContainerInitializerFilter.class);
        EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
        dispatcherTypes.add(DispatcherType.REQUEST);
        dispatcherTypes.add(DispatcherType.FORWARD);
        dynamicFilter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);
        System.out.println("注册filter到web容器结束!");
    }
}

现在准备工作都完成了,但是,web容器此时还无法识别到我们的ServletContainerInitializer,根据规范,还需要配置SPI,因此我们在META-INF/services下创建文件javax.servlet.ServletContainerInitializer,并填入实现类内容,如下:
在这里插入图片描述
接下来我们启动查看日志:

...snip...
注册servlet到web容器开始!
注册servlet到web容器结束!
注册filter到web容器开始!
注册filter到web容器结束!
...snip...

访问测试:

C:\Users\Administrator>curl http://192.168.10.119:10080/test_servlet3_without_webxml/hello
hello from servlet register by ServerContainerInitializer!!!

2:springMVC如何集成servlet3.0

我们自己开发程序来集成servlet3.0是通过提供ServletContainerInitializer的实现类,并通过SPI来配置,供web服务器读取,springMVC也是如此,springmvc配置如下图:
在这里插入图片描述
其提供的实现类是org.springframework.web.SpringServletContainerInitializer,源码如下详细看注释!!!

org.springframework.web.SpringServletContainerInitializer
// 该类设计的目的是让开发人员基于编码的方式来支持servlet容器,看到@HandlesTypes(WebApplicationInitializer.class),代表该类设置web容器在回调时,将classpath下的WebApplicationInitializer的实现类作为参数传递到方法onStartup的参数webAppInitializerClasses
// 中,这种是和web.xml对立的方式(也可能和web.xml方式混合使用)
// 操作机制:当支持servlet3的web容器启动的时候,会通过jar servicec API(ServiceLoader.load(xxx))从classpath下的spring-web.jar包中读取META-INF/services/javax.servlet.ServletContainerInitializer文件,在该文件中配置的实现类正是该类,
// 然后就会调用该类的onStartup方法,并将classpath下WebApplicationInitializer的实现类作为参数传递到webAppInitializerClasses参数中
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	// 参数webAppInitializerClasses:classpath下WebApplicationInitializer的实现类
	// 参数servletContext:web容器servlet上下文对象
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();
		// web容器传进来的webAppInitializerClasses不为空
		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 因为一些web容器会将@HandlesTypes指定的类型外的一些类传进来,所以再进一步做个判断,可以说是因为web容器的bug而不得不写的代码
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 添加到initializers集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}
		// initializers为空,即在classpath下没有WebApplicationInitializer的子类,简单的给出日志提示,并return
		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}
		// 日志记录在classpath下发现了多少个WebApplicationInitializer的子类
		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		// 排序
		AnnotationAwareOrderComparator.sort(initializers);
		// 循环调用onoStartup方法,注册web组件到ServletContext中
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

到此处,我们就知道,想要在无web.xml文件的情况下使用springMVC,只需要提供一个org.springframework.web.WebApplicationInitializer的实现类,然后在其onStartup方法中注册web容器相关组件就可以了,其源码如下详细看注释!!!

// 该接口用来在servlet3.0环境中通过编程的方式配置ServletContext(注册Servlet,Filter,Listener等),与之对应的是基于web.xml的配置方式(二者有时候也可以混用)。具体的实现类会通过SpringServletContainerInitializer(web容器的构子类)类加载并调用。
// 一般开发人员会通过web.xml方式来注册DispatcherServlet(当然还有其他组件)到web容器的。可能如下:
/*
<servlet>
   <servlet-name>dispatcher</servlet-name>
   <servlet-class>
     org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
   <init-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
   <servlet-name>dispatcher</servlet-name>
   <url-pattern>/</url-pattern>
 </servlet-mapping>
*/
// 如果是使用WebApplicationInitializer的等价编程配置方式的话,可能如下:
/*
 public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
      XmlWebApplicationContext appContext = new XmlWebApplicationContext();
      appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

      ServletRegistration.Dynamic dispatcher =
        container.addServlet("dispatcher", new DispatcherServlet(appContext));
      dispatcher.setLoadOnStartup(1);
      dispatcher.addMapping("/");
    }

 }
*/
// 例子中是直接实现WebApplicationInitializer接口,实际中可以通过继承AbstractDispatcherServletInitializer类来完成操作,
// 该类已经完成了注册DispatcherServlet等基础工作,我们只需要实现其抽象方法提供IOC容器就可以了
// 程序中“appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");”还是使用了xml文件,我们可以使用基于注解的spring IOC容器来改造代码,实现零xml配置,代码可能如下:
/*
 public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
      // Create the 'root' Spring application context
      AnnotationConfigWebApplicationContext rootContext =
        new AnnotationConfigWebApplicationContext();
      rootContext.register(AppConfig.class);

      // Manage the lifecycle of the root application context
      container.addListener(new ContextLoaderListener(rootContext));

      // Create the dispatcher servlet's Spring application context
      AnnotationConfigWebApplicationContext dispatcherContext =
        new AnnotationConfigWebApplicationContext();
      dispatcherContext.register(DispatcherConfig.class);

      // Register and map the dispatcher servlet
      ServletRegistration.Dynamic dispatcher =
        container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
      dispatcher.setLoadOnStartup(1);
      dispatcher.addMapping("/");
    }
 }
*/
public interface WebApplicationInitializer {

	// 在该方法中可以对servletContext进行servlets,fitlers,listeners,context-params以及属性信息的配置。
	void onStartup(ServletContext servletContext) throws ServletException;
}

既然是springMVC程序,首先当然得定义一个处理请求的handler了,如下:

@Controller
@RequestMapping("/testinterceptor")
public class HelloController {

    @PostConstruct
    public void xxx() {
        System.out.println("HelloController.xxx");
    }

    @RequestMapping("/hi")
    @ResponseBody
    public String sayHi(HttpServletResponse response) {
        System.out.println("HelloController.sayHi");
        String msg = "testinterceptor hi";
        System.out.println(msg);
        return msg;
    }
}

然后我们定义WebApplicationInitializer的子类,如下:

public class MyWebXml implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("MyWebXml 加载开始!");
        // new springmvc的容器对象
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        // 注册控制器
        ctx.register(HelloController.class);
        // 设置servlet上线文对象
        ctx.setServletContext(servletContext);
        String dispatcherServletName = DispatcherServlet.class.getSimpleName();
        // 注册springmvc分发请求的DispatcherServlet
        /*
        相当于在web.xml中配置代码:
          <servlet>
            <servlet-name>DispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
          </servlet>
          <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
          </servlet-mapping>
         */
        ServletRegistration.Dynamic dynamicDispatcherServlet = servletContext.addServlet(dispatcherServletName, new DispatcherServlet(ctx));
        dynamicDispatcherServlet.addMapping("/");
        dynamicDispatcherServlet.setLoadOnStartup(1);
        System.out.println("MyWebXml 加载结束!");
    }
}

此时我们就可以启动程序来访问我们的接口了,如下:

C:\Users\Administrator>curl http://localhost:10080/springmvc_without_webxml_war_exploded/testinterceptor/hi
testinterceptor hi

当然我们也可以通过实现WebApplicationInitializer的抽象子类AbstractDispatcherServletInitializer,该类已经完成了DispatcherServlet的注册工作,可以修改MyWebXml如下:

public class MyWebXml extends AbstractDispatcherServletInitializer {
    private static AnnotationConfigWebApplicationContext servletAc = new AnnotationConfigWebApplicationContext();
    private static AnnotationConfigWebApplicationContext rootAc = new AnnotationConfigWebApplicationContext();

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        // 注册handler
        servletAc.register(HelloController.class);
    }

    /*
    创建 dispatcherservlet的Spring IOC容器
    */
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        return servletAc;
    }

    /*
    返回DispatcherServlet的servletmapping信息
     */
    @Override
    protected String[] getServletMappings() {
        List<String> servletMappingList = new ArrayList<>();
        servletMappingList.add("/");
        return StringUtils.toStringArray(servletMappingList);
    }

    // 创建root 的spring IOC容器
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return rootAc;
    }
}

效果是完全一样的。测试源码从这里下载。

3:springboot如何使用servlet

测试源码从这里下载。

3.1:通过servlet3注解+@ServletComponentScan

通过servlet3.0中定义的@WebXxx相关注解,然后通过@ServletComponentScan配置要扫描的包路径,如下测试:

  • 定义servlet
@WebServlet("/myservlet")
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("res from spring boot servlet!!!");
    }
}
  • 启动类
@SpringBootApplication
@ServletComponentScan(basePackages = { "com.example.springbootintegarationspringmvc" })
public class SpringbootIntegrationSpringmvcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootIntegrationSpringmvcApplication.class, args);
    }

}
  • 启动测试
C:\Users\Administrator>curl http://localhost:8080/myservlet
res from spring boot servlet!!!

3.2:通过RegistrationBean

org.springframework.boot.web.servlet.RegistrationBeanspringboot提供的抽象类,实现了ServletContextInitializer接口,负责将servlet,filter,listener等注册到spring IOC容器中。其源码如下:

// 用于在servlet3.0+环境中程序化方式配置servletContext的接口。与实现了WebApplicationInitializer接口
// 的类不同,不会被SpringServletContainerInitializer自动获取,因此不会被web容器自动加载。
// 但是其扮演的角色和ServletContainerInitializer类似,不同之处在于通过spring来完成生命周期
// 的管理,而非像ServletContainerInitializer是通过web容器管理生命周期。
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {

	private static final Log logger = LogFactory.getLog(RegistrationBean.class);

	private int order = Ordered.LOWEST_PRECEDENCE;

	private boolean enabled = true;
	
	// 这里实现具体的逻辑,完成web组件,如servlet,filter,listener等向servletcontext中
	// 注册的工作,但是注意这个过程是spring在启动web容器的过程中完成的,因为此时web容器只是
	// springboot的一个组件而已。
	// 启动过程是"springboot main->启动web容器->调用该方法配置servletContext"
	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}

	protected abstract String getDescription();

	protected abstract void register(String description, ServletContext servletContext);

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

}

首先来定义一个servlet:

public class MyServlet1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello from my servlet 1");
    }
}

定义Java config类:

@Configuration
public class WebComponentConfiguration {

    @Bean
    public ServletRegistrationBean myServlet1() {
        ServletRegistrationBean myServlet1 = new ServletRegistrationBean();
        myServlet1.addUrlMappings("/myservlet1");
        myServlet1.setServlet(new MyServlet1());
        return myServlet1;
    }
}

启动类:

@SpringBootApplication
@ServletComponentScan(basePackages = { "com.example.springbootintegarationspringmvc" })
public class SpringbootIntegrationSpringmvcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootIntegrationSpringmvcApplication.class, args);
    }

}

启动测试:

$ curl.exe --silent http://192.168.10.119:8080/myservlet1
hello from my servlet 1

接下来,我们看下springboot是如何最终通过调用我们的RegistrationBean来完成ServletContext的设置的。

3.3:调用过程分析

基于springboot V1.5.4RELEASE版本分析。

程序最终会执行到org.springframework.boot.context.embedded.EmbeddedWebApplicationContext,这是AbstractApplicationContext的一个子类,在AbstractApplicationContext类中refresh方法中会调用一个空方法onRefresh,该方法的作用是供子类注册特殊场景下的bean,EmbeddedWebApplication类正是通过重写onRefresh方法来加入到了spring的生命周期中,我们就从这个方法开始分析,源码如下:

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh
protected void onRefresh() {
	super.onRefresh();
	try {
		// <202106031347>
		// 创建servlet容器
		createEmbeddedServletContainer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start embedded container",
				ex);
	}
}

<202106031347>处是创建servlet容器,源码如下:

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
	// localContainer为null
	EmbeddedServletContainer localContainer = this.embeddedServletContainer;
	// localServletContext为null
	ServletContext localServletContext = getServletContext();
	// if为true
	if (localContainer == null && localServletContext == null) {
		// 获取内嵌web容器工厂类
		// <202106031553>
		EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
		// <202106031416>
		// 创建内嵌web容器对象
		this.embeddedServletContainer = containerFactory
				.getEmbeddedServletContainer(getSelfInitializer());
	}
	...snip...
	initPropertySources();
}

<202106031553>处获取的类是通过springboot的autoconfigurationorg.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration定义的,源码如下:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}
	...snip...
}

<202106031416>处重点看下方法getSelfInitializer(),源码如下:

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getSelfInitialize
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	// 返回ServletContextInitializer匿名实现类
	// 注意这里返回的是ServletContextInitializer
	return new ServletContextInitializer() {
		@Override
		public void onStartup(ServletContext servletContext) throws ServletException {
			selfInitialize(servletContext);
		}
	};
}

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#selfInitialize
// 上述匿名实现类调用的方法
private void selfInitialize(ServletContext servletContext) throws ServletException {
	...snip...
	// <202106031436>
	// 获取所有的ServletContextInitializer实现类,并循环调用其onStartup方法
	// 这里其实就会获取到我们通过Javaconfig注册的RegistrationBean了
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		// 重点!!!
		// 包括但不限制于完成DispatahcerServlet注册到ServletContext的工作,以及自定义的RegsitrationBean实现类的相关web组件的注册工作
		beans.onStartup(servletContext);
	}
}

<202106031436>处重点看下getServletContextInitializerBeans(),源码如下:

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getServletContextInitializerBeans
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
	// <202106031440>
	return new ServletContextInitializerBeans(getBeanFactory());
}

<202106031440>处源码如下:

org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
	...snip...
	// <202106031443>
	addServletContextInitializerBeans(beanFactory);
	...snip...
}

<202106031443>处源码如下:

org.springframework.boot.web.servlet.ServletContextInitializerBeans#addServletContextInitializerBeans
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
	// <202106031447>
	for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
			beanFactory, ServletContextInitializer.class)) {
		// 将servletcontextinitializer实现类添加到private final MultiValueMap<Class<?>, ServletContextInitializer> initializers集合中
		//, 后续会循环遍历该集合调用onStartup方法
		addServletContextInitializerBean(initializerBean.getKey(),
				initializerBean.getValue(), beanFactory);
	}
}

<202106031447>处getOrderedBeansOfType方法就获取IOC容器中的ServletContextInitializer的实现类,如下图就是我们通过Java config定义的以及springboot用于注册dispatcherservler注册的:
在这里插入图片描述
注意到此处springboot会获取到IOC容器中所有的ServletContextInitializer的实现类,并调用其onStartup方法,因此我们也可以通过提供ServletContextInitializer的实现类的Java config的方式来注册web组件到内嵌的web容器中,如下定义实现类:

public class MyServlet2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello from servlet 2");
    }
}

@Configuration
public class MyServletContextInitializer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addServlet(MyServlet2.class.getSimpleName(), MyServlet2.class).addMapping("/myservlet2");
    }
}

获取到该java config如下图:
在这里插入图片描述
启动测试:

$ curl.exe --silent http://192.168.10.119:8080/myservlet2
hello from servlet 2

4:ServletWebServerApplicationContext

ServletWebServerApplicationContext方法签名如下:

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext

可以看出这是spring的ApplicationContext容器的一个子类,是当springboot使用内置web容器时的IOC容器类。会将内置web服务器生命周期加入到spring容器的生命周期中,重点看下其父接口org.springframework.boot.web.context.WebServerApplicationContext,源码如下:

// 管理内置web服务器对象WebServer生命周期的接口
public interface WebServerApplicationContext extends ApplicationContext {

	/**
	 * Returns the {@link WebServer} that was created by the context or {@code null} if
	 * the server has not yet been created.
	 * @return the web server
	 */
	WebServer getWebServer();

	String getServerNamespace();

	static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
		return (context instanceof WebServerApplicationContext) && ObjectUtils
				.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
	}

}

有了该接口,就拥有了控制内置web服务器的能力了,这很重要!!!接下来我们看下其加入到SpringIOC容器生命周期而重写的重要方法。

4.1:refresh

ServletWebServerApplicationContext复写了父类AbstractApplicationContext的refresh方法,增加了当刷新容器的过程中发生异常停止web服务器的操作,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
@Override
public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	}
	catch (RuntimeException ex) {
		// <202106041055>
		// 发生异常,停止并释放web服务器资源
		stopAndReleaseWebServer();
		throw ex;
	}
}
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#stopAndReleaseWebServer
private void stopAndReleaseWebServer() {
	WebServer webServer = this.webServer;
	if (webServer != null) {
		try {
			// 停止web服务器
			webServer.stop();
			// 置空web服务器
			this.webServer = null;
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
}

4.2:postProcessBeanFactory

ServletWebServerApplicationContext重写了AbstractApplicationContext的模板方法postProcessBeanFactory,执行该方法时,bean工厂对象已经创建完毕,所有的bean定义也都加载完毕,但是还没有进行任何spring bean的创建,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#postProcessBeanFactory
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	// <202106041124>
	beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
	// <202106041759>
	beanFactory.ignoreDependencyInterface(ServletContextAware.class);
	// 该处代码先忽略
	registerWebApplicationScopes();
}

<202106041124>处添加的WebApplicationContextServletContextAwareProcessorServletContextAwareProcessor类的子类,构造函数设置的是ServletWebServerApplicationContext类的实例,后续就可以通过该实例来获取ServletContext了。在bean生命周期初始化bean的过程中如果该bean实现了ServletContextAware接口,则会调用其对应的setServetContext方法设置ServletContext对象,这样在bean中就可以拿到内嵌web容器的ServletContext对象了,如下图是调用时debug信息:
代码位置org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
在这里插入图片描述
<202106041759>ignoreDependencyInterface忽略ServletContextAware类型的属性注入,因为会通过WebApplicationContextServletContextAwareProcessor处理属性,这里有个疑问,ServletContext外的其他属性怎么办呢?也不需要自动注入吗?

4.3:onRefresh

在AbstractApplicationContext类中refresh方法中会调用一个空方法onRefresh,该方法的作用是供子类注册特殊场景下的bean,接下来我们看下web容器对应的ApplicationContext类通过该方法实现了什么功能。源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		// <202106060911>
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

<202106060911>处是创建web服务器,注意此时只是创建,还没有启动web服务器,具体参考4.3.1:createWebServer

4.3.1:createWebServer

源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
	// 获取webServer
	WebServer webServer = this.webServer;
	// 获取servletcontext
	ServletContext servletContext = getServletContext();
	// webserver为null,servletcontext也为null时
	if (webServer == null && servletContext == null) {
		// <202106061108>
		ServletWebServerFactory factory = getWebServerFactory();
		// <202106061208>
		this.webServer = factory.getWebServer(getSelfInitializer());
	}
	else if (servletContext != null) {
		...snip...
	}
	// 初始化PropertySource
	initPropertySources();
}

<202106061108>处获取用于创建webserver的工厂对象,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getWebServerFactory
protected ServletWebServerFactory getWebServerFactory() {
	// 从IOC容器中获取类型ServletWebServerFactory的的spring bean的名称
	// 的数组,bean是在org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration类中配置的,如下:
	/*
	@Configuration
	class ServletWebServerFactoryConfiguration {
	
		@Configuration
		@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
		@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
		static class EmbeddedTomcat {
	
			@Bean
			public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
				return new TomcatServletWebServerFactory();
			}
	
		}
	}
	*/
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
	// bean的个数不是1个就抛出异常,0个和大于1个时异常信息稍有不同
	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
				+ "ServletWebServerFactory bean.");
	}
	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
				+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}
	// 执行到此处,说明只有一个类型为ServletWebServerFactory的bean
	// 直接根据bean名称从spring IOC容器中获取spring bean对象,并返回
	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

<202106061208>处是通过webserver的工厂对象,获取webserver实例,其中的getSelfInitializer(),主要是返回ServletContextInitializer对象,用于设置内置web容器的servletcontext对象,在3:springboot如何集成spring MVC中已经详细分析过了,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getSelfInitializer
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	// 匿名实现类,将this作为接口方法的参数,并调用其selfInitialize方法
	/*
	return new ServletContextInitializer() {
		@Override
		public void onStartup(ServletContext servletContext) throws ServletException {
			selfInitialize(servletContext);
		}
	};
	*/
	// 这种写法可以有效提高生产力,值得在工作中使用
	return this::selfInitialize;
}

关于selfInitialize方法具体看4.3.2:selfInitialize

4.3.2:selfInitialize

源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {
	// <202106061603>
	prepareWebApplicationContext(servletContext);
	// 忽略
	registerApplicationScope(servletContext);
	// 忽略
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	// 逐个调用ServletContextInitializer,比如DispatcherServlet
	// 的注册就是通过这里的方法调用完成的,这个我们在"3:springboot如何集成spring MVC"
	// 已经分析过了
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

<202106061603>处是注册ROOT IOC容器到servletcontext中,该处执行的逻辑和常规springMVC程序中ContextLoaderListener执行的操作类似,都是IOC容器和servletcontext的关联,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#prepareWebApplicationContext
protected void prepareWebApplicationContext(ServletContext servletContext) {
	// 获取ROOT IOC 容器
	Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	// 如果是IOC容器不为空
	if (rootContext != null) {
		// 如果是已存在的和当前的相等
		if (rootContext == this) {
			// 是否多个ServletContextInititalizer导致重复的异常信息提示
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - "
							+ "check whether you have multiple ServletContextInitializers!");
		}
		// 已存在,且不是当前的,则直接return
		return;
	}
	Log logger = LogFactory.getLog(ContextLoader.class);
	// 该日志在springboot程序启动时会看到,打印初始化内嵌web应用的IOC容器开始信息
	// ,是springboot启动过程中的重要节点
	servletContext.log("Initializing Spring embedded WebApplicationContext");
	try {
		// 设置IOC容器到servletcontext中
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
					+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		// 调用servletcontext属性的写方法设置到全局变量
		setServletContext(servletContext);
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - getStartupDate();
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

4.4:finishRefresh

源码:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh
@Override
protected void finishRefresh() {
	super.finishRefresh();
	// <202106061625>
	WebServer webServer = startWebServer();
	// 启动成功,发布事件
	if (webServer != null) {
		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
	}
}

<202106061625>处是启动web服务器,源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServer
private WebServer startWebServer() {
	// 获取web服务器对象WebServer
	WebServer webServer = this.webServer;
	if (webServer != null) {
		// <202106061632>
		// 启动
		webServer.start();
	}
	// 返回
	return webServer;
}

<202106061632>处执行完毕后会输出如下的日志信息,也是日志信息中比较关键的,如下图:
在这里插入图片描述

4.5:onClose

源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onClose
@Override
protected void onClose() {
	super.onClose();
	// <202106061643>
	stopAndReleaseWebServer();
}

<202106061643>处源码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#stopAndReleaseWebServer
private void stopAndReleaseWebServer() {
	WebServer webServer = this.webServer;
	if (webServer != null) {
		try {
			webServer.stop();
			this.webServer = null;
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值