围绕着@Bean
和@Configuration
为中心:@Configuration
的类里面有@Bean
的方法生成bean。
使用@Configuration
可以获得增强的CGLIB支持。
full还是lite的@Bean模式:
定义在非@Configuration
类里的@Bean方法被认为是lite模式。
Lite模式的@Bean方法不能声明bean内依赖,只能操作其容器类的内部有的,或者它的方法参数。
这种@Bean方法不能调用别的@Bean方法,仅仅作为bean的工厂方法参考,没有运行时意义。
正向的副作用是在类设计方面没有限制,比如final。
Full模式@Bean方法的跨方法引用都会被重定向到容器的生命周期管理,
可以防止同样的@Bean方法被普通的Java代码调用,以减少不易察觉的bug。
通过AnnotationConfigApplicationContext类实例化Spring容器:
不止可以接受@Configuration
类,还能接受@Component和JSR-330注解类。
当@Configuration
类作为参数时,这个类和类内的@Bean方法都会被注册成bean定义。
当@Component
和JSR-330类作为参数时,会被注册为bean定义,并假定用了@Autowired和@Inject。
如何使用:new一个AnnotationConfigApplicationContext
类,将配置类作为参数传递进去:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
它的构造函数接受可变参数,想传递几个类就传递几个。
编程式构建容器:调用它的register方法注册bean:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
开启组件扫描:
@Configuration类添加@ComponentScan:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
或者手动启动扫描:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
两者都能实现同样的功能,而且scan方法支持可变参数。
等价的xml配置:
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
注意:@Configuration注解含有@Component元注解,所以也可以是候选。
使用@Bean注解:
用于方法,用这个方法在容器内注册bean definition,类型是方法的返回值。
默认,bean的名称就是方法名:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
等价于xml中的:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Bean方法的返回类型也可以是接口类型:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是这样会限制更进一步的类型匹配,容器仅在实例化这个bean的时候知道它的完全类型。
非lazy初始化的bean按照定义顺序进行实例化,可能会导致不同的匹配结果。
对于实现了多个接口的bean最好定义成返回最确切的类型。
bean依赖:2
可以将任意数量的依赖作为@Bean方法的参数,和基于构造器的DI一样:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
生命周期调用:
可以使用来自JSR-250的@PostConstruc
和@PreDestroy
注解;
bean实现InitializingBean
, DisposableBean
, 或Lifecycle
接口;
实现标准的*Aware之类的接口;
@Bean注解提供的两个属性:initMethod和destroyMethod。
比如:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在Java配置模式下如果bean有一个public的名为close
或shutdown
方法会自动当作销毁调用。
可以用@Bean(destroyMethod=””)将其禁用(inferred模式)。
可以通过直接调用初始化方法达成相同的目的:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
使用@Scope声明bean作用域:
默认bean是singleton作用域,可声明为别的:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope和scoped-proxy:
用于不同作用域的依赖注入。
创建一个代理最简单的方法就是在xml配置文件里添加:<aop:scoped-proxy/>元素。@Scope
注解提供等价的proxyMode属性,默认值是ScopedProxyMode.DEFAULT,不创建scoped-proxy
,除非在扫描阶段提供不同的默认值。
可选的值为:ScopedProxyMode.TARGET_CLASS
,ScopedProxyMode.INTERFACES
或ScopedProxyMode.NO
。
定制bean名称:
默认的bean名称就是工厂方法名,通过@Bean注解的name属性重新设定:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
bean别名:
name属性接受一个字符串数组参数,可以给bean声明多个别名/名称:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
bean描述:
提供更多的bean的细节描述,通过@Description注解:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
使用@Configuration注解:
类级别注解,表示一个对象就是一个bean的定义源。
通过@Bean方法声明bean。也可以用于直接调用bean方法达成bean内依赖:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
注意:仅可用于@Configuration
类内!
方法注入:
singletin作用域的bean依赖于prototype作用域的bean。反正少用就对了,创建singleton bean的时候创建一个prototype的bean就行。
更多的@Configuration的内部信息:
看看下面的代码片段:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()创建了几个实例?实际上就创建了一个实例。
@Configuration类在启动时就成为CGLIB的子类,所以@Configuration类不能是final的。
其内部的方法被调用时会先检查容器,如果容内没有这个bean,就会调用bean的工厂方法生成一个实例,生成后bean实例后就相当于被缓存了。
有些许限制,由于在开始时候CGLIB动态新增的功能,@Configuration类不可以是final的,但是允许有构造器。如果想要避开这个限制就不要使用@Configuration,但是不能进行bean内依赖。
组合基于Java的配置
使用@Import
注解:从别的类导入bean定义:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
使用时只要注册B类就能获取到类A的bean:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
可以导入没有注解的类,类似于通过AnnotationConfigApplicationContext.register方法实现的。
在不想被自动扫描的时候很有用。
在bean定义之间注入依赖:
反正都是围绕容器转的,只要将依赖作为工厂方法参数即可完成自动注入:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
注意:最好别将依赖搞的很复杂。@Configuration类很早就被初始化,这时候的依赖注入可能会导致无法预料的过早初始化。
同样需要注意通过@Bean
定义BeanPostProcessor
和BeanFactoryPostProcessor
,这些方法应该被声明称static,这样就不会触发初始化它们的容器类。否则@Configuration类的@Autowired
和@Value
可能会不工作,因为可以在AutowiredAnnotationBeanPostProcessor
之前实例化bean。
使用全限定路径导入bean让代码更好理解:
如果要在代码里寻找类似@Autowired AccountRepository的内容会很麻烦。
如果不想找来找去,可以考虑在代码里装配配置类:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// 通过bean方法直接获得bean
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
但是这样ServiceConfig就和RepositoryConfig紧耦合了。可以通过装配接口类型缓解。
条件化包含@Configuration类或@Bean方法:
有条件地开启或禁用@Configuration类或@Bean方法,可以让代码有多种状态,如开发版,测试版等。
常用@Profile注解。可以通过Spring Environment
修改启用的profile。
@Profile被更灵活的@Conditional
注解实现,指示特定的org.springframework.context.annotation.Condition实现类应该在bean被注册之前访问。
Condition接口有一个返回boolean名为matches的方法,比如@Profile的实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
组合Java配置和xml配置
@Configuration类并不是为了100%替代xml而生的。
Java配置为中心添加xml:使用@ImportResource注解来导入xml配置。
xml配置为中心添加Java配置:将@Configuration
类声明成<bean/>元素:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
使用:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("xxx.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
由于开启了<context:annotation-config/>,所以AppConfig内的bean都会被处理。
${jdbc.url}等样子的代码是占位符,Spring会读取配置文件,就是 <context:property-placeholder location=”classpath:/com/acme/jdbc.properties”/>,之后会自动替换。
jdbc.properties文件:
jdbc.url=jdbc:mysql://localhost/xdb
jdbc.username=sa
jdbc.password=pw
或者通过<context:component-scan/>开启自动扫描。
Java配置包含xml:
使用@ImportResource注解作用在类上:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml文件:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>