zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Springboot注册Servlet几种方式你都知道?内部实现原理解析

2023-03-09 22:04:27 时间

环境:springboot2.3.9.RELEASE

1 Servlet注册

方式1:

在配置类(启动类)上添加@ServletComponentScan注解

  1. @SpringBootApplication 
  2. @ServletComponentScan 
  3. public class SpringBootComprehensiveApplication 

Servlet类上添加@WebServlet注解接口

  1. @WebServlet("/hello"
  2. public class MyServlet extends HttpServlet { 
  3. }     

对应的Filter, Linstener有:@WebFilter, @WebListener

方式2:

通过向IOC容器添加ServletRegistrationBean方式;该种方式可以在Servlet中注入其它Bean或者读取application.properties配置信息等。对应的filter, Listener有对应的bean;FilterRegistrationBean,ServletListenerRegistrationBean

  1. @Bean 
  2. public ServletRegistrationBean<MyServlet> servlet() { 
  3.   ServletRegistrationBean<MyServlet> servlet = new ServletRegistrationBean<>(new MyServlet()) ; 
  4.   servlet.addUrlMappings("/hello") ;  
  5.   return servlet ; 

 方式3:

动态注册Servlet

  1. @Component 
  2. public class DynamicRegServlet implements ServletContextInitializer { 
  3.  
  4.   @Override 
  5.   public void onStartup(ServletContext servletContext) throws ServletException { 
  6.     ServletRegistration initServlet = servletContext.addServlet("myServlet", MyServlet.class) ; 
  7.     initServlet.addMapping("/hello") ; 
  8.   } 
  9.  

 该种方式是利用的Servlet3.0开始才有的功能,通过SPI技术在容器启动的时候做一些初始化工作,比如注册Servlet等。在Servle规范中通过

ServletContainerInitializer实现该功能。

该Servlet规范的开发流程如下;

2、配置

ServletContainerInitializer

在src/META-INF/services下新建

javax.servlet.ServletContainerInitializer文件,文件内容为ServletContainerInitializer接口的实现类(完整的包名+类名)如下:

  1. com.pack.container.config.CustomServletInitializer 

CustomServletInitializer类

  1. @HandlesTypes({ScoreWebInit.class}) 
  2. public class CustomServletInitializer implements ServletContainerInitializer { 
  3.   @Override 
  4.   public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { 
  5.     c.forEach(web -> { 
  6.       try { 
  7.         ((ScoreWebInit)web.newInstance()).pay() ; 
  8.       } catch (InstantiationException e) { 
  9.         e.printStackTrace(); 
  10.       } catch (IllegalAccessException e) { 
  11.         e.printStackTrace(); 
  12.       } 
  13.     }); 
  14.     ServletRegistration.Dynamic reg = ctx.addServlet("MyServlet", com.pack.servlet.MyServlet.class) ; 
  15.     reg.setLoadOnStartup(1) ; 
  16.     reg.addMapping("/hello") ; 
  17.   } 

注意:@HandlesTypes该注解会把属性value配置的值(ScoreWebInt.class)对应的所有类都收集上然后在onStartup方法中的Set 集合中应用。

在spring-web-xxxx.jar下就有通过该技术实现的相应功能。

3 扫描Servlet实现原理

在方式1中的实现原理就是扫描类路径下所有@WebServlet,@WebFilter,@WebListener。找到所有的类后再通过方式2的方式进行注册。下面将核心源码贴出

3.1 导入核心类

  1. // 该注解如果没有配置basePackages或者basePackageClasses属性,那么会读取当前添加@ServletComponentScan注解的类的包路径进行扫描。 
  2. @Target(ElementType.TYPE) 
  3. @Retention(RetentionPolicy.RUNTIME) 
  4. @Documented 
  5. @Import(ServletComponentScanRegistrar.class) 
  6. public @interface ServletComponentScan { 
  7. // 注册BeanDefinition 
  8. class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar{ 
  9.   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
  10.     Set<String> packagesToScan = getPackagesToScan(importingClassMetadata); 
  11.     if (registry.containsBeanDefinition(BEAN_NAME)) { 
  12.       updatePostProcessor(registry, packagesToScan); 
  13.     } else { 
  14.       // 当前容器中没有对应的Bean时执行该方法   
  15.       addPostProcessor(registry, packagesToScan); 
  16.     } 
  17.   } 

3.2 注册BeanFactory 处理器

ServletComponentScanRegistrar最核心的功能就是注册BeanFactoryPostProcessor

  1. class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar{ 
  2.   private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) { 
  3.     GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 
  4.     beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class); 
  5.     beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan); 
  6.     beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 
  7.     registry.registerBeanDefinition(BEAN_NAME, beanDefinition); 
  8.   } 

3.3 实例化扫描组件

进入

ServletComponentRegisteringPostProcessor类中首先这个类有个static代码块

  1. class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware { 
  2.   private static final List<ServletComponentHandler> HANDLERS; 
  3.   static { 
  4.     List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>(); 
  5.     servletComponentHandlers.add(new WebServletHandler()); 
  6.     servletComponentHandlers.add(new WebFilterHandler()); 
  7.     servletComponentHandlers.add(new WebListenerHandler()); 
  8.     HANDLERS = Collections.unmodifiableList(servletComponentHandlers); 
  9.   } 
  10. }     

这段代码分别是添加相应Servlet, Filter, Listener的处理句柄,分别处理@WebServlet,@WebFilter,@WebListener 注解。

查看WebServletHandler

  1. class WebServletHandler extends ServletComponentHandler { 
  2.   WebServletHandler() { 
  3.     super(WebServlet.class); 
  4.   } 
  5.   // 看到该方法也就十分清楚了最终找到所有的Class以后,通过ServletRegistrationBean进行注册为Bean 
  6.   @Override 
  7.   public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,BeanDefinitionRegistry registry) { 
  8.     BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServletRegistrationBean.class); 
  9.     builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported")); 
  10.     builder.addPropertyValue("initParameters", extractInitParameters(attributes)); 
  11.     builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup")); 
  12.     String name = determineName(attributes, beanDefinition); 
  13.     builder.addPropertyValue("name"name); 
  14.     builder.addPropertyValue("servlet", beanDefinition); 
  15.     builder.addPropertyValue("urlMappings", extractUrlPatterns(attributes)); 
  16.     builder.addPropertyValue("multipartConfig", determineMultipartConfig(beanDefinition)); 
  17.     registry.registerBeanDefinition(name, builder.getBeanDefinition()); 
  18.   } 
  19.   // other code 
  20. }     
  21. // 父类ServletComponentHandler;在父类总添加相应的过滤器(分别查找相应注解的类,@WebServlet等。) 
  22. abstract class ServletComponentHandler { 
  23.   private final Class<? extends Annotation> annotationType; 
  24.   private final TypeFilter typeFilter; 
  25.   protected ServletComponentHandler(Class<? extends Annotation> annotationType) { 
  26.     this.typeFilter = new AnnotationTypeFilter(annotationType); 
  27.     this.annotationType = annotationType; 
  28.   } 

 接下来执行BeanFactoryPostProcessor对应的回调方法了

  1. class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware { 
  2.   @Override 
  3.   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
  4.     if (isRunningInEmbeddedWebServer()) { 
  5.       ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider(); 
  6.       for (String packageToScan : this.packagesToScan) { 
  7.         scanPackage(componentProvider, packageToScan); 
  8.       } 
  9.     } 
  10.   } 

createComponentProvider方法进行创建扫描相应符合条件的Bean扫描类

  1. private ClassPathScanningCandidateComponentProvider createComponentProvider() { 
  2.   ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false); 
  3.   componentProvider.setEnvironment(this.applicationContext.getEnvironment()); 
  4.   componentProvider.setResourceLoader(this.applicationContext); 
  5.   for (ServletComponentHandler handler : HANDLERS) { 
  6.     componentProvider.addIncludeFilter(handler.getTypeFilter()); 
  7.   } 
  8.   return componentProvider; 

在该方法中为当前的

ClassPathScanningCandidateComponentProvider类扫描设置过滤器;过滤器在上面的static静态代码块中已经设置了WebServletHandler,WebFilterHandler,WebListenerHandler在父类中分别创建不同注解的new AnnotationTypeFilter(annotationType)过滤类。

创建完类扫描类以后开始扫描通过该类扫描相应包(路径)下的所有类文件 检查是否有对应的注解。

3.4 查找及注册Bean

  1. class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware { 
  2.   private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) { 
  3.     for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) { 
  4.       if (candidate instanceof AnnotatedBeanDefinition) { 
  5.         for (ServletComponentHandler handler : HANDLERS) { 
  6.           handler.handle(((AnnotatedBeanDefinition) candidate), (BeanDefinitionRegistry) this.applicationContext); 
  7.         } 
  8.       } 
  9.     } 
  10.   } 

findCandidateComponents方法查找候选的(符合条件)的类并实例化为BeanDefinition对象。

方法执行链findCandidateComponents ---》scanCandidateComponents

在scanCandidateComponents方法中查找符合条件的类,然后实例化为BeanDefinition

  1. public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { 
  2.   private Set<BeanDefinition> scanCandidateComponents(String basePackage) { 
  3.     Set<BeanDefinition> candidates = new LinkedHashSet<>(); 
  4.     MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); 
  5.     if (isCandidateComponent(metadataReader)) { 
  6.       ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 
  7.       sbd.setSource(resource); 
  8.       if (isCandidateComponent(sbd)) { 
  9.         candidates.add(sbd); 
  10.       } 
  11.     } 
  12.     return candidates; 
  13.   } 
  14.   protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { 
  15.     for (TypeFilter tf : this.excludeFilters) { 
  16.       if (tf.match(metadataReader, getMetadataReaderFactory())) { 
  17.         return false
  18.       } 
  19.     } 
  20.     for (TypeFilter tf : this.includeFilters) { 
  21.       if (tf.match(metadataReader, getMetadataReaderFactory())) { 
  22.         return isConditionMatch(metadataReader); 
  23.       } 
  24.     } 
  25.     return false
  26.   } 

在第二个for开始匹配所有的类是否有相关的注解。如果匹配上就相应的创建BeanDefinition对象放入集合Set中。

查找到所有的类以后分别调用相应的Web*Handler(ServletComponentHandler)进行注册Bean。

返回到上面的scanPackage方法中执行handler.handle方法。

  1. abstract class ServletComponentHandler { 
  2.   void handle(AnnotatedBeanDefinition beanDefinition, BeanDefinitionRegistry registry) { 
  3.     Map<String, Object> attributes = beanDefinition.getMetadata().getAnnotationAttributes(this.annotationType.getName()); 
  4.     if (attributes != null) { 
  5.       doHandle(attributes, beanDefinition, registry); 
  6.     } 
  7.   } 

doHandle方法分别在子类(WebServletHandler,WebFilterHandler,WebListenerHandler)中实现,如上面已经提到的WebServletHandler类的doHandler方法。

到这里我们知道了注解的方式最终也是被注册为ServletRegistrationBean 实例。那这个ServletRegistrationBean又是如何被容器(Tomcat)所感知的呢?

3.5 Tomcat注册Servlet

在Web容器下 BeanFactory使用的是

AnnotationConfigServletWebServerApplicationContext

Spring IOC容器核心方法是refresh方法

  1. public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { 
  2.   public void refresh() throws BeansException, IllegalStateException { 
  3.     synchronized (this.startupShutdownMonitor) { 
  4.       try { 
  5.         // Initialize other special beans in specific context subclasses. 
  6.         onRefresh(); 
  7.       } catch (BeansException ex) { 
  8.         throw ex; 
  9.       } finally { 
  10.         resetCommonCaches(); 
  11.       } 
  12.     } 
  13.   } 
  14. }     

这里只留了onRefresh方法,进入该方法:

onRefresh会进入到子类的方法

  1. public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { 
  2.   protected void onRefresh() { 
  3.     super.onRefresh(); 
  4.     try { 
  5.       createWebServer(); 
  6.     } catch (Throwable ex) { 
  7.       throw new ApplicationContextException("Unable to start web server", ex); 
  8.     } 
  9.   } 
  10. }     

 进入createWebServer方法

  1. public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { 
  2.   private void createWebServer() { 
  3.     WebServer webServer = this.webServer; 
  4.     ServletContext servletContext = getServletContext(); 
  5.     if (webServer == null && servletContext == null) { 
  6.       ServletWebServerFactory factory = getWebServerFactory(); 
  7.       this.webServer = factory.getWebServer(getSelfInitializer()); 
  8.       getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer)); 
  9.       getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); 
  10.     } else if (servletContext != null) { 
  11.       try { 
  12.         getSelfInitializer().onStartup(servletContext); 
  13.       } catch (ServletException ex) { 
  14.         throw new ApplicationContextException("Cannot initialize servlet context", ex); 
  15.       } 
  16.     } 
  17.     initPropertySources(); 
  18.   } 
  19.  

