SpringBoot2.2.x(三)注册Servlet组件

本系列文章都是基于SpringBoot2.2.5.RELEASE

SpringBoot使用内嵌web容器启动的时候并没有遵守 Servlet 的规范,无论是 web.xml 中的配置,还是 Servlet3.0 中的 ServletContainerInitializer 和 Spring Boot 的加载流程都没有太大的关联。在SpringBoot中新增ServletFilterListener有三种方式。

Servlet 3.0 注解+@ServletComponentScan

// 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注解标注的类,然后将他们分别封装成ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean类型的组件注入到容器中。

因为ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean都继承了RegistrationBean,而RegistrationBean实现了ServletContextInitializer接口,因此在web容器启动后,可以通过ServletContext动态地注册ServletFilterListener

RegistrationBean注解

// 注册一个 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;
}

实现ServletContextInitializer

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");
    }
}

你可能感兴趣的:(SpringBoot)