1.12. Java-based Container Configuration
1.12.1. Basic Concepts: @Bean and @Configuration
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
通常使用以上方式实现基于注解的bean配置。这种配置方式与下面的xml配置语义上等价:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration vs “lite” @Bean mode?
所有的@Bean注解都使用在@Configuration注解的类中时称为Full @Configuration mode;否则,称为“lite” @Bean mode,比如在@Component注解的类中使用@Bean。
lite @Bean mode:
@Component
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
在正常的场景中,Spring建议使用Full @Configuration。后面的章节会进行深入讨论。
1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext
本节讲述基于注解方式SpringContainer的几种初始化方式。使用AnnotationConfigApplicationContext作为容器实现进行初始化。
- 如果使用的是@Configuration,@Configuration注解的类本身与所有@Bean所注解的方法全部作为bean定义注册到容器中
- 如果使用的是@Component或者JSR-330注解,同样作为bean定义注册到容器,而且在这些被注解的类中可以使用@Autowired 和 @Inject
Simple Construction
使用配置类初始化:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
使用@Componet注解或者 JSR-330 注解的类初始化(甚至至POJO都可以):
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
Building the Container Programmatically by Using register(Class<?>…)
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();
}
Enabling Component Scanning with scan(String…)
以下两种自动扫描的方式等价:
@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);
}
Support for Web Applications with AnnotationConfigWebApplicationContext
待补充
1.12.3. Using the @Bean Annotation
Declaring a Bean and Bean Dependencies
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
该定义方式类似于构造函数实例化的方式。
Receiving Lifecycle Callbacks
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();
}
}
Spring自动推断close或shutdown为这两种方法为销毁方法。所以如果实现了名为这两个的方法,Spring会识别该方法并在合适的时间执行销毁方法。如果想要禁止Spring的这种自动推断行为,自己管理该声明周期,可以使用如下方式,将destroyMethod设置为空:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
Specifying Bean Scope
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope and scoped-proxy
@Scope支持指定bean的代理类型:
@Bean
@Scope(proxyMode = ScopedProxyMode.NO)
public AccountRepository accountRepository() {
return new AccountRepositoryImpl();
}
Customizing Bean Naming
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean Aliasing
bean的别名的基于注解的设置方法:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean Description
This can be particularly useful when beans are exposed (perhaps through JMX) for monitoring purposes.
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4. Using the @Configuration annotation
@Configuration用来配置bean定义,与@Bean共用。
Injecting Inter-bean Dependencies
如果bean之间有依赖,@Configuration中可以直接使用方法指定依赖的bean,语义跟java的工厂方法语义是不同的,@Configuration注解的对象通过代理实现了依赖语义,这样保证beanTwo()方法在这里返回的是单例。
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
如果将上面的@Configuration替换成@Componet,调用beanTwo() 只会得到一个新的对象:简单的工厂方法调用。如果要实现跟上面代码相同的语义效果,需要像这样实现:
@Component
public class AppConfig {
@Autowired
private BeanTwo beanTwo;
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo);
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
Lookup Method Injection
之前已经学习过xml版的,现在给出java注解版的示例:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
Further Information About How Java-based Configuration Works Internally
All @Configuration classes are subclassed at startup-time with CGLIB. In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.
1.12.5. Composing Java-based Configurations
Using the @Import Annotation
跟xml中的import用法相同,用于导入其他配置类。
@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();
}
}
这种方式让我们可以将bean的配置分散到不同的配置类中,使用时用import导入配置类。
Injecting Dependencies on Imported @Bean Definitions
不同的配置类中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");
}
下面使用@Autowired方式注入,与上面代码的效果相同:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
@Autowired
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
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");
}
Fully-qualifying imported beans for ease of navigation
之前的定义方式是隐式注入的,比如:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
用户并不能从上面的ServiceConfig类中明确知道AccountRepository到底是怎么注入的,AccountRepository 的实现具体是哪个类。如果有这样的需求,可以通过如下方式实现:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
Conditionally Include @Configuration Classes or @Bean Methods
可以通过实现org.springframework.context.annotation.Condition接口,过滤出符合条件的@Configuration注解的类或者@Bean注解的方法,以供Spring Container使用,符合条件返回true,不符合条件返回false:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 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;
}
注解@Conditional:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
通过将该注解注释到配置类,并指定Condition的实现类,就可以实现过滤功能。
Combining Java and XML Configuration
有两种方式来混合使用java和xml配置方式。
XML-centric Use of @Configuration Classes
以xml为中心的使用方式。即初始化的容器类型为:ClassPathXmlApplicationContext。在xml中指定自动扫描的包即可。
@Configuration Class-centric Use of XML with @ImportResource
以@Configuration类为中心的配置方式。即初始化的容器类型为:AnnotationConfigApplicationContext。可以使用@ImportResource导入xml的配置:
@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);
}
}
以上混合使用方式主要是针对某些场景使用某种方式更擅长的情况时,可以混合搭配使用。个人倾向于要么使用注解方式,要么使用xml方式,减少混用。
总结,本章详细介绍了基于java注解的配置方式。
网友评论