社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
从现在开始的SpringBoot学习就是咱们的分水岭了,为什么这么讲呢?
因为之前咱们学习的基本上都是SpringBoot内置的东西,我们直接去用,或者说写个配置文件就完事儿了。比如说DispatcherServlet不用我们自己去配置,内部资源访问解析器InternalViewResolver也是默认帮我们配置了,连参数都不用改,但是接下来我们可能会涉及到自定义Servlet,Filter,Listener等情况,但是以往的配置在SpringBoot中肯定是行不通的,这个时候就需要我们学会使用SpringBoot注册咱们自定义的注册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组件的测试结果也没有问题~
虽然咱们使用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
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!