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規範中才有的。
完畢!!!
給個關注+轉發唄謝謝