1. 程式人生 > 其它 >Springboot註冊Servlet幾種方式你都知道?內部實現原理解析

Springboot註冊Servlet幾種方式你都知道?內部實現原理解析

環境:springboot2.3.9.RELEASE


1 Servlet註冊

方式1:

在配置類(啟動類)上新增@ServletComponentScan註解

@SpringBootApplication
@ServletComponentScan
public class SpringBootComprehensiveApplication
}

Servlet類上新增@WebServlet註解介面

@WebServlet("/hello")
public class MyServlet extends HttpServlet {
}  
 

對應的Filter, Linstener有:@WebFilter, @WebListener

方式2:

通過向IOC容器新增ServletRegistrationBean方式;該種方式可以在Servlet中注入其它Bean或者讀取application.properties配置資訊等。對應的filter, Listener有對應的bean;FilterRegistrationBean,
ServletListenerRegistrationBean

@Bean
public ServletRegistrationBean<MyServlet> servlet() {
  ServletRegistrationBean<MyServlet> servlet = new ServletRegistrationBean<>(new MyServlet()) ;
  servlet.addUrlMappings("/hello") ; 
  return servlet ;
}

方式3:

動態註冊Servlet

@Component
public class DynamicRegServlet implements ServletContextInitializer {

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    ServletRegistration initServlet = servletContext.addServlet("myServlet", MyServlet.class) ;
    initServlet.addMapping("/hello") ;
  }

}

該種方式是利用的Servlet3.0開始才有的功能,通過SPI技術在容器啟動的時候做一些初始化工作,比如註冊Servlet等。在Servle規範中通過
ServletContainerInitializer實現該功能。

該Servlet規範的開發流程如下;

1、配置
ServletContainerInitializer

在src/META-INF/services下新建
javax.servlet.ServletContainerInitializer檔案,檔案內容為ServletContainerInitializer介面的實現類(完整的包名+類名)如下:

com.pack.container.config.CustomServletInitializer

CustomServletInitializer類

@HandlesTypes({ScoreWebInit.class})
public class CustomServletInitializer implements ServletContainerInitializer {
  @Override
  public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
    c.forEach(web -> {
      try {
        ((ScoreWebInit)web.newInstance()).pay() ;
      } catch (InstantiationException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    });
    ServletRegistration.Dynamic reg = ctx.addServlet("MyServlet", com.pack.servlet.MyServlet.class) ;
    reg.setLoadOnStartup(1) ;
    reg.addMapping("/hello") ;
  }
}

注意:@HandlesTypes該註解會把屬性value配置的值(ScoreWebInt.class)對應的所有類都收集上然後在onStartup方法中的Set<Class>集合中應用。

在spring-web-xxxx.jar下就有通過該技術實現的相應功能。

2 掃描Servlet實現原理

在方式1中的實現原理就是掃描類路徑下所有@WebServlet,@WebFilter,@WebListener。找到所有的類後再通過方式2的方式進行註冊。下面將核心原始碼貼出

2.1 匯入核心類

// 該註解如果沒有配置basePackages或者basePackageClasses屬性,那麼會讀取當前新增@ServletComponentScan註解的類的包路徑進行掃描。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {
}
// 註冊BeanDefinition
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar{
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
    if (registry.containsBeanDefinition(BEAN_NAME)) {
      updatePostProcessor(registry, packagesToScan);
    } else {
      // 當前容器中沒有對應的Bean時執行該方法  
      addPostProcessor(registry, packagesToScan);
    }
  }
}

2.2 註冊BeanFactory 處理器


ServletComponentScanRegistrar最核心的功能就是註冊BeanFactoryPostProcessor

class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar{
  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);
  }
}

2.3 例項化掃描元件

進入
ServletComponentRegisteringPostProcessor類中首先這個類有個static程式碼塊

class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
  private static final List<ServletComponentHandler> HANDLERS;
  static {
    List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
    servletComponentHandlers.add(new WebServletHandler());
    servletComponentHandlers.add(new WebFilterHandler());
    servletComponentHandlers.add(new WebListenerHandler());
    HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
  }
} 
 

這段程式碼分別是新增相應Servlet, Filter, Listener的處理控制代碼,分別處理@WebServlet,@WebFilter,@WebListener 註解。

檢視WebServletHandler

class WebServletHandler extends ServletComponentHandler {
  WebServletHandler() {
    super(WebServlet.class);
  }
  // 看到該方法也就十分清楚了最終找到所有的Class以後,通過ServletRegistrationBean進行註冊為Bean
  @Override
  public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,BeanDefinitionRegistry registry) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServletRegistrationBean.class);
    builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
    builder.addPropertyValue("initParameters", extractInitParameters(attributes));
    builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup"));
    String name = determineName(attributes, beanDefinition);
    builder.addPropertyValue("name", name);
    builder.addPropertyValue("servlet", beanDefinition);
    builder.addPropertyValue("urlMappings", extractUrlPatterns(attributes));
    builder.addPropertyValue("multipartConfig", determineMultipartConfig(beanDefinition));
    registry.registerBeanDefinition(name, builder.getBeanDefinition());
  }
  // other code
}    
// 父類ServletComponentHandler;在父類總新增相應的過濾器(分別查詢相應註解的類,@WebServlet等。)
abstract class ServletComponentHandler {
  private final Class<? extends Annotation> annotationType;
  private final TypeFilter typeFilter;
  protected ServletComponentHandler(Class<? extends Annotation> annotationType) {
    this.typeFilter = new AnnotationTypeFilter(annotationType);
    this.annotationType = annotationType;
  }
}

