如果有成百上千個dao接口呢,那我們豈不是要配置添加成百上千個bean,當然不是這樣,spring還為MyBatis添加了拓展的功能,可以通過掃描包目錄的方式,添加dao,讓我看看具體使用和實現。
<!-- 去掉該配置 <bean id="personDao" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="net.itaem.dao.PersonDao"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> --> <!-- 如果 net.itaem.dao 包下面有很多dao需要注冊,那么可以使用這種掃描的方式添加dao--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="net.itaem.dao"/> </bean>
我們屏蔽掉了最原始的代碼(userMapper 的創建)而增加了MapperScannerConfigurer的配置,basePackage屬性是讓你為映射器接口文件設置基本的包路徑。你可以使用分號或逗號作為分隔符設置多於一個的包路徑。每個映射器將會在指定的包路徑中遞歸地被搜索到。被發現的映射器將會使用Spring對自動偵測組件默認的命名策略來命名。也就是說,如果沒有發現注解,它就會使用映射器的非大寫的非完全限定類名。但是如果發現了@Component或JSR-330@Named注解,它會獲取名稱。
public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); }
afterPropertiesSet()方法除了一句對basePackage屬性的驗證代碼外並沒有太多的邏輯實現。
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,如果MapperScannerConfigurer實現了該接口,那么說明在application初始化的時候該接口會被調用,具體實現,讓我先看看:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
processPropertyPlaceHolders屬性的處理
執行屬性的處理,簡單的說,就是把xml中${XXX}中的XXX替換成屬性文件中的相應的值
private void processPropertyPlaceHolders() { Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) { BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform // property placeholder substitution. Instead, create a BeanFactory that just // contains this mapper scanner and post process the factory. DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); } PropertyValues values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values); this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); } }
BeanDefinitionRegistries會在應用啟動的時候調用,並且會早於BeanFactoryPostProcessors的調用,這就意味着PropertyResourceConfigurers還沒有被加載所有對於屬性文件的引用將會失效。為避免此種情況發生,此方法手動地找出定義的PropertyResourceConfigurers並進行提前調用以保證對於屬性的引用可以正常工作。
根據配置屬性生成過濾器
scanner.registerFilters();方法會根據配置的屬性生成對應的過濾器,然后這些過濾器在掃描的時候會起作用。
public void registerFilters() { boolean acceptAllInterfaces = true; //對於annotationClass屬性的處理 if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } //對於markerInterface屬性的處理 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // default include filter that accepts all classes addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); } //不掃描package-info.java文件 addExcludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); } }); }
代碼中得知,根據之前屬性的配置生成了對應的過濾器。
(1)annotationClass屬性處理。
如果annotationClass不為空,表示用戶設置了此屬性,那么就要根據此屬性生成過濾器以保證達到用戶想要的效果,而封裝此屬性的過濾器就是AnnotationTypeFilter。AnnotationTypeFilter保證在掃描對應Java文件時只接受標記有注解為annotationClass的接口。
(2)markerInterface屬性處理。
如果markerInterface不為空,表示用戶設置了此屬性,那么就要根據此屬性生成過濾器以保證達到用戶想要的效果,而封裝此屬性的過濾器就是實現AssignableTypeFilter接口的局部類。表示掃描過程中只有實現markerInterface接口的接口才會被接受。
(3)全局默認處理。
在上面兩個屬性中如果存在其中任何屬性,acceptAllInterfaces的值將會改變,但是如果用戶沒有設定以上兩個屬性,那么,Spring會為我們增加一個默認的過濾器實現TypeFilter接口的局部類,旨在接受所有接口文件。
(4)package-info.java處理。
對於命名為package-info的Java文件,默認不作為邏輯實現接口,將其排除掉,使用TypeFilter接口的局部類實現match方法。
從上面的函數我們看出,控制掃描文件Spring通過不同的過濾器完成,這些定義的過濾器記錄在了includeFilters和excludeFilters屬性中。
掃描java文件
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); //如果配置了includeAnnotationConfig,則注冊對應注解的處理器以保證注解功能的正常使用。 if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } //scan是個全局方法,掃描工作通過doScan(basePackages)委托給了doScan方法,同時,還包括了includeAnnotationConfig屬性的處理, //AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)代碼主要是完成對於注解處理器的簡單注冊, //比如AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor等,這里不再贅述,我們重點研究文件掃描功能的實現。 protected Set<BeanDefinitionHolder> doScan(String... basePackages) { //如果沒有掃描到任何文件發出警告 Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); //掃描basePackage路徑下java文件 for (String basePackage : basePackages) { //如果當前bean是用於生成代理的bean那么需要進一步處理 //findCandidateComponents方法根據傳入的包路徑信息並結合類文件路徑拼接成文件的絕對路徑, //同時完成了文件的掃描過程並且根據對應的文件生成了對應的bean,
//使用ScannedGenericBeanDefinition類型的bean承載信息,bean中只記錄了resource和source信息。 //我們更感興趣的是isCandidateComponent(metadataReader),此句代碼用於判斷當前掃描的文件是否符合要求,
//而我們之前注冊的一些過濾器信息也正是在此時派上用場的。 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { //解析scope屬性 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //如果是AnnotatedBeanDefinition類型的bean,需要檢測下常用注解如:Primary、Lazy等 if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //檢測當前bean是否已經注冊 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
總結