Marco's Java【SpringBoot进阶(二) 之 注册Web三大组件及内嵌Web服务器加载原理】 - Go语言中文社区

Marco's Java【SpringBoot进阶(二) 之 注册Web三大组件及内嵌Web服务器加载原理】


前言

从现在开始的SpringBoot学习就是咱们的分水岭了,为什么这么讲呢?
因为之前咱们学习的基本上都是SpringBoot内置的东西,我们直接去用,或者说写个配置文件就完事儿了。比如说DispatcherServlet不用我们自己去配置,内部资源访问解析器InternalViewResolver也是默认帮我们配置了,连参数都不用改,但是接下来我们可能会涉及到自定义Servlet,Filter,Listener等情况,但是以往的配置在SpringBoot中肯定是行不通的,这个时候就需要我们学会使用SpringBoot注册咱们自定义的注册Web三大组件。

注册Web三大组件

学习过Servlet的朋友对Web三大组件应该很清楚了,就是我们上面提到的Servlet,Filter以及Listener。这样,虽然不清楚怎么让自定义的组件生效,咱们还是先分别创建几个自定义类再说吧。

接下来的三大组件创建都特别简单,就没啥好说的啦

第一步:创建自定义Servlet

package com.marco.controller;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserServlet extends HttpServlet{

	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		PrintWriter writer = resp.getWriter();
		writer.print("success");
		writer.flush();
		writer.close();
	}
}

第二步:创建自定义Filter

package com.marco.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class SessionFilter implements Filter{

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("doFilter");
		chain.doFilter(request, response);
	}
}

第三步:创建自定义Listener

package com.marco.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class AppListener implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("###@########################");
		System.out.println("servlet context start up");
		System.out.println("###@########################");
	}
}	

第四步:创建自定义Servlet
虽然把简单的事儿做完了,但是该来的还是要来,就像生活和工作一样,很多事情,逃避是没得用的!第一时间想想怎么去解决问题比磨磨蹭蹭的拖延下去后面再去解决,显然前者更理智。
所以… 我的文章还是得连载下去啊,不能偷懒,一偷懒说不定就没人看了…

好啦,依然我不知道怎么去SpringBoot注册,找个模板先看看还不行吗,就拿咱们之前分析过多次得DispatcherServlet为例。

DispatcherServlet的源码也看了不下两遍吧?这一步就是创建Servlet的意思,显然咱们已经创建好了
在这里插入图片描述
接下来我们再看注册方法,这个方法的意思是,先创建一个DispatcherServlet的注册器,并注入DispatcherServlet对象,接着给DispatcherServlet设置属性值。
在这里插入图片描述
接着咱们抽丝剥茧,会发现DispatcherServletRegistrationBean注册器的父类是ServletRegistrationBean,并且泛型是DispatcherServlet
在这里插入图片描述
那我们能不能模拟它的写法呢?咱们现在自定义的Servlet是UserServlet,那么根据前面的推测它的注册器应该就是ServletRegistrationBean<UserServlet>对吧?顺着这个思路我们尝试着先写一下。

@Configuration//代表这是一个配置类
@ConditionalOnWebApplication(type = Type.SERVLET)//表示需要在servlet的运行环境下生效
public class WebComponentAutoConfiguration {
	
	@Bean
	public ServletRegistrationBean<UserServlet> getServletRegistration() {
		UserServlet userServlet = new UserServlet();
		//创建Servlet注册器对象
		ServletRegistrationBean<UserServlet> registrationBean = new ServletRegistrationBean<>();
		//注入自定义servlet
		registrationBean.setServlet(userServlet);
		//注入映射路径
		registrationBean.addUrlMappings("/user");
		return registrationBean;
	}
}

测试结果也是没有问题的。
在这里插入图片描述
那么顺着这个思路,咱们的过滤器和监听器也可以通过这种方式来创建了。只不过需要注意的是注册器和监听器的注册器和Servlet就不一样了,不过咱们顺藤摸瓜,也能找到过滤器和监听器对应的注册器了
在这里插入图片描述
注册Web的三大组件示例的完整代码如下

package com.marco.controller;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.marco.filter.SessionFilter;
import com.marco.listener.AppListener;

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class WebComponentAutoConfiguration {
	
	@Bean
	public ServletRegistrationBean<UserServlet> getServletRegistration() {
		UserServlet userServlet = new UserServlet();
		//创建Servlet注册器对象
		ServletRegistrationBean<UserServlet> registrationBean = new ServletRegistrationBean<>();
		//注入自定义servlet
		registrationBean.setServlet(userServlet);
		//注入映射路径
		registrationBean.addUrlMappings("/user");
		return registrationBean;
	}
	
	@Bean
	public FilterRegistrationBean<SessionFilter> getFilterRegistrationBean() {
		//创建过滤器
		SessionFilter sessionFilter = new SessionFilter();
		//创建过滤器注册器对象
		FilterRegistrationBean<SessionFilter> registrationBean = new FilterRegistrationBean<>();
		//注入自定义filter
		registrationBean.setFilter(sessionFilter);
		//设置映射的servlet名称为dispatcherServlet
		registrationBean.setName("dispatcherServlet");
		return registrationBean;
	}
	
	@Bean
	public ServletListenerRegistrationBean<AppListener> getServletListenerRegistrationBean() {
		//创建Listener
		AppListener appListener = new AppListener();
		//创建监听器注册器对象
		ServletListenerRegistrationBean<AppListener> registrationBean = new ServletListenerRegistrationBean<>();
		//注入自定义监听器
		registrationBean.setListener(appListener);
		return registrationBean;
	}
}

