SpringBoot(十九)Servlet,Filter和listener是如何實現自動裝配的?

目錄

註解方式

@ServletComponentScan註解

ServletComponentScanRegistrar類

ServletComponentRegisteringPostProcessor

handle()方法

配置類方式

ServletContextInitializerBeans 


SpringBoot版本:2.1.1 

註解方式

@ServletComponentScan註解

先了解下@ServletComponentScan註解。

@Import註解導入了一個類:ServletComponentScanRegistrar

三個屬性,也就是兩個屬性:

basePackages:String數組,指定要掃描的包

basePackageClasses:Class數組,指定掃描該類所在的包

如果沒有指定要掃描的包,則掃描主類所在的包,即@SpringBootApplication註解所在類的包。

ServletComponentScanRegistrar類

該類主要作用是確定要掃描的包,並註冊一個BeanFactoryPostProcessor,用於掃描@WebServlet、@WebFilter和@WebListener註解,自動註冊成bean。

class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

	private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) {
            //得到註解中指定的包,如果沒有則掃描主類所在的包
		Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
            //判斷bean工廠中是否存在servletComponentRegisteringPostProcessor的bean定義
		if (registry.containsBeanDefinition(BEAN_NAME)) {
                //存在的話直接從servletComponentRegisteringPostProcessor的構造參數中得到要掃描的包,加上從註解中得到的指定包
			updatePostProcessor(registry, packagesToScan);
		}
		else {
                //不存在就註冊一個
			addPostProcessor(registry, packagesToScan);
		}
	}

	private void updatePostProcessor(BeanDefinitionRegistry registry,
			Set<String> packagesToScan) {
            //得到bean定義
		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) {
           //通用bean定義
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            //設置class
		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 對象,包含了@ServletComponentScan的屬性值
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(
				metadata.getAnnotationAttributes(ServletComponentScan.class.getName()));
            //得到basePackages的值
		String[] basePackages = attributes.getStringArray("basePackages");
            //得到basePackageClasses的值
		Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
		Set<String> packagesToScan = new LinkedHashSet<>();
		packagesToScan.addAll(Arrays.asList(basePackages));
		for (Class<?> basePackageClass : basePackageClasses) {
                //得到指定類所在的包
			packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
		}
            //如果爲空
		if (packagesToScan.isEmpty()) {
                //這裏取到的包名,就是主類的包名
			packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
		}
		return packagesToScan;
	}

}

ServletComponentRegisteringPostProcessor

上面說的構造方法中的值即要掃描的包,靜態代碼塊往List中添加了三個handler,分別對應處理@WebServlet、@WebFilter和@WebListener

 postProcessBeanFactory()方法會在刷新上下文的時候進行調用,前面說過了,不詳細介紹在哪調用。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
		throws BeansException {
        //是否web應用
	if (isRunningInEmbeddedWebServer()) {
            //該對象主要是根據過濾規則掃描候選項
		ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
		for (String packageToScan : this.packagesToScan) {
			scanPackage(componentProvider, packageToScan);
		}
	}
}

private void scanPackage(
		ClassPathScanningCandidateComponentProvider componentProvider,
		String packageToScan) {
        //根據過濾規則返回候選項,循環
	for (BeanDefinition candidate : componentProvider
			.findCandidateComponents(packageToScan)) {
		if (candidate instanceof ScannedGenericBeanDefinition) {
			for (ServletComponentHandler handler : HANDLERS) {
                        //傳入bean定義,已經beanRegistry
				handler.handle(((ScannedGenericBeanDefinition) candidate),
						(BeanDefinitionRegistry) this.applicationContext);
			}
		}
	}
}

private boolean isRunningInEmbeddedWebServer() {
	return this.applicationContext instanceof WebApplicationContext
			&& ((WebApplicationContext) this.applicationContext)
					.getServletContext() == null;
}

private ClassPathScanningCandidateComponentProvider createComponentProvider() {
	ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
			false);
        //設置環境
	componentProvider.setEnvironment(this.applicationContext.getEnvironment());
        //設置resourceLoader
	componentProvider.setResourceLoader(this.applicationContext);
	for (ServletComponentHandler handler : HANDLERS) {
                //添加過濾規則,即上面講的三個註解,在啓動流程中創建上下文說過的
		componentProvider.addIncludeFilter(handler.getTypeFilter());
	}
	return componentProvider;
}

handle()方法

該方法在父類ServletComponentHandler中。attributes見的應該很多了,doHandle()方法是個抽象方法,由子類實現,下面就只拿一個講。annotationType是由子類構造方法中傳遞過來的。

 在doHandle()方法中就是創建bean定義,然後從註解獲取屬性值添加,最後註冊。

 

上面整個流程就是使用註解的方式來使用Servlet,Filter和listener,註冊bean是在刷新上下文執行invokeBeanFactoryPostProcessors()方法的過程中調用ServletComponentRegisteringPostProcessor的postProcessBeanFactory()方法實現自動註冊。

配置類方式

 先看下面這張類圖,頂層接口是ServletContextInitializer。該接口只有一個方法void onStartup(ServletContext servletContext);在容器啓動過程中調用。與WebApplicationInitializer不同,SpringServletContainerInitializer不會檢測到實現此接口(且不實現WebApplicationInitializer)的類,因此不會由servlet容器自動引導。

 

 通過ServletWebServerFactory工廠創建webServer,默認是TomcatServletWebServerFactory。所以得到的webServer也是TomcatWebServer。具體TomcatWebServer創建過程不詳細介紹,截圖展示一下。

 

 

在這裏調用。StandardContext的startInternal()方法中。

 

 這裏主要看selfInitialize方法中創建ServletContextInitializerBeans對象的過程。循環裏onStartup()方法就是將Servlet,Filter和listener添加到ServletContext中。

ServletContextInitializerBeans 

 創建ServletContextInitializerBeans對象,該對象父類繼承Collection;主要是看該類構造方法中執行的操作。

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
        //不爲空就是傳進來的type,爲空就是ServletContextInitializer
	this.initializerTypes = (initializerTypes.length != 0)
			? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
        //這裏就會從bean工廠得到ServletContextInitializer類型的beanName,然後根據name得到bean
        //根據類型添加到MultiValueMap中
	addServletContextInitializerBeans(beanFactory);
        //設置order,默認就是Integer的最大值
	addAdaptableBeans(beanFactory);
        //根據order排序
	List<ServletContextInitializer> sortedInitializers = this.initializers.values()
			.stream()
			.flatMap((value) -> value.stream()
					.sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
        //不可變List
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
        //debug日誌
	logMappings(this.initializers);
}

這裏主要看getOrderedBeansOfType這個方法。 

以配置類的方式大致就是上面這個流程,看到網上有人討論這兩種方式使用,filter的執行順序,如果沒指定order值,那麼默認值就是Integer.MAX_VALUE,這個可以到AnnotationAwareOrderComparator的父類OrderComparator中的getOrder()方法去看。

兩種方使用方式是如何實現自動注入bean的就說完了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章