一、背景
我們在Spring+SpringMVC+Mybatis的整合開發中,經常會遇到事務配置不起作用等問題,那麼本文就來分析下出現這種問題可能的原因以及解決方式。
二、分析及原理窺探
1.專案結構
2.我們在spring-mvc.xml檔案中進行如下配置,這種方式會成功掃描到帶有@Controller註解的Bean,不會掃描帶有@Service/@Repository註解的Bean,是正確的。
<context:component-scan base-package="com.hafiz.www.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3.但是如下方式,不僅僅掃描到帶有@Controller註解的Bean,還掃描到帶有@Service/@Repository註解的Bean,可能造成事務不起作用等問題。
<context:component-scan base-package="com.hafiz.www">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
這是因為什麼呢?下面讓我們來從原始碼進行分析。
1.<context:component-scan>會交給org.springframework.context.config.ContextNamespaceHandler處理.
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
2.ComponentScanBeanDefinitionParser會讀取配置檔案資訊並組裝成org.springframework.context.annotation.ClassPathBeanDefinitionScanner進行處理。
3.如果沒有配置<context:component-scan>的use-default-filters屬性,則預設為true,在建立ClassPathBeanDefinitionScanner時會根據use-default-filters是否為true來呼叫如下程式碼:
protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false)); logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false)); logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }
從以上原始碼我們可以看出預設ClassPathBeanDefinitionScanner會自動註冊對@Component、@ManagedBean、@Named註解的Bean進行掃描。
4.在進行掃描時會通過include-filter/exclude-filter來判斷你的Bean類是否是合法的:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); if (!metadata.isAnnotated(Profile.class.getName())) { return true; } AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); return this.environment.acceptsProfiles(profile.getStringArray("value")); } } return false; }
從以上原始碼可看出:掃描時首先通過exclude-filter 進行黑名單過濾,然後通過include-filter 進行白名單過濾,否則預設排除。
三、結論
在spring-mvc.xml中進行如下配置:
<context:component-scan base-package="com.hafiz.www"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
則SpringMVC容器不僅僅掃描並註冊帶有@Controller註解的Bean,而且還掃描並註冊了帶有@Component的子註解@Service、@Reposity的Bean。因為use-default-filters預設為true。所以如果不需要預設的,則use-default-filters=“false”禁用掉。
當我們進行上面的配置時,SpringMVC容器會把service、dao層的bean重新載入,從而造成新載入的bean覆蓋了老的bean,但事務的AOP代理沒有配置在spring-mvc.xml配置檔案中,造成事務失效。解決辦法是:在spring-mvc.xml配置檔案中的context:component-scan標籤中使用use-default-filters=“false”禁用掉預設的行為。