大家好,又见面了,我是你们的朋友全栈君。
我们在使用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;
}
1.首先是@Import(MapperScannerRegistrar.class)
,从名字上看是一个扫描注册器,点击去看下,发现其继承于ImportBeanDefinitionRegistrar
。
主要作用是:
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
了。
2 . value
和basePackages
String[] value() default {
};
String[] basePackages() default {
};
这两行应该比较好辨认,就是需要我们填写的mapper接口所在的包路径信息。
就像这样写,后面我们会发现这俩填哪个都行,结果是一样的。
或者这样,这样写的话默认是赋值给value
的
3.annotationClass
,在这里的作用是:配置了该注解的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Annotation.class
。
Class<? extends Annotation> annotationClass() default Annotation.class;
4.markerInterface
,在这里的作用是:基于接口的过滤器,实现了该接口的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Class.class
。
Class<?> markerInterface() default Class.class;
5.构造工厂
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
二.MapperScannerRegistrar
在一中我们知道了实现ImportBeanDefinitionRegistrar
的大致作用。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/** * {@inheritDoc} */
@Override
public 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.1
if (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} */
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
1.首先在registerBeanDefinitions()
方法第一行打个断点,看下是不是真的得到引用类的所有注解信息了
点开看下详细信息:可以看到只含有两个注解@SpringBootApplication
和@MapperScan
,然后看下@MapperScan
注解的value值,没错,也是我们填的值。
2.这里会通过getAnnotationAttributes
方法获取MapperScan
的所有信息,上面我们知道importingClassMetadata
包含启动类的所有注解信息,所以这里获取MapperScan.class
注解的信息也就很轻松了。
AnnotationAttributes annoAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
打个断点就能很清晰的看到annoAttrs
的值了:
这里我们能看到value
的值就是我上面填的。
3.然后是比较重要的一点,新建扫描器scanner
,对mapper接口的扫描在这里实现。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
4.然后是把一些参数赋给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进行过滤。
5.这里开始获取我们配置的要扫描的包了,可以看到会从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));
}
6.在这里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。
7.所有流程走完后会生成一个Bean定义集合(Set<BeanDefinitionHolder>
,内含BeanDefinition
),之后
BeanDefinitionRegistry
接口(ApplicationContext
会实现此接口)会将新的BeanDefinition
注册到Spring IOC容器中。
主要实现方法
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
三.ClassPathMapperScanner
扫描器
因为源码相对有点长,这里就不放所有的了,只截取重点部分说明,请自行查看所有源码。
1.首先看下继承关系,ClassPathMapperScanner
继承于ClassPathBeanDefinitionScanner
。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
熟悉Spring IOC容器的应该会知道ClassPathBeanDefinitionScanner
,我们知道有多种方式将Bean注入IOC容器,可以通过在xml
中配置<bean>
,也可以通过@Bean
注解,两种方式最终都会调用ClassPathBeanDefinitionScanner
来实现扫描并生成BeanDefinition
。
2.registerFilters()
,在上面二.MapperScannerRegistrar
中有讲到是用来配置扫描过滤条件的,来看下源码:
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
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;
}
});
}
// exclude 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");
}
});
}
从上面可以看到会判断annotationClass
和markerInterface
,然后通过AnnotationTypeFilter()
和AssignableTypeFilter()
来设置过滤器。如果这俩都没设置的话就会默认扫描所有的:
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
最后会过滤掉package-info.java
。
// exclude 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");
}
});
3.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
方法再加工一下。
4.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 MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.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;
}
}
(1)通过实现BeanFactoryPostProcessor
(Bean工厂的后置处理器)注入。
该接口在Spring IOC容器注册Bean定义的逻辑都跑完后,但是所有的Bean都还没真正实例化之前调用。
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
这个方法的主要是通过注入进来的BeanFactory,在真正初始化Bean之前,再对spring
IOC容器做一些动态修改。增加或者修改某些Bean定义的值,甚至再动态创建一些BeanDefinition。
新建MyBeanFactoryPostProcessor
并实现BeanFactoryPostProcessor
。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public 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());
}
}
结果,成功注入。
(2)通过实现BeanDefinitionRegistryPostProcessor
接口(Bean注册器的后置处理器)注入。
新建MyBeanDefinitionRegistryPostProcessor
实现BeanDefinitionRegistryPostProcessor
接口
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(UserInfo.class);
genericBeanDefinition.getPropertyValues().add("userName","456");
beanDefinitionRegistry.registerBeanDefinition("userInfo2",genericBeanDefinition);
}
@Override
public 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对象。
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
方法。看下源码
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
从表面上看好像mybatis也自己实现了一个容器,然后会从容器中取出Bean,之后Spring 的IOC容器会缓存该Bean,所以第二次之后就不会再去取了。
以上,就是mapper扫描,实例化到注入容器的整个过程了。
这里我们可以自己写一个demo来验证下。当然我这里就不用接口了,为了简单起见,直接注入类。
四.自动注入带自定义注解的类
1.首先让我们模仿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;
}
可以看到有三个参数,相信看完文章的各位一定都知道意义,就不多讲了。
2.新建MyTestScannerRegistrar
,扫描注册器。
MyTestScannerRegistrar.java
public class MyTestScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public 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
。
3.新建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));
}
}
@Override
protected 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;
}
}
因为扫描的都是类,可以实例化,所以就直接返回了。
4.新建注解MyTestAnntation
,之后会自动注入带有该注解的类。
MyTestAnntation.java
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
public @interface MyTestAnntation {
}
没有参数,最简单的方式。
5.新建BaseService接口
BaseService.java
public interface BaseService {
void getInfo();
}
5.新增包和类
新增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");
}
}
6.在启动类上添加@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);
}
}
这里我设置了basePackages
和annotationClass
两个参数,意思为只扫描com.p6spy.demop6spy.baseI
包中带MyTestAnntation
注解的类。
@MyScanAnnotation(basePackages = {
"com.p6spy.demop6spy.baseI"},
annotationClass = MyTestAnntation.class)
7.测试
测试一下
@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成功注入了,这也符合我们定的扫描策略。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143022.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...