@Configuration和@Componet、@Service、@Controller、@Repository的类创建并添加到Spring容器中;
那么,基于Spring的这一特性,我们能否自己编写一个注解,让Spring在启动时扫描指定目录下带有指定注解的的类创建并加载到Spring容器中呢;
我们先创建一个Maven工程,在Maven中添加Spring的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
在上一篇文章中我们提到过,实现ImportBeanDefinitionRegistrar接口可以动态的管理Bean,那么我们编写一个类来实现ImportBeanDefinitionRegistrar这个接口;在前面我们提到过,Spring中的类都是由BeanFactory来创建的,所以这里我们还需要得到BeanFactory,如下:
package com.example.bean.ImportBeanDefinitionRegistrar2;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
/**
* 类 名: MyAutoConfiguredMyBatisRegistrar
* 描 述: 测试动态注入Bean到Spring容器中
*/
public class MyAutoConfiguredMyBatisRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware , BeanFactoryAware {
private ResourceLoader resourceLoader;
/**
* 动态置顶扫描包路径下特殊的类加载到Bean中
* @param importingClassMetadata
* @param registry
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 当前MyClassPathBeanDefinitionScanner已被修改为扫描带有指定注解的类
MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry, false);
scanner.setResourceLoader(resourceLoader);
scanner.registerFilters();
scanner.doScan("com.example.bean.testScan");
}
/**
* 获取Spring中的一些元数据
* @param resourceLoader
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
}
}
在这里,我们自定义一个注解,被扫描的路径下包含这个注解的类会被加载到Spring容器中;
/**
* 类 名: MyService
* 描 述: 自定义注解,带有当前注解的类会被注入到Spring容器中
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface MyService {
}
注解定义好,但是Spring怎么知道我们需要扫描哪个地方的带有这个注解的类的,所以我们还需要借助一个ClassPathBeanDefinitionScanner扫描器来获取我们所需要创建的Bean;
可以看到,扫描器ClassPathBeanDefinitionScanner扫描类路径上的需要被管理的类,通过BeanFactory创建Bean给ApplicationComtext(Spring容器)管理;
那么我们就来定义一个扫描器继承ClassPathBeanDefinitionScanner这个扫描器;
package com.example.bean.ImportBeanDefinitionRegistrar2;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.Set;
/**
* 类 名: MyClassPathBeanDefinitionScanner
* 描 述: 定义一个扫描器,指定需要扫描的标识
*
* @author: jiaYao
*/
public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
}
/**
* @addIncludeFilter 将自定义的注解添加到扫描任务中
* @addExcludeFilter 将带有自定义注解的类 ,不加载到容器中
*/
protected void registerFilters () {
/**
* TODO addIncludeFilter 满足任意includeFilters会被加载
*/
addIncludeFilter(new AnnotationTypeFilter(MyService.class));
/**
* TODO addExcludeFilter 同样的满足任意excludeFilters不会被加载
*/
// addExcludeFilter(new AnnotationTypeFilter(MyService.class));
}
/**
* 重写类扫描包路径加载器,调用父类受保护的扫描方法 doScan
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
return super.doScan(basePackages);
}
}
上述代码中,比较重要的一个点就是registerFilters()这个方法,在里面我们可以定义让Spring去扫描带有特定标志的类选择进行管理或者是选择不管理;通过addIncludeFilter()方法和通过addExcludeFilter()方法;
在上面我们已经指定了需要被扫描的包路径;现在我们开始进行一些数据的准备进行测试;我们先创建一个独立的包,这个包是不被默认的Spring扫描的,这个包需要被我们自定义的这个类的扫描器进行扫描到。也就是上述代码中的包路径com.example.bean.testScan;
我们在包路径com.example.bean.testScan创建3个类用来测试,分别是
/**
* 类 名: Test01
* 描 述: 带有默认Spring扫描时会进行管理的注解
*/
@Component
public class Test01 {
}
/**
* 类 名: Test01
* 描 述: 带有我们自定义的注解,不会被默认的Spring扫描器所扫描管理
*/
@MyService
public class Test02 {
}
/**
* 类 名: Test03
* 描 述: 一个普通的类,什么都不带
*/
public class Test03 {
}
(@Import仅仅是引入,不会被Spring容器管理);
代码如下:
@Configuration
@Import(MyAutoConfiguredMyBatisRegistrar.class)
@ComponentScan("com.example.bean.ImportBeanDefinitionRegistrar2")
public class MyConf {
}
我们可以看出,此时Spring扫描的包路径是不包含我们测试的Bean所在的路径的;我们写一个测试类看一下效果,看看最终Spring容器中会有哪些类被加载了;
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConf.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (int i = 0; i < beanDefinitionNames.length; i++) {
System.out.println(beanDefinitionNames[i]);
}
}
程序输出结果如下:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConf
test02
从输入结果我们可以看到,使用@Import注解引入的类,是不会被加载到Spring容器中的。同时,我们指定自定义的类扫描器在扫描的路径下,即使出现了默认Spring本该扫描的注解@Component时它也没有将其加载到Spring容器中。而带有我们特定标识@MyService的类则被加载到Spring容器中了,而另外的一个什么标识都没有的类,也没有被加载到容器中;