关于springboot:基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中

36次阅读

共计 7525 个字符,预计需要花费 19 分钟才能阅读完成。

一、背景

咱们本人开发了一个第三方的 jar 包,想和 Spring 整合起来,并注入到 Spring 容器中。本人的 jar 包中,须要退出到 Spring 容器的类上都退出了一个自定义注解 @CustomImport(beanName=""),beanName 属性的值示意须要注入到 Spring 容器中的名字。

二、实现计划

1、基于 @ComponentScan 注解实现

应用此种计划比较简单,间接应用 @ComponentScan(includeFilters = {@ComponentScan.Filter(value = CustomImport.class)}) 即可将咱们自定义的注解退出到 Spring 容器中。此种计划略。

2、基于 ImportBeanDefinitionRegistrar 和 FactoryBean 实现

1、实现此接口 (ImportBeanDefinitionRegistrar),能够将咱们本人的BeanDefinition 对象加到 BeanDefinitionRegistry 中,期待后续 Spring 初始化对象。

注:

​ 1. 咱们能够从自定义的注解中获取到一些属性,而后来个性化咱们要初始化的 Bean 是什么样的。

​ 2. 实现 ImportBeanDefinitionRegistrar 的类配合上 @Configuration@Import注解,能够在程序启动的时候加载这个类。

2、实现此接口(FactoryBean),能够让咱们自定义实例化出咱们要构建的 Bean。

3、留神

可能有些人会说,我用 @ComponentScan 就能够搞定的事件,为何还要抉择基于第二种办法实现,这个不是多此一举吗?这个其实我次要是记录这样的一个思路。比方:Mybatis 和 Spring 整合后,只须要申明一个 @Mapper 就能够自定退出到 Spring 治理中(MapperScannerRegistrar),那么这是怎么实现的呢?应该是和上方的计划 2 相似。

三、实现步骤

1、自定义一个注解 CustomImport,被此注解标注的类,示意须要退出到 Spring 容器中。

  1. CustomImport 注解能够减少一些额定的属性,比方 beanName 示意注入到 Spring 容器时,bean 的名字。

2、写一个类实现 CustomImportFactoryBean,示意如何实例化出CustomImport 注解标注的类。

  1. 结构化对象。
  2. 结构出的对象须要本人实现初始化操作,如果须要用到 Spring 的里的对象,能够获取到 ApplicationContext 而后获取注入。

3、写一个类实现 ImportBeanDefinitionRegistrar,扫描出所有的CustomImport 的类,而后结构出BeanDefinition, 退出到 Spring 容器中。

  1. 结构 BeanDefinition 时的 BeanClass 属性须要指定上一个的 CustomImportFactoryBean 的 class。
  2. ClassPathBeanDefinitionScanner 能够扫描包。

4、自定义一个注解 EnableCustomImport,启动类上退出此注解后,实现ImportBeanDefinitionRegistrar 的调用。

  1. 此注解上须要应用 @Import 标注

1、自定义注解CustomImport

/**
 * 此注解标注的类也会主动退出到 Spring 治理中。*
 * @author huan.fu 2021/4/14 - 上午 9:23
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomImport {
    /**
     * 这个 bean 注入到 Spring 容器中的名字
     */
    String beanName();}

2、实现 CustomImportFactoryBean 构建对象

package com.huan.study.framewrok;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 工厂 Bean,用于构建 CustomImport 注解标注的类,如何进行实例化
 *
 * @author huan.fu 2021/4/14 - 上午 9:44
 */
public class CustomImportFactoryBean implements FactoryBean<Object>, ApplicationContextAware {

    private Class<?> type;
    private String beanName;
    private ApplicationContext applicationContext;

    /**
     * 此处构建的对象,如果须要依赖 Spring Bean 的话,须要本人构建进去,默认不会主动注入,即默认状况下 @Autowired 注解不失效
     */
    @Override
    public Object getObject() throws Exception {Object instance = this.type.getDeclaredConstructor().newInstance();
        applicationContext.getAutowireCapableBeanFactory().autowireBean(instance);
        return instance;
    }

    @Override
    public Class<?> getObjectType() {return type;}

    public Class<?> getType() {return type;}

    public void setType(Class<?> type) {this.type = type;}

    public String getBeanName() {return beanName;}

    public void setBeanName(String beanName) {this.beanName = beanName;}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

3、编写ImportBeanDefinitionRegistrar

package com.huan.study.framewrok;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * @author huan.fu 2021/4/14 - 上午 9:25
 */
public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {private static final Logger log = LoggerFactory.getLogger(CustomImportBeanDefinitionRegistrar.class);

    private Environment environment;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {if (!annotationMetadata.hasAnnotation(EnableCustomImport.class.getName())) {return;}
        Map<String, Object> annotationAttributesMap = annotationMetadata.getAnnotationAttributes(EnableCustomImport.class.getName());
        AnnotationAttributes annotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(annotationAttributesMap)).orElseGet(AnnotationAttributes::new);
        // 获取须要扫描的包
        String[] packages = retrievePackagesName(annotationMetadata, annotationAttributes);
        // useDefaultFilters = false, 即第二个参数 示意不扫描 @Component、@ManagedBean、@Named 注解标注的类
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false, environment, resourceLoader);
        // 增加咱们自定义注解的扫描
        scanner.addIncludeFilter(new AnnotationTypeFilter(CustomImport.class));
        // 扫描包
        for (String needScanPackage : packages) {Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(needScanPackage);
            try {registerCandidateComponents(registry, candidateComponents);
            } catch (ClassNotFoundException e) {log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 获取须要扫描的包
     */
    private String[] retrievePackagesName(AnnotationMetadata annotationMetadata, AnnotationAttributes annotationAttributes) {String[] packages = annotationAttributes.getStringArray("packages");
        if (packages.length > 0) {return packages;}
        String className = annotationMetadata.getClassName();
        int lastDot = className.lastIndexOf('.');
        return new String[]{className.substring(0, lastDot)};
    }

    /**
     * 注册 BeanDefinition
     */
    private void registerCandidateComponents(BeanDefinitionRegistry registry, Set<BeanDefinition> candidateComponents) throws ClassNotFoundException {for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                Map<String, Object> customImportAnnotationAttributesMap = annotationMetadata.getAnnotationAttributes(CustomImport.class.getName());
                AnnotationAttributes customImportAnnotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(customImportAnnotationAttributesMap)).orElseGet(AnnotationAttributes::new);
                String beanName = customImportAnnotationAttributes.getString("beanName");
                String className = annotationMetadata.getClassName();
                Class<?> clazzName = Class.forName(className);
                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(CustomImportFactoryBean.class)
                        .addPropertyValue("type", clazzName)
                        .addPropertyValue("beanName", beanName)
                        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                        .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
                        .getBeanDefinition();
                registry.registerBeanDefinition(beanName, beanDefinition);

            }
        }
    }


    @Override
    public void setEnvironment(Environment environment) {this.environment = environment;}

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}
}

4、编写 @EnableCustomImport

/**
 * 启用主动导入
 *
 * @author huan.fu 2021/4/14 - 上午 9:29
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CustomImportBeanDefinitionRegistrar.class)
public @interface EnableCustomImport {String[] packages() default {};
}

5、运行一个小的案例,获取测试后果

四、实现代码

1、https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/bean-definition-registrar

五、参考链接

1、https://stackoverflow.com/questions/4970297/how-to-get-beans-created-by-factorybean-spring-managed

正文完
 0