目录
1、使用 AnnotationConfigApplicationContext 初始化 Spring 容器
(2)构造容器时,使用 register(Class…),传入注册类
(3)构造容器时,调用 scan(String…) 方法,启用组件扫描
(4)构造支持 Web 应用的 AnnotationConfigWebApplicationContext
(1)声明一个 Bean :@Configuration + @Bean / 使用接口的默认方法
(1)注入 @Configuration 内部的 bean 依赖
(1)使用 @Import 注解,从外部配置类加载 Bean 定义
(5)使用 @ImportResource 注解,根据需要导入 XML 配置
@Bean 注解,用来标识一个方法(工厂方法),该方法用来实例化、配置并初始化一个被 Spring 容器管理的新对象,@Bean 注解与 xml 配置中的 <bean/> 标签作用类似。虽然你可以把 @Bean 和 @Component 一起使用,但是,正常情况下,该注解更多的是和 @Configuration 一起使用。
@Configuration 注解,用来标识一个类,指定该类为 Bean 定义的资源文件。此外,在被 @Configuration 注解的类中,内部 Bean 的依赖可以通过调用同一个类中 @Bean 方法来进行注入。最简单的 @Configuration 类如下所示:
@Configuration
public class AppConfig {
@Bean //bean名称就是方法名称:myService
public MyService myService() {
return new MyServiceImpl();
}
}
完全 @Configuration 和精简 @Bean 模式
当 @Bean 方法被定义在一个不是由 @Configuration 注解的类中时,就认为使用了精简模式(比如定义在被 @Component 注解的类中)。 精简模式下的 @Bean 方法不能使用内部的 Bean 依赖(不能调用同一个类中 @Bean 方法),同时也不能使用 CGLIB 代理进行增强。
当 @Bean 方法被定义在一个由 @Configuration 注解的类中时,就认为使用了完全模式。在完全模式下,内部的 Bean 依赖被重定向到 Spring 容器的生命周期中进行管理,可以阻止 @Bean 方法被另一 @Bean 方法通过常规的 Java 代码进行调用,从而避免在精简模式下,出现一些比较难以追踪的 bug。// 所以还是推荐 @Bean + @Configuration 一起使用,此模式下,Bean 有一个完整的生命周期。
下边介绍使用 Java 配置创建 Spring 容器的几种方法。
1、使用 AnnotationConfigApplicationContext 初始化 Spring 容器
// 该节简单介绍了容器的一些实例化代码以及一个 Web 容器
AnnotationConfigApplicationContext 是 ApplicationContext 接口的一个实现类,提供了多样化的功能,不仅支持 @Configuration 注解的 class 作为资源文件,也支持 @Component 注解的配置,同时还支持 JSR-330 规范的注解。
(1)使用构造函数,简单构造一个容器
使用 xml 配置文件作为资源注入一样,需要实例化 ClassPathXmlApplicationContext 容器,使用 AnnotationConfigApplicationContext 跟上边情况类似,但是使用 @Configuration 注解标识配置文件,可以在完全不使用 xml 配置的情况下,实例化一个 Spring 容器:
public static void main(String[] args) {
// AppConfig为@Configuration注解的class
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
(2)构造容器时,使用 register(Class<?>…),传入注册类
你可以使用无参构造函数初始化一个 AnnotationConfigApplicationContext 实例,然后调用 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();
}
(3)构造容器时,调用 scan(String…) 方法,启用组件扫描
为了启用组件扫描,可以在 @Configuration 注解的 class 上进行如下配置:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
// ...
}
AnnotationConfigApplicationContext 提供了与 @ComponentScan 注解相同功能的方法 scan(String…),该方法同样支持容器扫描指定包路径下的组件类型的 calss(被 @Component 等注解标记的类), 示例如下:// 其中 refresh() 是一个很重要的方法
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh(); // 这是一个很重要的方法
MyService myService = ctx.getBean(MyService.class);
}
(4)构造支持 Web 应用的 AnnotationConfigWebApplicationContext
AnnotationConfigApplicationContext 的 WebApplicationContext 变体可通过 AnnotationConfigWebApplicationContext 获得。你可以在配置了 Spring ContextLoaderListener servlet 监听器、Spring MVC DispatcherServlet 等的时候,使用这个实现。下面的 web.xml 代码片段配置了一个典型的 Spring MVC web 应用程序:// 支持 Web 应用的容器拥有了更多可配置的属性
<web-app>
<!-- 配置ContextLoaderListener使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- contextConfigLocation的值必须由一个或多个有逗号或空格分隔的@Configuration类组成。使用完全限定的包路径主要为了支持组件扫描-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 像平常一样使用ContextLoaderListener引导根应用程序上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 像平常一样声明一个Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- 再一次, contextConfigLocation的值必须由一个或多个有逗号或空格分隔的@Configuration类组成。使用完全限定的包路径主要为了支持组件扫描 -->
<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>
2、使用 @Bean 注解
@Bean 是一个方法级别的注解,它支持以下一些属性的配置:// 可以配置方法名称,是否自动配置,初始化回调方法、销毁回调方法等
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* @AliasFor 用于为注解属性声明别名。
*/
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
(1)声明一个 Bean :@Configuration + @Bean / 使用接口的默认方法
下面的例子展示了 @Bean 的方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
你还可以使用默认方法来定义 bean。这允许在默认方法上实现带有 bean 定义的接口来组合 bean 配置。// 这个是要知道的,提前定义一些 bean
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
把 @Bean 方法的返回类型,声明为接口类型,示例如下:// 可以通过类型进行匹配,名称不重要,当只有一个实现类时,具体实现也不重要,但有多个实现时,就需要注意了
@Configuration
public class AppConfig {
@Bean //返回 TransferService 接口类型
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,对于实现多个接口的组件,或者对于可能由其实现类型引用的组件,声明最具体的返回类型会更安全。
(2)附加参数的 @Bean 方法
带有 @bean 注释的方法可以具有任意数量的参数,用于描述构建该 bean 所需的依赖项。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入非常相似。// 如果 AccountRepository 不存在,会报错
(3)@Bean 中的生命周期回调方法定义
任何使用 @Bean 注释定义的类都支持常规生命周期回调,并且可以使用 JSR-250 中的 @PostConstruct 和 @PreDestroy 注释。// 常规的生命周期方法可以在注解中指定
常规的 Spring 生命周期回调也得到了充分的支持。如果 bean 实现了 InitializingBean、DisposableBean 或 Lifecycle,容器将调用它们各自的方法。
感知接口的标准集(如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也得到了完全支持。
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 时,可以对对象做任何想做的事情,而不总是需要依赖容器生命周期。比如,在上述例子中的 BeanOne 中,在构造过程中直接调用 init() 方法,该方法同样有效,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
3、使用 @Configuration 注解
@Configuration 是一个类级注释,指示对象是 bean 定义文件。
(1)注入 @Configuration 内部的 bean 依赖
当 bean 之间存在依赖关系时,表达这种依赖关系就像让一个 bean 方法调用另一个 bean 方法一样简单,如下面的示例所示:// 该方式仅在 @Configuration 注解的类中有效
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
(2)基于原型 bean 的查找方法注入
查找方法注入是一个高级特性,日常开发中很少使用。它的主要应用于当单个作用域的 bean 依赖于原型作用域的 bean 时的场景。点击这里,查看具体使用。
(3)@Configuration 注解的工作流程
下面的例子,它显示了一个 @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();
}
}
在 Spring 中,实例化的 bean 默认为单例作用域。所有 @Configuration 类在启动时被 CGLIB 的子类化。在子类中,调用父方法并创建新实例之前,子类的方法首先会检查容器中是否有(限定作用域的)bean 的缓存。如果有缓存,就不会再创建新的 bean,所以,一个 @Bean 注释的方法被调用了两次,它也只会产生一个 bean 。
4、Java 配置中的其他注解
(1)使用 @Import 注解,从外部配置类加载 Bean 定义
<import/> 标签在 Spring XML 文件中可用于帮助模块化配置,同样,@Import 注解允许从另一个配置类加载 @Bean 定义,如下例所示:// @Import 注解在 Spring Boot 中非常有用
@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.class 就可以了, 如下例所示:
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() 方法。所以,如果你想要避免组件扫描,可以通过使用一些配置类作为入口点显式的定义所有组件。// Spring Boot 的组件自动配置
@Import 中跨配置类的依赖关系
前面的示例过于简单。在大多数实际场景中,bean 之间都存在着跨配置类的依赖关系。当使用 @Configuration 类时,Java 编译器会对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。// 是引用一个 bean 时,那个 bean 必须存在
@Bean 方法可以有任意数量的参数来描述 bean 的依赖关系。例如,以下几个 @Configuration 类,每个类都取决于在其他类中声明的 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");
}
在上边配置中,ServiceConfig.calss 需要 RepositoryConfig.class 中定义的 AccountRepository 实例,RepositoryConfig.class 需要 SystemTestConfig.calss 中定义的 DataSource,当其中任何一个实例缺失时,都会导致容器加载失败。
还有一种方法可以达到同样的效果。记住,@Configuration 类最终只是容器中的另一个 bean:这意味着它们可以像任何其他 bean 一样利用 @Autowired 注解或 @Value 注入以及其他的一些特性。
注意:利用 @Autowired 注解或 @Value 注入,需要确保以这种方式注入的依赖项只是最简单的类型,因为 @Configuration 类在容器初始化过程中会被很早的进行处理,以这种方式强制注入依赖可能会导致依赖的 bean 意外的过早初始化。所以,应该尽可能使用基于参数的注入,如前面的示例所示。// 使用参数注入时,还可以懒加载
下面的例子展示了如何将一个 bean 自动装配到另一个 bean:// 注入效果与前边示例一样
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
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");
}
基于构造器注入,需要依赖项被过早的初始化,在处理 @Configuration 类时,依赖项就已经被实例化了。
明确的指定导入的 Bean 依赖,确定 bean 定义声明的确切位置
在前面的场景中,使用 @Autowired 可以正常工作,而且提供了所需的模块化。但是并不能清晰的确定 bean 定义声明的确切位置。例如,作为查看 ServiceConfig 的开发人员,如何确切地知道 @Autowired AccountRepository 这个 bean 是在哪里声明的呢?
如果上边这种模糊性是不可接受的,并且你希望对引入的 bean 有一个明确的索引,你可以使用以下代码进行定义:// 明确 bean 定义的位置
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
上边代码中,定义 AccountRepository 的位置是完全显式的。但是,ServiceConfig.class 却和 RepositoryConfig.class 紧密的耦合在了一起。所以为了进行权衡。通过使用基于接口或基于抽象类的 @Configuration 类,可以在一定程度上缓解这种紧密的耦合。// 使用接口或抽象类可以进行解耦合
@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");
}
(2)使用 @Conditional 注解按条件进行匹配
根据系统的要求,通常需要有条件地启用或禁用一个完整的 @Configuration 类甚至单个的 @Bean 方法。一个常见的例子是,在 Spring 环境中启用特定的配置文件时,可以使用 @Profile 注解来激活指定的 beans。
@Profile 注释实际上是通过使用一个更灵活的注释 @Conditional 实现的。@Conditional 注解指示容器在注册 @Bean 之前,应该满足 org.springframework.context.annotation.Condition 的具体实现类定义的规则。
Condition 接口的实现提供了一个 matches(…) 方法,该方法返回 true 或 false。例如,下面代码展示了 @Profile 注解实现 Condition 接口的匹配过程:// 条件匹配
@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;
}
@Conditional 注解的接口文档,请点击这里。
(5)使用 @ImportResource 注解,根据需要导入 XML 配置
Spring 的支持 @Configuration 类并不是为了完全取代 Spring XML。 Spring XML 中的一些工具,如命名空间,仍然是理想的配置容器的方式。所以,在 XML 配置方便或必要的情况下,你可以有诸多选择:比如,使用 ClassPathXmlApplicationContext 以 XML 配置文件为中心的实例化容器方式,或者使用 AnnotationConfigApplicationContext 以 Java 配置为中心的实例化容器方式,使用 Java 配置方式,可以使用 @ImportResource 注解根据需要导入 XML 配置。
更多组合方式,请查看官方文档。