另外两个Web组件的测试结果也没有问题~
在这里插入图片描述


内嵌Web服务器加载原理

虽然咱们使用SpringBoot有些时了,之前在入门的第二节讲SpringBoot启动原理的时候也提到过SpringBoot能够在不依赖外部的tomcat的情况下就可以直接运行,并搭建Web项目的原因是SpringBoot有内嵌的tomcat服务器。
可是我们知其然不知其所以然,对SpringBoot如何在程序运行的时候启动tomcat,以及什么时候启动tomcat还是了解的不够透彻。
因此咱们本节就专门针对内嵌Web服务器加载原理进行分析。前往SpringBoot底层的车要开动了,大家抓好咯~

咱们还是从SpringBoot洞穴的入口@SpringBootApplication先进去,之前演示过很多遍,这里就不放图啦。一样的,还是通过@EnableAutoConfiguration找到AutoConfigurationImportSelector.class
在这里插入图片描述
然后还是根据selectImports(AnnotationMetadata annotationMetadata)找到spring.factories文件,加载自动配置类,如果对配置类的加载的原理不熟悉的朋友,可以翻阅之前解析加载原理的博文
Marco’s Java【SpringBoot入门(三) 之 SpringBoot的两种配置文件语法以及配置文件加载原理】
在这里插入图片描述
接下来加载的108个配置类中有我们的前端控制器对应的自动装配类DispatcherServletAutoConfiguration(这家伙出镜率很高啊!),还是打开DispatcherServletAutoConfiguration,本次的侧重点在@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)这个注解上。
在这里插入图片描述
之前我们讲过,这个注解的意思就是DispatcherServlet的自动配置类想要生效,就必须在ServletWebServerFactoryAutoConfiguration运行创建之后才能生效。既然这样,我们来点进去看看,究竟是何方神圣?

@Configuration//声明这一个自动配置类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)//配置顺序,HIGHEST_PRECEDENCE证明优先级很高
@ConditionalOnClass(ServletRequest.class))//必须有ServletRequest类
@ConditionalOnWebApplication(type = Type.SERVLET))//必须依赖servlet/web运行环境
@EnableConfigurationProperties(ServerProperties.class))//配置服务器的相关属性
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,//内嵌tomcat
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,//内嵌jetty
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })//内嵌undertow
public class ServletWebServerFactoryAutoConfiguration {

}

分析完上面的代码,可见ServletWebServerFactoryAutoConfiguration配置类必须依赖于Web环境,并且引入三个内嵌的服务器类EmbeddedTomcat、EmbeddedJetty以及EmbeddedUndertow

Servlet服务器 WebServer模型接口 WebServer工厂实现类
Tomcat TomcatWebServer TomcatServletWebServerFactory
Jetty JettyWebServer JettyServletWebServerFactory
Undertow UndertowWebServer UndertowServletWebServerFactory

有了这些信息之后,咱们再来看这个配置类的 “说明书” ServerProperties,发现里面的属性相当多!
在这里插入图片描述
根据我们之前的经验,这些属性都是可以在properties文件或者yml中进行修改并重新配置的,就像下面这样。

server:
  address: # Network address to which the server should bind to.
  servlet:
    context-path: /marco #springboot2.0以上的配置
    session:
      timeout: 2000
  port: 8082 #配置程序端口,默认为8080
  tomcat:
    uri-encoding: UTF-8 # 配置编码  

这里稍微提一提servlet.context-path: /marco,之前咱们访问请求的url不是没有项目名称么?原因是我们的项目一经创建就被SpringBoot存放在ROOT目录下,因此不需要项目名称也能够访问,如果硬要在url地址栏上加上项目名,那么重新配置servlet.context-path就可以啦~ 所以说SpringBoot还是挺人性化的。

不扯远啦,因为咱们使用的是tomcat服务器,因此我们就重点分析内嵌tomcat的启动原理,其他两个服务器的启动原因也都相似,所以我们点击去EmbeddedTomcat.class谈谈究竟。
在这里插入图片描述
发现上面的代码就new了一个TomcatServletWebServerFactory,继续跟进!
找到下面的getWebServer(ServletContextInitializer... initializers)方法,这个方法的作用就是创建tomcat,至于里边的set方法,都是一些tomcat的初始化设置,我就不深入的分析啦~
在这里插入图片描述
getWebServer(ServletContextInitializer... initializers)的最后调用下面这个方法
在这里插入图片描述
看到这里好像有点眉目了吧?这个方法就是注入刚才在getWebServer创建的tomcat对象,autoStart就是自动启动的意思,当getPort() >= 0的时候tomcat会自动启动,反之getPort() < 0时,不会自动启动
在这里插入图片描述
根据AbstractConfigurableWebServerFactory中的配置可以看到port默认是8080,因此我们的tomcat服务器默认是自动启动的
在这里插入图片描述
终于到最后一步啦,initialize()本质上就是启动了咱们的tomcat服务器,因此当我们运行启动SpringBoot项目时,会自动的启起内嵌的tomcat服务器!

private void initialize() throws WebServerException {
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	synchronized (this.monitor) {
		try {
			addInstanceIdToEngineName();

			Context context = findContext();
			context.addLifecycleListener((event) -> {
				if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
					// Remove service connectors so that protocol binding doesn't
					// happen when the service is started.
					//移除服务连接器,当服务启动时不进行协议绑定
					removeServiceConnectors();
				}
			});
			//启动tomcat
			// Start the server to trigger initialization listeners
			this.tomcat.start();
			//...omitted
	}
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44698119/article/details/98877468
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-13 14:59:14
  • 阅读 ( 1607 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