Spring @Configurable基本用法

作者 plentymore 日期 2018-12-11
Spring @Configurable基本用法

关于@Configurable的用法,Spring文档有详细的描述,不过由于看得比较粗略,后面实际使用的时候踩了不少坑。这个注解有以下几个用途:

为非Spring管理的对象注入Spring Bean

非Spring管理的对象,这里指的是我们自己new的对象,比如Dog dog = new Dog(),这个dog对象的生命周期不是由Spring管理的,而是我们自己创建的对象,根据文档的说法,我们只要在类上面加上@Configurable注解,就可以让Spring来配置这些非Spring管理的对象了,即为它们注入需要的依赖(实际上还有很多额外的工作要做)。下面有个例子

// Account类,使用new操作符号手动创建,不交由Spring Container管理
@Configurable(autowire = Autowire.BY_TYPE)
public class Account {

@Autowired
Dog dog;

public void output(){
System.out.println(dog);
}

}

上面的Account类使用的是@Configurable,而不是@Configuration@Configuration类似于XML配置里面的<beans></beans>,我们可以在<beans></beans>里面声明要交由Spring Cotainer创建和管理的Bean,因此我们可以在@Configuration注解的类里面使用@Bean注解达到同样的效果,注意被@Configuration标注的类也会被Spring Container创建和管理,因此它也是一个Bean。

@Configuration标注的类,能够被Spring配置,然后当我们手动创建Account对象的时候(Account acc = new Account()),Spring将会用创建一个Account的Bean,然后这个Bean能被Spring正常地注入需要的属性,接着Spring使用这个Bean来设置我们刚刚创建的
Account对象(acc)的属性,最后返回的对象的属性就和Bean的一样了。

// 配置类,使用注解的方式创建Bean
@Configuration
@EnableLoadTimeWeaving
@EnableSpringConfigured
public class Config {

// 这个Bean将会被注入到Account的属性中
@Bean
Dog dog(){
Dog d = new Dog();
d.setId(1);
d.setName("dog");
return d;
}
}
public class Dog {
private int id;
private String name;
// set,get和toString方法就不贴了
}
// 启动类
@ComponentScan
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
Account account = new Account();
account.output();
}
}

程序输入结果如下:

Imgur

可以看到,我们自己创建的Account对象,输出了Dog对象,而不是null。因为Spring使用AspectJ的LTW(LoadTimeWeaving)技术为我们自己创建的Account对象注入了Dog对象。

要使上面的例子正常运行,需要满足几个要求:

  • spring-core, spring-beans, spring-context, spring-instrument, spring-aspects, aspectjweaver, spring-tx等几个依赖要有,其中spring-tx是可选的,没有的话会输出一些警告信息。

  • @EnableLoadTimeWeaving@EnableSpringConfigured两个注解必须有,可以注解在任意被@Configuration注解的类上面

  • 运行前加上-javaagent:/path/to/spring-instrument.jar这个jvm参数,/path/to/spring-instrument.jar为你的spring-instrumentjar包的路径

如果用的maven管理依赖,并且IDE为Intellij IDEA,可以打开Project Structure的Libraries选项查看jar包的路径,然后在run/debug里面配置jvm参数。
Imgur
Imgur

How it works?

首先,如果仅仅有@Configuration注解,是不起任何作用的,因此我们这时候手动创建Account对象的话,将会输出null。因为起到关键作用的时候AnnotationBeanConfigurerAspect这个类(它是使用aspect定义的,而不是class,因为我还没用过aspectj编程,所以还不太了解),而这个类的实例在添加@EnableSpringConfigured注解后将会被Spring创建,然后Spring将会结合LWT技术调用这个对象里面的方法对我们手动创建的Account对象的属性进行处理。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SpringConfiguredConfiguration.class) //然后我们去看看SpringConfiguredConfiguration干了什么
public @interface EnableSpringConfigured {

}
@Configuration
public class SpringConfiguredConfiguration {

/**
* The bean name used for the configurer aspect.
*/
public static final String BEAN_CONFIGURER_ASPECT_BEAN_NAME =
"org.springframework.context.config.internalBeanConfigurerAspect";

@Bean(name = BEAN_CONFIGURER_ASPECT_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AnnotationBeanConfigurerAspect beanConfigurerAspect() {
return AnnotationBeanConfigurerAspect.aspectOf();
}

}

从上可以看到,一个AnnotationBeanConfigurerAspect类型的Bean将会被Spring Container创建和管理,因此它就是让@Configurable注解起作用的核心。

public aspect AnnotationBeanConfigurerAspect extends AbstractInterfaceDrivenDependencyInjectionAspect
implements BeanFactoryAware, InitializingBean, DisposableBean{
//类的实现就不贴了,从它实现的接口也可以猜到一些东西
}

文档上面还提到,不仅仅是使用new创建的时候,在反序列化的时候,被@Configurable注解的类和使用new创建的时候一样,会被拦截然后注入属性

方便单元测试

当类没有被AspectJ动态织入(LoadTimeWeaving)的时候,@Configuration是不起任何作用的,因此对单元测试不会产生影响