SpringBoot对于嵌入式Servlet容器
之前一直是讲的Spring对servlet web的支持,接下来说springboot。
关于嵌入式servlet容器,springboot默认使用的是tomcat,关于嵌入式容器和非嵌入式容器本质上是没有区别的,那在springboot环境下,做出了几个调整:
不支持web.xml:使用RegistrationBean或@Bean
不支持ServletContainerInitializer接口:改用ServletContextInitializer
注解驱动如@WebServlet有限制:不会自动扫描,需要@ServletComponentScan
实现一下:
加依赖,新建一个子模块,把之前的模块加进来用
<!-- 引入spring-servlet子模块作为依赖 -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-servlet</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
/**
* SpringBoot Servlet 引导类
*/
@EnableAutoConfiguration
@ServletComponentScan(basePackages = "com.haozi.servlet")
public class SpringBootServletBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletBootstrap.class , args);
}
}
这里先说的是第三种,springboot在启动的时候不会自动扫描servlet,所以用@ServletComponentScan来手动配置。
运行一下看控制台
之前配置的/asyncservlet就被映射上来了。
但这样会有局限性,如果我想把之前的asyncservlet运用在不是"/asyncservlet",或者说别的url也可以有这个上下文怎么办?
这就是第二种方式了
/**
* SpringBoot Servlet 引导类
*/
@EnableAutoConfiguration
@ServletComponentScan(basePackages = "com.haozi.servlet")
public class SpringBootServletBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletBootstrap.class , args);
}
@Bean
public ServletContextInitializer servletContextInitializer(){
return servletContext -> {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
FilterRegistration.Dynamic registration = servletContext.addFilter("filter", filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST) , false , "/hhhh");
};
}
}
这样运行起来后, 如果有/hhhh的请求,就会匹配到filter了。
但通过debug可以看出,上面的方法虽然都加载了filter,但是实际运行的时候并没有加载我们自己的,而是用的DispatcherServlet,那么接下来用第一种@Bean试试
/**
* SpringBoot Servlet 引导类
*/
@EnableAutoConfiguration
public class SpringBootServletBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletBootstrap.class , args);
}
@Bean
public AsyncServlet asyncServlet(){
return new AsyncServlet();
}
@Bean
public ServletContextInitializer servletContextInitializer(){
return servletContext -> {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
FilterRegistration.Dynamic registration = servletContext.addFilter("filter", filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST) , false , "/");
};
}
}
启动后可以发现,asyncServlet注册进来了
但是运行还是没有用,原因是因为加载顺序,DispatcherServlet在前面,所以在同一个路径的时候,会加载前面的,但通过order把顺序提高,还是没用,所以这种方式需要改造一下:
/**
* SpringBoot Servlet 引导类
*/
@EnableAutoConfiguration
public class SpringBootServletBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletBootstrap.class , args);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ServletRegistrationBean asyncServletRegistrationBean(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new AsyncServlet() , "/");
return registrationBean;
}
}
从日志就可以看出,这种方式就提前了,在请求就可以进我们自己的方法而不是执行DispatcherServlet了。
接下来解决几个问题
第一个,我们自定的ServletContextInitializer没有日志?而ServletRegistrationBean有日志?
第二个,为什么ServletRegistrationBean优先级高于DispatcherServlet时,可以把DispatcherServlet覆盖掉?
第三个,ServletComponentScan如何识别filter、listener等annotation的?
先看ServletContextInitializer接口,里面只有一个onStartup,通过搜索可以看到,他被tomcat调用过
打开发现是ServletContextInitializer数组循环启动,而这个数组是final类型,所以只有在构造阶段赋值
那么找一下哪个地方调了这个构造器
接下来一层一层往上找,最终可以找到,框起来的看名字就知道获取ServletContextInitializer的
打个断点debug
从这里可以看到,第一个就是AsyncServlet,因为排过序了,那在哪排的序?继续找sortedList
可以看到,在写入list之前,确实排过一次序这就解释了第二个问题。
再看列表,这个类为ServletContextInitializerBean,里面包含了ServletRegistrationBean和FilterRegistrationBean,说明会有一个层次关系
通过ServletRegistrationBean找父类,一直往上,最终可以找到RegistrationBean,这个类实现了ServletContextInitializer,那么再看RegistrationBean的子类就包含了下面三种
接下来我们通过搜索源码可以知道是通过上面框里的三个子类分别实现的,所以因为我们自己的并没有输出,所以没有日志。
关于最后一个问题,看ServletComponentScan,里面import了一个ServletComponentScanRegistrar类,进去之后会找到ServletComponentRegisteringPostProcessor,进去
可以看到,这里有三种handler
在接下来的扫描方法中,会有分别根据三中类型dohandler的操作
在接下来不往下贴了,三种handler都会通过自己的dohandle生成各自类型的bean,因此第三个问题解决。