> 文章列表 > 利用ImportBeanDefinitionRegistrar手动向Spring容器注入Bean

利用ImportBeanDefinitionRegistrar手动向Spring容器注入Bean

利用ImportBeanDefinitionRegistrar手动向Spring容器注入Bean

一、注入方式

向Spring容器中注入Bean的方法很多,比如:

  • 利用<Bean>...<Bean>Xml文件描述来注入
  • 利用JavaConfig的@Configuration@Bean注入
  • 利用springboot的自动装配,即实现ImportSelector来批量注入
  • 利用ImportBeanDefinitionRegistrar来实现注入

二、@Enable注解简介

我们常常使用@EnableScheduling@EnableWebMvc等,@Enable注解的主要作用就是利用@Import注解来实现IoC对象注入,比如Spring自带的@EnableScheduling,分析一下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}

可以看到其实就是导入了一个配置类@Import({SchedulingConfiguration.class})

@Configuration(proxyBeanMethods = false)
@Role(2)
public class SchedulingConfiguration {public SchedulingConfiguration() {}@Bean(name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"})@Role(2)public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}
}

这里就是用的@Configuration@Bean注入。

再例如springboot启用类注解@SpringBootApplication里面的@EnableAutoConfiguration

@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) })
public @interface SpringBootApplication {/* Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {/* Environment property that can be used to override when auto-configuration is* enabled.*/String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/* Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/Class<?>[] exclude() default {};/* Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/String[] excludeName() default {};}

其实就是AutoConfigurationImportSelector类来实现了自动装配的逻辑。

三、利用ImportBeanDefinitionRegistrar接口来装配

本文讲的是ImportBeanDefinitionRegistrar接口结合@Enable注解来实现注入:

首先我们要自己写一个@Enable注解@EnableMyBean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyDeanDefinitionRegister.class)
public @interface EnableMyBean {}

写出我们的待注入对象:

public class MyBean {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "MyBean{" +"name='" + name + '\\'' +'}';}
}

编写@Import的类MyDeanDefinitionRegister(实现ImportBeanDefinitionRegistrar接口):

public class MyDeanDefinitionRegister implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();rootBeanDefinition.setBeanClass(MyBean.class);String className = MyBean.class.getName();MutablePropertyValues propertyValues = new MutablePropertyValues();propertyValues.add("name", "lisi");//添加属性rootBeanDefinition.setPropertyValues(propertyValues);//注入到spring容器registry.registerBeanDefinition(className, rootBeanDefinition);}
}

springboot启动类,加上我们自己写的注解:

@SpringBootApplication
@EnableMyBean
public class Applicaiton {public static void main(String[] args) {SpringApplication.run(Applicaiton.class, args);}
}

写个单元测试:

@SpringBootTest
class Test1 {@AutowiredMyBean bean;@AutowiredApplicationContext applicationContext;@Testvoid test1() {System.out.println(bean);}
}

结果如下:

MyBean{name='lisi'}

可见我们已经成功将MyBean注入到Spring容器了。

四、带条件的注入方式

假设我们有以下批量注入需求(同时满足一下条件)才能注入:

1、要求把某个包下的类都加到容器
2、要加一个自定义注解,只有在这个包下加了自定义注解的才能被加到容器!

首先注解上面加入参数:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyDeanDefinitionRegister.class)
public @interface EnableMyBean {String[] basePackages() default {};
}

再写一个自定义的条件注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Meeting {
}

启动类修改,添加包扫描:

@SpringBootApplication
@EnableMyBean(basePackages = {"com.my.test.meeting"})
public class Applicaiton {public static void main(String[] args) {SpringApplication.run(Applicaiton.class, args);}
}

待注入的类:

@Meeting
public class Money {
}@Meeting
public class Work {
}//这个类就不加@Meeting注解了,不想它加入到容器,太狗了
public class Dog {}

修改类MyDeanDefinitionRegister(实现ImportBeanDefinitionRegistrar接口)逻辑:

public class MyDeanDefinitionRegister implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//获取@EnableMyBean注解的属性Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableMyBean.class.getName());//获取包扫描ClassPathScanningCandidateComponentProvider pathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);//添加过滤,带有@Meeting注解的类pathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Meeting.class));String[] basePackages = (String[]) attributes.get("basePackages");LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();for (String basePackage : basePackages) {//选择该包下的candidateComponents.addAll(pathScanningCandidateComponentProvider.findCandidateComponents(basePackage));}//注入Beanfor (BeanDefinition candidateComponent : candidateComponents) {registry.registerBeanDefinition(candidateComponent.getBeanClassName(), candidateComponent);}}
}

单元测试一下:

@SpringBootTest
class Test1 {@AutowiredApplicationContext applicationContext;@Testvoid test1() {Arrays.stream(applicationContext.getBeanDefinitionNames()).forEach(System.out::println);}
}

结果:

com.my.test.meeting.Money
com.my.test.meeting.Work

可以看到Money、Work都成功注入了,而没有Dog,因为它没有@Meeting注解

五、直接利用beanFactory注入

这里作为额外补充:

public class Compont2
{
}
@Component
public class ContextAware implements ApplicationContextAware {@Autowiredprivate DefaultListableBeanFactory beanFactory;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Class<Compont2> c2 = Compont2.class;beanFactory.createBean(c2);String name = c2.getName();BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(c2);beanFactory.registerBeanDefinition(name, beanDefinitionBuilder.getBeanDefinition());}
}

至此已经注入完毕,可以被依赖了:

@Component
public class ContextInitializingBean1 implements InitializingBean {@Autowiredprivate Compont2 compont2;@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("ContextInitializingBean1  " + compont2);}
}