前言
本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址
之前介绍了如何使用XML及注解的方式配置容器,接下来介绍使用Java 代码中来配置Spring 容器。
1. @Bean和@Configuration注解
Spring 基于Java 配置支持的核心注解是 @Configuration(类级别)和@Bean(方法级别)。
@Bean注解标识于方法返回一个实例,并初始化到Spring IoC容器进行管理。@Bean注解扮演着与<bean/>
标签相同的角色。可以将@Bean与 Spring 中@Component注解一起使用 。但是一般与@Configuration一起使用。
被@Configuration标识的类表明它的主要目的是作为 bean 定义的来源。最简单的@Configuration类如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的AppConfig类等效于以下 Spring XML <beans/>
:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
2. 使用AnnotationConfigApplicationContext实例化 Spring 容器
Spring 3.0 中引入AnnotationConfigApplicationContext容器,这种通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受@Component类和用 JSR-330 元数据注解的类。
当提供@Configuration类作为输入时,@Configuration类本身被注册为 bean 定义,并且@Bean类中方法声明的类也被注册为 bean 定义。
当提供@Component和 JSR-330 类时,它们都会被注册为 bean 定义,并且 DI 元数据如@Autowired或@Inject在必要时会在这些类中使用。
2.1 使用简单的构造函数
与ClassPathXmlApplicationContext在实例化时使用 Spring XML 文件作为输入的方式大致相同 ,可以在实例化AnnotationConfigApplicationContext时输入被@Configuration注解标识的类,这种方式完全无 XML 即可使用 Spring 容器,如以下示例所示:
首先添加Java配置类:
@Configuration
public class AnimalConfig {
@Bean()
public Animal animal() {
return new Animal("AnimalConfig",18);
}
}
然后在AnnotationConfigApplicationContext 的构造函数中传入当前@Configuration类,即可将@Bean标记方法返回的对象注入到IOC中。
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnimalConfig.class);
Animal animal = applicationContext.getBean("animal", Animal.class);
animal.eat("mike");
AnnotationConfigApplicationContext不仅限于使用@Configuration类。任何@Component或 JSR-330 注解类都可以作为构造函数的参数输入。
2.2 使用编程方式构建容器
可以使用AnnotationConfigApplicationContext的无参数构造函数实例化容器 ,然后使用register()方法配置。这种方法在以编程方式构建AnnotationConfigApplicationContext。
以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AnimalConfig.class);
applicationContext.refresh();
Animal animal = applicationContext.getBean("animal", Animal.class);
animal.eat("mike");
2.3 启用组件扫描 scan(String…)
要启用组件扫描,可以@Configuration类中添加@ComponentScan注解:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
// ...
}
在前面的示例中,会扫描com.acme包以查找任何带 @Component注解的类,并将这些类注册为容器内的 Spring bean 。而AnnotationConfigApplicationContext的scan(String…)方法允许相同的组件扫描功能,如以下示例所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
2.4 支持 Web 应用程序 的AnnotationConfigWebApplicationContext
AnnotationConfigWebApplicationContext
是AnnotationConfigApplicationContext的变体,在使用Spring MVC时,配置 ContextLoaderListener(servlet 侦听器)、Spring MVC DispatcherServlet等就是使用此实现类。
以下web.xml代码段配置了一个典型的 Spring MVC Web 应用程序(注意contextClasscontext-param 和 init-param 的使用):
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
3. 使用@Bean注解
@Bean是方法级别的注解,和 XML<bean/>
元素起相同的作用。该注解支持<bean/>
提供的一些属性,例如:
-
init-method
-
destroy-method
-
autowiring
-
name
可以在@Configuration或 @Component标识的类中使用@Bean注解。
3.1 声明一个 Bean
要声明 bean,可以对方法使用@Bean注解。默认情况下,bean 名称与方法名称相同。以下示例显示了@Bean方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
前面的配置完全等同于以下 Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明的bean名称都为 的 transferService, ApplicationContext绑定到对象实例为TransferServiceImpl,如下面的文本所示:
transferService -> com.acme.TransferServiceImpl
还可以在使用@Bean的方法返回值使用接口(或基类)返回类型,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
3.2 Bean 依赖
如果@Bean注册的对象,需要依赖其他Bean时,可以通过该方法的参数注入,例如,TransferService 需要一个AccountRepository,可以使用方法参数传入该依赖项,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入几乎相同。
3.3 接收生命周期回调
任何@Bean注解定义的类都支持常规生命周期回调,可以使用JSR-250 中的@PostConstruct和@PreDestroy注解。
也完全支持常规的 Spring生命周期回调。如果 bean 实现InitializingBean、DisposableBean或Lifecycle接口,则容器会调用它们各自的方法。
也完全支持标准Aware接口集(例如BeanFactoryAware、 BeanNameAware、 MessageSourceAware、 ApplicationContextAware等)。
@Bean注解支持指定任意的初始化和销毁回调方法,很像 Spring XML中的bean标签的init-method和destroy-method属性,如以下示例所示:
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();
}
}
3.4 指定 Bean 作用域范围
@Scope注解可以指定 bean 的范围,以前已经介绍过@Scope的用法。
@Bean
@Scope(value="session")
public Animal animal() {
return new Animal("AnimalConfig",18);
}
3.5 自定义 Bean 命名
默认情况下,配置类使用@Bean方法的名称作为 bean 的名称。但是,可以使用name属性覆盖此功能,如以下示例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
3.6 Bean别名
有时需要给单个 bean 定义多个名称,也称为 bean 别名。 @Bean注解的name属性接受一个 String 数组。以下示例显示如何为 bean 设置多个别名:
@Configuration
public class AppConfig {
@Bean({
"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
3.7 Bean描述
有时,提供 bean 的更详细的文本描述会很有帮助。当 bean 被公开(可能通过 JMX)用于监视目的时,这可能特别有用。
要将描述添加到@Bean,您可以使用 @Description注解,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
4. 使用@Configuration注解
@Configuration是一个类级别的注解,表明当前对象是 bean 定义的来源。
@Configuration类通过@Bean方法声明 bean 。@Configuration类方法的调用也可用于定义 bean 间的依赖关系。
4.1 注入bean 之间的依赖
当 bean 相互依赖时,可以直接调用方法即可,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
这种 bean 间依赖关系的方法仅在该方法在@Configuration类中声明时才有效。不能使用普通@Component类来声明 bean 间的依赖关系。
4.2 查找方法注入
查找方法注入是一项您应该很少使用的高级功能。在单例范围的 bean 依赖于原型范围的 bean 的情况下,它很有用。将 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();
}
通过使用 Java 配置,您可以创建一个子类,其中CommandManager抽象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();
}
}
}
4.3 基于 Java 的配置如何在内部工作的更多信息
以下示例显示了一个带@Bean注解的方法被调用了两次:
@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()被调用了两次,由于此方法创建了一个新实例ClientDaoImpl并返回它,通常希望有两个实例(每个服务一个)。这肯定会有问题:在 Spring 中,实例化的 bean默认有一个作用域singleton。这就是神奇之处:所有@Configuration类在启动时都使用CGLIB在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean。
5. 编写基于 Java 的配置
Spring 的基于 Java 的配置功能允许编写注解,这可以降低配置的复杂性。
5.1 使用@Import注解
@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();
}
}
现在,不需要在实例化上下文时同时指定ConfigA.class和ConfigB.class,只需要ConfigB显式提供,如下例所示:
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);
}
这种方法简化了容器实例化,因为只需要处理一个类,而不是要求您@Configuration在构造过程中记住大量的 类。
从 Spring Framework 4.2 开始,@Import也支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果您想避免组件扫描,这将特别有用,通过使用一些配置类作为入口点来显式定义所有组件。
5.2 有条件地包含@Configuration类或@Bean方法
有条件的启用或禁用一个完整的@Configuration类甚至单个@Bean方法通常很有用。一个常见的例子是@Profile只有在 Spring 中启用了特定配置文件时才使用激活 bean。
还有更灵活的注解@Conditional来完成条件匹配。
Condition接口的实现提供了一个matches(…) 返回true或者alse的方法。例如,以下清单显示了Condition用于的实际 实现@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;
}
5.3 结合 Java 和 XML 配置
Spring 的@Configuration类支持并不旨在 100% 完全替代 Spring XML。例如 Spring XML 命名空间,仍然是配置容器的理想方式。
在XML是方便或必要的情况下,你有一个选择:要么通过实例化容器中的“XML为中心”的方式,例如, ClassPathXmlApplicationContext或通过使用它实例化一个“Java为中心”的方式 AnnotationConfigApplicationContext和@ImportResource注释根据需要导入 XML。