springboot动态创建bean spring 动态创建bean_spring


@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;


springboot动态创建bean spring 动态创建bean_springboot动态创建bean_02


可以看到,扫描器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容器中了,而另外的一个什么标识都没有的类,也没有被加载到容器中;