我們在使用springboot 整合MyBatis時,需要在啟動類上添加上@MapperScan
注解,并寫入mapper接口的包路徑,然后我們就能通過從spring IOC容器中取對應的mapper的Bean來進行持久化操作了,那么@MapperScan
是如何將mapper接口實例化并注入到Spring IOC容器中的呢?
首先搭建一個spring boot項目,引入mybatis和mysql的相關maven包。在application.properties
中配置好連接參數。這些基操我就不寫了
新建mapper,當然我這里取名取成dao了,意思到了就行。
package com.p6spy.demop6spy.dao;import org.apache.ibatis.annotations.Select;
public interface TestDao {@Select("select count(*) from subject_sae_detail")String getInfo();
}
啟動類上添加注解,指定掃描com.p6spy.demop6spy.dao
包
@SpringBootApplication
@MapperScan({"com.p6spy.demop6spy.dao"})
public class Demop6spyApplication {public static void main(String[] args) {SpringApplication.run(Demop6spyApplication.class, args);}}
就能在service實現類中注入TestDao
來進行持久化操作了。
@MapperScan
首先讓我們來看下@MapperScan
注解源碼(這里為了清晰的了解掃描注入過程,所以我只選取了部分代碼,其余的刪除了)。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
@Import(MapperScannerRegistrar.class)
,從名字上看是一個掃描注冊器,點擊去看下,發現其繼承于ImportBeanDefinitionRegistrar
。spring 自定義注解及使用。主要作用是:
ImportBeanDefinitionRegistrar
接口的作用是當這個接口的實現類(類A)被@Import接口引入某個被標記了@Configuration的注冊類(類B)時,可以得到這個類(類B)的所有注解,然后做一些動態注冊Bean的事兒。
我們看下@MapperScan
,這也沒有@Configuration
啊,別急,因為@MapperScan
是被啟動類引用的,所以還要去看下啟動類,雖然啟動類看上去是只有@SpringBootApplication
和@MapperScan
兩個注解,實際上是這樣的。
@SpringBootApplication
如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@SpringBootConfiguration
如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
找到@Configuration
了。
value
和basePackages
String[] value() default {};String[] basePackages() default {};
這兩行應該比較好辨認,就是需要我們填寫的mapper接口所在的包路徑信息。
就像這樣寫,后面我們會發現這倆填哪個都行,結果是一樣的。
或者這樣,這樣寫的話默認是賦值給value
的
annotationClass
,在這里的作用是:配置了該注解的mapper才會被掃描器掃描,與basePackage是與的作用。默認值是Annotation.class
。Class<? extends Annotation> annotationClass() default Annotation.class;
markerInterface
,在這里的作用是:基于接口的過濾器,實現了該接口的mapper才會被掃描器掃描,與basePackage是與的作用。默認值是Class.class
。Class<?> markerInterface() default Class.class;
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
MapperScannerRegistrar
@mapper注解的作用,在一中我們知道了實現ImportBeanDefinitionRegistrar
的大致作用。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;/*** {@inheritDoc}*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// this check is needed in Spring 3.1if (resourceLoader != null) {scanner.setResourceLoader(resourceLoader);}Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(basePackages));}/*** {@inheritDoc}*/@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}
registerBeanDefinitions()
方法第一行打個斷點,看下是不是真的得到引用類的所有注解信息了
點開看下詳細信息:可以看到只含有兩個注解@SpringBootApplication
和@MapperScan
,然后看下@MapperScan
注解的value值,沒錯,也是我們填的值。
getAnnotationAttributes
方法獲取MapperScan
的所有信息,上面我們知道importingClassMetadata
包含啟動類的所有注解信息,所以這里獲取MapperScan.class
注解的信息也就很輕松了。AnnotationAttributes annoAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
打個斷點就能很清晰的看到annoAttrs
的值了:
這里我們能看到value
的值就是我上面填的。
scanner
,對mapper接口的掃描在這里實現。ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner
掃描器。 Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}......
這里annotationClass
對應的是只會掃描到帶該注解的mapper,而markerInterface
對應的是只會掃描到實現該接口的mapper。
因為我們并沒有在啟動器上的@MapperScan
中添加annotationClass
和markerInterface
參數,所以這里會使用默認值Annotation.class
和Class.class
,并且不會給scanner
掃描器賦值,而之后scanner
掃描器也就不會對掃描到的mapper進行過濾。
value
,basePackages
和basePackageClasses
中獲取,所以我上面說結果是一樣的了。 List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}
scanner
掃描器會開始掃描,并返回Bean定義集合(Set<BeanDefinitionHolder>
)。 scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(basePackages));
在第一句scanner.registerFilters();
中會用到上面3
中設置的過濾參數,從方法名也可以看出來,這里會對掃描出的mapper設置過濾條件,只留下帶有annotationClass
注解和實現了markerInterface
接口的mapper,我們知道這里實際并沒有給scanner
賦這些值,所以相當于沒有過濾這倆條件。
在第二句scanner.doScan(StringUtils.toStringArray(basePackages));
中,會掃描出所有符合條件的mapper。
Set<BeanDefinitionHolder>
,內含BeanDefinition
),之后java代碼掃描工具。BeanDefinitionRegistry
接口(ApplicationContext
會實現此接口)會將新的BeanDefinition
注冊到Spring IOC容器中。
主要實現方法
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
ClassPathMapperScanner
掃描器因為源碼相對有點長,這里就不放所有的了,只截取重點部分說明,請自行查看所有源碼。
ClassPathMapperScanner
繼承于ClassPathBeanDefinitionScanner
。public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
熟悉Spring IOC容器的應該會知道ClassPathBeanDefinitionScanner
,我們知道有多種方式將Bean注入IOC容器,可以通過在xml
中配置<bean>
,也可以通過@Bean
注解,兩種方式最終都會調用ClassPathBeanDefinitionScanner
來實現掃描并生成BeanDefinition
。
registerFilters()
,在上面二.MapperScannerRegistrar
中有講到是用來配置掃描過濾條件的,來看下源碼: public void registerFilters() {boolean acceptAllInterfaces = true;// if specified, use the given annotation and / or marker interfaceif (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}// override AssignableTypeFilter to ignore matches on the actual marker interfaceif (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}if (acceptAllInterfaces) {// default include filter that accepts all classesaddIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});}// exclude package-info.javaaddExcludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");}});}
從上面可以看到會判斷annotationClass
和markerInterface
,然后通過AnnotationTypeFilter()
和AssignableTypeFilter()
來設置過濾器。如果這倆都沒設置的話就會默認掃描所有的:
addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});
最后會過濾掉package-info.java
。
// exclude package-info.javaaddExcludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");}});
doScan()
,終于到最重要的doScan
方法了。看下源碼:public Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}
第一眼看上去是不是瞬間臥槽,怎么源碼有點少,這里實際上是調用父類ClassPathBeanDefinitionScanner
的doScan
方法進行掃描(內容有點多=.=)。這里我們只要知道會對basePackages
參數包含的所有包進行掃描,并且會使用之前設置的過濾器。最后返回掃描到的所有類——的BeanDefinitionHolder
集合(當成類定義集合就行)。因為這里掃描的是Mybatis的mapper,我們知道mapper都是接口,而接口是無法實例化的,所以這樣直接返回的話是沒辦法生成對應的實例的,就更不用說注入Spring IOC容器了。所以需要使用processBeanDefinitions
方法再加工一下。
processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();if (logger.isDebugEnabled()) {logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");}// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {if (logger.isDebugEnabled()) {logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");}definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}
}
如何自定義注解實現功能、別看洋洋灑灑寫了一大堆東西,核心只有BeanDefinition
一點。上面我們知道beanDefinitions
集合中的bean定義都是針對mapper接口的,而接口無法實例化。
先來認知下GenericBeanDefinition
,大致講下作用:類的定義,創建完后,BeanDefinitionRegistry
接口(ApplicationContext
會實現此接口)會將新的BeanDefinition
注冊到Spring IOC容器中(GenericBeanDefinition
繼承于AbstractBeanDefinition
,AbstractBeanDefinition
實現了BeanDefinition
)。
說幾個用GenericBeanDefinition
注入Bean的例子吧:
新建UserInfo.java
類,之后會通過幾種方式實例化并注入Spring IOC容器。
public class UserInfo {private String userName;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}
}
BeanFactoryPostProcessor
(Bean工廠的后置處理器)注入。該接口在Spring IOC容器注冊Bean定義的邏輯都跑完后,但是所有的Bean都還沒真正實例化之前調用。
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
這個方法的主要是通過注入進來的BeanFactory,在真正初始化Bean之前,再對spring
IOC容器做一些動態修改。增加或者修改某些Bean定義的值,甚至再動態創建一些BeanDefinition。
新建MyBeanFactoryPostProcessor
并實現BeanFactoryPostProcessor
。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();genericBeanDefinition.setBeanClass(UserInfo.class);genericBeanDefinition.getPropertyValues().add("userName","123");((DefaultListableBeanFactory) configurableListableBeanFactory).registerBeanDefinition("userInfo", genericBeanDefinition);}
}
測試下
@RequestMapping("/testUser")public void testUser(){try{UserInfo s4 = (UserInfo)applicationContext.getBean("userInfo");System.out.println(s4.getUserName());}catch (Exception ex){System.out.println("4.There is something error:"+ex.getMessage());}}
結果,成功注入。
BeanDefinitionRegistryPostProcessor
接口(Bean注冊器的后置處理器)注入。sql注入點批量掃描工具,新建MyBeanDefinitionRegistryPostProcessor
實現BeanDefinitionRegistryPostProcessor
接口
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();genericBeanDefinition.setBeanClass(UserInfo.class);genericBeanDefinition.getPropertyValues().add("userName","456");beanDefinitionRegistry.registerBeanDefinition("userInfo2",genericBeanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}
我們能看到這里竟然也有postProcessBeanFactory
方法。原來BeanDefinitionRegistryPostProcessor
繼承了BeanFactoryPostProcessor
。然后注入一個UserInfo
實例,beanName
為userInfo2
。
測試下
@RequestMapping("/testUser")public void testUser(){try{UserInfo s4 = (UserInfo)applicationContext.getBean("userInfo");System.out.println("userInfo userName:"+s4.getUserName());}catch (Exception ex){System.out.println("4.There is something error:"+ex.getMessage());}try{UserInfo s5 = (UserInfo)applicationContext.getBean("userInfo2");System.out.println("userInfo2 userName:"+s5.getUserName());}catch (Exception ex){System.out.println("5.There is something error:"+ex.getMessage());}}
結果如下,也注入進來了。
我們只要知道該步驟是完善BeanDefinition
,使之后能夠實例化并注入Spring IOC即可。
讓我們回到源碼中來。打個斷點看下,在對beanDefinitions
進行加工前,能看到BeanDefinition
中的beanClass
是我們寫我的Mybatis的mapper
TestDao,而TestDao是一個接口,是無法實例化的,所以之后會修改該值。
之后會重新設置BeanClass
definition.setBeanClass(this.mapperFactoryBean.getClass());
這個mapperFactoryBean
又是什么?
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {//......
}
翻看源碼后我們發現該類繼承了SqlSessionDaoSupport
并實現了FactoryBean<T>
,SqlSessionDaoSupport
主要是可以提供sqlSessionTemplate
,我們可以通過繼承其來使用sqlSessionTemplate
連接數據庫并進行持久化操作,當然這個不是本篇的重點,重點是FactoryBean<T>
。
FactoryBean是spring對外提供的對接接口,當向spring對象使用getBean("…")方法時,spring會使用FactoryBean的getObject方法返回對象。所以當一個類實現了 factoryBean接口時,那么每次向spring要這個類時,spring就返回T對象。
Springboot注解?MapperFactoryBean<T>
中有一個成員變量
private Class<T> mapperInterface;
這個是什么時候傳入的呢?
讓我們往回一步,在重新設置beanName
的上一步。
設置了ConstructorArgumentValues
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
這里會將definition.getBeanClassName()
添加進ConstructorArgumentValues
中。
之后在實例化并注入Spring IOC容器時。程序會獲取beanDefinition
的beanName
,然后調用beanName
對應的構造方法實例化beanName
。用到的參數就是這里添加的ConstructorArgumentValues
>
讓我們看看MapperFactoryBean<T>
的構造方法:
public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;
}
是不是整個流程都清晰了呢。
我們知道實現了FactoryBean<T>
接口的類,想要從Spring IOC容器中獲取該Bean,會調用getObject
方法。看下源碼
@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}
獲取自定義注解的值,從表面上看好像mybatis也自己實現了一個容器,然后會從容器中取出Bean,之后Spring 的IOC容器會緩存該Bean,所以第二次之后就不會再去取了。
以上,就是mapper掃描,實例化到注入容器的整個過程了。
這里我們可以自己寫一個demo來驗證下。當然我這里就不用接口了,為了簡單起見,直接注入類。
MapperScan
寫一個自定義注解,作用是啟動自動掃描。MyScanAnnotation.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(MyTestScannerRegistrar.class)
public @interface MyScanAnnotation {String[] basePackages() default {};Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;
}
可以看到有三個參數,相信看完文章的各位一定都知道意義,就不多講了。
MyTestScannerRegistrar
,掃描注冊器。MyTestScannerRegistrar.java
public class MyTestScannerRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(MyScanAnnotation.class.getName()));MyTestScanner scanner = new MyTestScanner(beanDefinitionRegistry);Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(basePackages));}
}
自定義注解有什么用。這里也是很簡單,獲取注解參數,新建掃描器,然后把注解參數賦值給新建的掃描器。然后運行掃描。獲取Set<BeanDefinitionHolder>
,內含BeanDefinition
,之后spring的上下文applicationContext實現的BeanDefinitionRegistry
接口的
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法實例化和注入這些BeanDefinition
。
MyTestScanner
。MyTestScanner.java
public class MyTestScanner extends ClassPathBeanDefinitionScanner {private Class<? extends Annotation> annotationClass;private Class<?> MarkerInterface;public Class<?> getMarkerInterface() {return MarkerInterface;}public void setMarkerInterface(Class<?> markerInterface) {MarkerInterface = markerInterface;}public Class<? extends Annotation> getAnnotationClass() {return annotationClass;}public void setAnnotationClass(Class<? extends Annotation> annotationClass) {this.annotationClass = annotationClass;}public MyTestScanner(BeanDefinitionRegistry registry) {super(registry);}public void registerFilters(){if(!this.annotationClass.isInstance(Annotation.class)){addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));}}@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {logger.info("No MyScanAnnotation class was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");}return beanDefinitions;}
}
因為掃描的都是類,可以實例化,所以就直接返回了。
MyTestAnntation
,之后會自動注入帶有該注解的類。MyTestAnntation.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MyTestAnntation {
}
沒有參數,最簡單的方式。
jsonpropertyorder注解。BaseService.java
public interface BaseService {void getInfo();
}
新增com.p6spy.demop6spy.baseI
和com.p6spy.demop6spy.baseII
包,用來對比參照
(1)在com.p6spy.demop6spy.baseI
中新建BaseITestI
,BaseITestII
和BaseITestIII
,都實現BaseService
接口,BaseITestI.java
和BaseITestII.java
加上@MyTestAnntation
注解。
BaseITestI.java
@MyTestAnntation
public class BaseITestI implements BaseService {public void getInfo(){System.out.println("Hello,BaseITestI");}
}
BaseITestII.java
@MyTestAnntation
public class BaseITestII implements BaseService {public void getInfo(){System.out.println("Hello,BaseITestII");}
}
BaseITestIII.java
public class BaseITestIII implements BaseService {public void getInfo(){System.out.println("Hello,BaseITestIII");}
}
(2)在com.p6spy.demop6spy.baseII
中新建BaseIITestI
,實現BaseService
接口,添加@MyTestAnntation
注解。
BaseIITestI.java
@MyTestAnntation
public class BaseIITestI implements BaseService {public void getInfo(){System.out.println("Hello,BaseIITestI");}
}
@MyScanAnnotation
注解Demop6spyApplication.java
@SpringBootApplication
@MapperScan({"com.p6spy.demop6spy.dao"})
@MyScanAnnotation(basePackages = {"com.p6spy.demop6spy.baseI"},
annotationClass = MyTestAnntation.class)
public class Demop6spyApplication {public static void main(String[] args) {SpringApplication.run(Demop6spyApplication.class, args);}}
java調用掃描儀?這里我設置了basePackages
和annotationClass
兩個參數,意思為只掃描com.p6spy.demop6spy.baseI
包中帶MyTestAnntation
注解的類。
@MyScanAnnotation(basePackages = {"com.p6spy.demop6spy.baseI"},
annotationClass = MyTestAnntation.class)
測試一下
@RequestMapping("/testAnn")public void testAnnotation(){try{BaseService s1 = (BaseService)applicationContext.getBean("baseITestI");s1.getInfo();}catch (Exception ex){System.out.println("1.There is something error:"+ex.getMessage());}try{BaseService s2 = (BaseService)applicationContext.getBean("baseITestII");s2.getInfo();}catch (Exception ex){System.out.println("2.There is something error :"+ex.getMessage());}try{BaseService s3 = (BaseService)applicationContext.getBean("baseITestIII");s3.getInfo();}catch (Exception ex){System.out.println("3.There is something error:"+ex.getMessage());}try{BaseService s4 = (BaseService)applicationContext.getBean("baseIITestI");s4.getInfo();}catch (Exception ex){System.out.println("4.There is something error:"+ex.getMessage());}}
結果
可以看到只有I和II成功注入了,這也符合我們定的掃描策略。
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态