一、注入方式
向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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
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;
}
@Override
public String toString() {
return "MyBean{" +
"name='" + name + '\'' +
'}';
}
}
编写@Import
的类MyDeanDefinitionRegister(实现ImportBeanDefinitionRegistrar接口):
public class MyDeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public 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);
registry.registerBeanDefinition(className, rootBeanDefinition);
}
}
springboot启动类,加上我们自己写的注解:
@SpringBootApplication
@EnableMyBean
public class Applicaiton {
public static void main(String[] args) {
SpringApplication.run(Applicaiton.class, args);
}
}
写个单元测试:
@SpringBootTest
class Test1 {
@Autowired
MyBean bean;
@Autowired
ApplicationContext applicationContext;
@Test
void 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 {
}
public class Dog {
}
修改类MyDeanDefinitionRegister(实现ImportBeanDefinitionRegistrar接口)逻辑:
public class MyDeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableMyBean.class.getName());
ClassPathScanningCandidateComponentProvider pathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
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));
}
for (BeanDefinition candidateComponent : candidateComponents) {
registry.registerBeanDefinition(candidateComponent.getBeanClassName(), candidateComponent);
}
}
private ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
}
单元测试一下:
@SpringBootTest
class Test1 {
@Autowired
ApplicationContext applicationContext;
@Test
void 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 {
@Autowired
private DefaultListableBeanFactory beanFactory;
@Override
public 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 {
@Autowired
private Compont2 compont2;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("ContextInitializingBean1 " + compont2);
}
}