Spring的注解@configuration和@Configurable两者长得非常相似,但用途却有很大的差异。
@configuration 注解大家已经非常熟悉了,配合@bean注解就可以轻松减少xml配置,这里不再过多介绍;
@Configurable 平时用的却较少,它用于解决非Spring容器管理的Bean中却依赖Spring Bean的场景,也就是说Bean A依赖了一个Spring的Bean B,但是A不是Spring 得Bean所以无法进行属性注入拿不到B的情况。
本文就来详细聊一聊这个注解的使用方法和底层原理。
使用方法
在使用之前,咱们先看看@Configurable注解的源码(Spring5.x)
/**
* Marks a class as being eligible for Spring-driven configuration.
*
* <p>Typically used with the AspectJ {@code AnnotationBeanConfigurerAspect}.
*
* @author Rod Johnson
* @author Rob Harrop
* @author Adrian Colyer
* @author Ramnivas Laddad
* @since 2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Configurable {
/**
* The name of the bean definition that serves as the configuration template.
*/
String value() default "";
/**
* Are dependencies to be injected via autowiring?
*/
Autowire autowire() default Autowire.NO;
/**
* Is dependency checking to be performed for configured objects?
*/
boolean dependencyCheck() default false;
/**
* Are dependencies to be injected prior to the construction of an object?
*/
boolean preConstruction() default false;
}
咱们从源码可以看到,这个注解必须打在类上,Autowire类型默认为Autowire.NO(实际上此值不生效,不需要修改为BY_TYPE也能注入成功,下文会提到)。其他的咱们暂时不用关心。
先新建一个类AA如下:
@Configurable
public class AA {
@Autowired
private BB b;
@Autowired
ApplicationContext applicationContext;
public BB getB() {
return b;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
}
AA类增加了注解@Configurable,没有标记@Component等Spring Bean注解,依赖了类BB、ApplicationContext并实现了Getter方法,看看BB类的源码:
@Component
public class BB {
private Long id = 12306L;
public Long getId() {
return id;
}
}
BB类非常简单,@Component标记为Spring扫描类,然后返回id属性的值为12306。
在applicationContext.xml中添加包扫描,注解,aspectj:
<!-- 扫描com.biyao.api.ordergroup下注解 -->
<context:component-scan base-package="com.kevin"/>
<!-- 允许apo注解 -->
<context:annotation-config/>
<bean class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" factory-method="aspectOf"/>
再配置一下maven的编译插件(必须配置,否则会注入失败,后面会讲解):
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<outxml>true</outxml>
<verbose>true</verbose>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
好了,代码都写完成了,咱们来跑一个单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class T1 {
@Test
public void t1() {
AA aa = new AA();
BB b = aa.getB();
System.out.println("aa.getB() is .." + b);
System.out.println("bb.id = " + b.getId());
}
}
测试结果如下:
aa.getB() is ..com.kevin.beans.BB@1c88ec8
bb.id = 12306
实验证明通过@Configurable注解能往new 出来的对象注入spring bean;以后咱们有些不想Spring IOC管理的bean也能注入了。这样可以随用随取,在一些非单例场景下特别有用,比如定时任务,每次执行的时候需要new一个新的业务对象,这样也能拿到IOC里的JDBC对象(场景仅举例,通过ApplicationContext等Spring接口也能获取)。接下来咱们来分析下原理。
@Configurable注解的实现原理
为什么要在POM文件里引入aspectj-maven-plugin呢?
我们可以先注释掉aspectj-maven-plugin插件,然后看看AA类的反编译代码。
@Configurable
public class AA {
@Autowired
private BB b;
@Autowired
ApplicationContext applicationContext;
public AA() {
}
public BB getB() {
return this.b;
}
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}
可以看到AA没有编译后再反编译回来代码没有差别:
使用了插件之后再反编译回来:
可以看到多了构造函数AA():
public AA() {
JoinPoint var2 = Factory.makeJP(ajc$tjp_1, this, this);
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
if (this != null && this.getClass().isAnnotationPresent(Configurable.class) && AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class))) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$before$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$1$e854fa65(this);
}
if (this != null && this.getClass().isAnnotationPresent(Configurable.class) && (this == null || !this.getClass().isAnnotationPresent(Configurable.class) || !AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class))) && AbstractDependencyInjectionAspect.ajc$if$6f1(var1)) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
if (!AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class)) && AbstractDependencyInjectionAspect.ajc$if$6f1(var2)) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
}
这个方法比较复杂,但经过一些列调用最终会指向org.springframework.beans.factory.wiring.BeanConfigurerSupport#configureBean方法,摘录部分关键代码:
// 解决给定bean实例的BeanWiringInfo。 beanInstance用于解析信息的bean实例
BeanWiringInfo bwi = bwiResolver.resolveWiringInfo(beanInstance);
// 按名称或类型自动装配给定bean实例的bean属性。 也可以使用AUTOWIRE_NO调用,以便仅应用实例化后的回调(例如,用于注释驱动的注入)。
// 不应用标准BeanPostProcessors回调或对bean进行任何进一步的初始化。 为此,此接口提供了不同的细粒度操作,例如initializeBean 。 但是,如果适用于实例的配置,则将应用InstantiationAwareBeanPostProcessor回调。
beanFactory.autowireBeanProperties(beanInstance, bwi.getAutowireMode(), bwi.getDependencyCheck());
// 试图将Bean放置到Spring IOC容器中
beanFactory.initializeBean(beanInstance, (beanName != null ? beanName : ""));
做了两件事:1.进行spring bean的自动注入;2.试图将新生成的bean放置到IOC容器中(会放置失败)。感兴趣的同学可以进一步追踪源码,这里往后已经进入spring bean的生命周期了。
原理总结
配置了aspectj-maven-plugin插件之后,插件会辅助将含有@Configurable注解的类,在编译时AnnotationBeanConfigurerAspect相关操作写入构造方法,这样当开发者使用new方法创建对象时就调用BeanConfigurerSupport#configureBean()方法,实现spring bean的注入。还记得我们在applicationContext.xml中配置了AnnotationBeanConfigurerAspect类吗?这就是配置的原因,如果不配置@Configurable注解不会生效。