这里会进入factory.getWebServer(getSelfInitializer())方法执行

进入getSelfInitializer()方法

  1. public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { 
  2.   private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { 
  3.     return this::selfInitialize; 
  4.   } 
  5.  
  6.   private void selfInitialize(ServletContext servletContext) throws ServletException { 
  7.     prepareWebApplicationContext(servletContext); 
  8.     registerApplicationScope(servletContext); 
  9.     WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); 
  10.     for (ServletContextInitializer beans : getServletContextInitializerBeans()) { 
  11.       beans.onStartup(servletContext); 
  12.     } 
  13.   } 

这里的for循环是遍历当前容器中所有ServletContextInitializer类型的Bean。ServletRegistrationBean就是继承ServletContextInitializer

到这里分别调用ServletContextInitializer的onStartup方法,进入onStartup方法:

  1. public abstract class RegistrationBean implements ServletContextInitializer, Ordered { 
  2.   @Override 
  3.   public final void onStartup(ServletContext servletContext) throws ServletException { 
  4.     String description = getDescription(); 
  5.     if (!isEnabled()) { 
  6.       logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); 
  7.       return
  8.     } 
  9.     register(description, servletContext); 
  10.   } 
  11.   // 在子类中实现   
  12.   protected abstract void register(String description, ServletContext servletContext);   

