本系列文章都是基于SpringBoot2.2.5.RELEASE
SpringBoot使用内嵌web容器启动的时候并没有遵守 Servlet
的规范,无论是 web.xml
中的配置,还是 Servlet3.0 中的 ServletContainerInitializer
和 Spring Boot 的加载流程都没有太大的关联。在SpringBoot中新增Servlet
、Filter
、Listener
有三种方式。
// Servlet实例
@WebServlet(name = "MyServlet", urlPatterns = "/myServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("MyServlet
");
}
}
// Filter实例
@WebFilter(urlPatterns = "/myServlet")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter......init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter......doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter......destroy");
}
}
// Listener实例
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
System.out.println("MyServletContextListener......contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
System.out.println("MyServletContextListener......contextDestroyed");
}
}
最后在启动类加上@ServletComponentScan
注解
@SpringBootApplication
@ServletComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
简单看一下@ServletComponentScan注解的作用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {
......
}
使用@Import注解指定ServletComponentScanRegistrar.class往容器中注入组件。
// ServletComponentScanRegistrar.class
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取需要扫描的包路径
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 判断容器中是否存在 servletComponentRegisteringPostProcessor
if (registry.containsBeanDefinition(BEAN_NAME)) {
// 存在就更新
updatePostProcessor(registry, packagesToScan);
}
else {
// 不存在就往容器中注入类型为 ServletComponentRegisteringPostProcessor类型的组件
addPostProcessor(registry, packagesToScan);
}
}
private void updatePostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
ValueHolder constructorArguments = definition.getConstructorArgumentValues().getGenericArgumentValue(Set.class);
@SuppressWarnings("unchecked")
Set<String> mergedPackages = (Set<String>) constructorArguments.getValue();
mergedPackages.addAll(packagesToScan);
constructorArguments.setValue(mergedPackages);
}
private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(ServletComponentScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
if (packagesToScan.isEmpty()) {
// 如果basePackages和basePackageClasses都没有值
// 则默认只扫描启动类的包路径
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
因此,@ServletComponentScan
的作用其实就是往容器中注入了ServletComponentRegisteringPostProcessor
,这个组件实现了BeanFactoryPostProcessor
。查看ServletComponentRegisteringPostProcessor.postProcessBeanFactory()
方法,这个方法比较简单,就是去包路径下找到被@WebServlet
、@WebFilter
、@WebListener
注解标注的类,然后将他们分别封装成ServletRegistrationBean
、FilterRegistrationBean
、ServletListenerRegistrationBean
类型的组件注入到容器中。
因为ServletRegistrationBean
、FilterRegistrationBean
、ServletListenerRegistrationBean
都继承了RegistrationBean
,而RegistrationBean
实现了ServletContextInitializer
接口,因此在web容器启动后,可以通过ServletContext
动态地注册Servlet
、Filter
、Listener
。
// 注册一个 Servlet
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(new MyServlet());
registrationBean.setName("myServlet");
registrationBean.addUrlMappings("/myServlet");
return registrationBean;
}
// 注册一个 Filter
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setName("myFilter");
registrationBean.addUrlPatterns("/myServlet");
return registrationBean;
}
// 注册一个Listener
@Bean
public ServletListenerRegistrationBean myServletContextListener() {
ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean();
registrationBean.setListener(new MyServletContextListener());
return registrationBean;
}
以Servlet
为例
@Component
public class MyServletContextInitializer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic dynamic = servletContext.addServlet("MyServlet",new MyServlet());
dynamic.addMapping("/myServlet");
}
}