接下來執行BeanFactoryPostProcessor對應的回撥方法了

class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    if (isRunningInEmbeddedWebServer()) {
      ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
      for (String packageToScan : this.packagesToScan) {
        scanPackage(componentProvider, packageToScan);
      }
    }
  }
}

createComponentProvider方法進行建立掃描相應符合條件的Bean掃描類

private ClassPathScanningCandidateComponentProvider createComponentProvider() {
  ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false);
  componentProvider.setEnvironment(this.applicationContext.getEnvironment());
  componentProvider.setResourceLoader(this.applicationContext);
  for (ServletComponentHandler handler : HANDLERS) {
    componentProvider.addIncludeFilter(handler.getTypeFilter());
  }
  return componentProvider;
}

在該方法中為當前的
ClassPathScanningCandidateComponentProvider類掃描設定過濾器;過濾器在上面的static靜態程式碼塊中已經設定了WebServletHandler,WebFilterHandler,WebListenerHandler在父類中分別建立不同註解的new AnnotationTypeFilter(annotationType)過濾類。

建立完類掃描類以後開始掃描通過該類掃描相應包(路徑)下的所有類檔案 檢查是否有對應的註解。

2.4 查詢及註冊Bean

class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
  private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
    for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
      if (candidate instanceof AnnotatedBeanDefinition) {
        for (ServletComponentHandler handler : HANDLERS) {
          handler.handle(((AnnotatedBeanDefinition) candidate), (BeanDefinitionRegistry) this.applicationContext);
        }
      }
    }
  }
}

findCandidateComponents方法查詢候選的(符合條件)的類並例項化為BeanDefinition物件。

方法執行鏈findCandidateComponents ---》scanCandidateComponents

在scanCandidateComponents方法中查詢符合條件的類,然後例項化為BeanDefinition

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
    if (isCandidateComponent(metadataReader)) {
      ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
      sbd.setSource(resource);
      if (isCandidateComponent(sbd)) {
        candidates.add(sbd);
      }
    }
    return candidates;
  }
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return false;
      }
    }
    for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return isConditionMatch(metadataReader);
      }
    }
    return false;
  }
}

在第二個for開始匹配所有的類是否有相關的註解。如果匹配上就相應的建立BeanDefinition物件放入集合Set中。

查詢到所有的類以後分別呼叫相應的Web*Handler(ServletComponentHandler)進行註冊Bean。

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

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

doHandle方法分別在子類(WebServletHandler,WebFilterHandler,WebListenerHandler)中實現,如上面已經提到的WebServletHandler類的doHandler方法。

到這裡我們知道了註解的方式最終也是被註冊為ServletRegistrationBean 例項。那這個ServletRegistrationBean又是如何被容器(Tomcat)所感知的呢?

2.5 Tomcat註冊Servlet

在Web容器下 BeanFactory使用的是
AnnotationConfigServletWebServerApplicationContext

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

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      try {
        // Initialize other special beans in specific context subclasses.
        onRefresh();
      } catch (BeansException ex) {
        throw ex;
      } finally {
        resetCommonCaches();
      }
    }
  }
} 
 

這裡只留了onRefresh方法,進入該方法:

onRefresh會進入到子類的方法

public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
  protected void onRefresh() {
    super.onRefresh();
    try {
      createWebServer();
    } catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
    }
  }
}    

進入createWebServer方法

public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
  private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
      getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
    } else if (servletContext != null) {
      try {
        getSelfInitializer().onStartup(servletContext);
      } catch (ServletException ex) {
        throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
    }
    initPropertySources();
  }
}

這裡會進入factory.getWebServer(getSelfInitializer())方法執行

進入getSelfInitializer()方法

public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
  private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
  }

  private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
    }
  }
}

這裡的for迴圈是遍歷當前容器中所有ServletContextInitializer型別的Bean。ServletRegistrationBean就是繼承ServletContextInitializer

到這裡分別呼叫ServletContextInitializer的onStartup方法,進入onStartup方法:

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
  @Override
  public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
      logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
      return;
    }
    register(description, servletContext);
  }
  // 在子類中實現  
  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);
14 }

進入子類ServletRegistrationBean

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

到這裡就是通過ServletContext來動態註冊Servlet(Servilet3.0)。

這裡返回了
ServletRegistration.Dynamic物件會繼續執行configure方法配置urlMapping等資訊。

public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
  @Override
  protected void configure(ServletRegistration.Dynamic registration) {
    super.configure(registration);
    String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
    if (urlMapping.length == 0 && this.alwaysMapUrl) {
      urlMapping = DEFAULT_MAPPINGS;
    }
    if (!ObjectUtils.isEmpty(urlMapping)) {
      registration.addMapping(urlMapping);
    }
    registration.setLoadOnStartup(this.loadOnStartup);
    if (this.multipartConfig != null) {
      registration.setMultipartConfig(this.multipartConfig);
    }
  }
}

到此在Springboot環境下Servlet如何被註冊到Servlet容器中就已經清晰了。這動態註冊Servlet的相關API都是在Servlet3.0規範中才有的。

完畢!!!

給個關注+轉發唄謝謝