进入子类DynamicRegistrationBean

  1. public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean { 
  2.   @Override 
  3.   protected final void register(String description, ServletContext servletContext) { 
  4.     D registration = addRegistration(description, servletContext); 
  5.     if (registration == null) { 
  6.       logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); 
  7.       return
  8.     } 
  9.     // 配置Servlet Mapping相关的信息(在子类ServletRegistrationBean中实现的)   
  10.     configure(registration); 
  11.   } 
  12.   // 子类中实现 
  13.   protected abstract D addRegistration(String description, ServletContext servletContext); 

 进入子类ServletRegistrationBean

  1. public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> { 
  2.   @Override 
  3.   protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { 
  4.     String name = getServletName(); 
  5.     return servletContext.addServlet(name, this.servlet); 
  6.   } 

 到这里就是通过ServletContext来动态注册Servlet(Servilet3.0)。

这里返回了

ServletRegistration.Dynamic对象会继续执行configure方法配置urlMapping等信息。

  1. public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> { 
  2.   @Override 
  3.   protected void configure(ServletRegistration.Dynamic registration) { 
  4.     super.configure(registration); 
  5.     String[] urlMapping = StringUtils.toStringArray(this.urlMappings); 
  6.     if (urlMapping.length == 0 && this.alwaysMapUrl) { 
  7.       urlMapping = DEFAULT_MAPPINGS; 
  8.     } 
  9.     if (!ObjectUtils.isEmpty(urlMapping)) { 
  10.       registration.addMapping(urlMapping); 
  11.     } 
  12.     registration.setLoadOnStartup(this.loadOnStartup); 
  13.     if (this.multipartConfig != null) { 
  14.       registration.setMultipartConfig(this.multipartConfig); 
  15.     } 
  16.   } 

到此在Springboot环境下Servlet如何被注册到Servlet容器中就已经清晰了。这动态注册Servlet的相关API都是在Servlet3.0规范中才有的。