@Bean和@Configuration注解
Spring中基于Java配置的核心内容是@Configuration注解的类和@Bean注解的方法
@Bean注解用于表名方法的实例化,配置和初始化都是由Spring Ioc容器管理的新对象,就像在XML配置的元素一样,开发者可以在任意的Spring @Component中使用@Bean注解方法,但是大多数情况下@Bean是配合@Configuration使用的。
使用@Configuration注解类时,这个类的目的就是作为bean定义的地方。此外,@Configuration类内部的bean可以调用本类定义的其它bean作为依赖。
- 使用AnnotationConfigApplicationContext初始化Spring容器
Spring中的AnnotationConfigApplicationContext是Spring3.0总新增的内容,这是一个强大的ApplicationContext实现,它不仅能解析@Configuration注解类,也能解析@Component注解的类和使用JSR-330注解的类。
当使用@Configuration类作为输入时,@Configuration类本身被注册为一个bean定义,类中所有声明的@Bean方法也被注册为bean定义。
当使用@Component或者JSR-330类时,他们被注册为bean定义,并且假定在必要时在这些类中使用DI元数据,比如@Autowried 或 @Inject
Spring以XML作为数据配置实例化一个ClassPathXmlApplicationContext,以@Configuration类作为元数据配置时,Spring以差不多的方式实例化一个AnnotationConfigApplicationContext,因此,Spring容器可以实现零XIML配置
如下:AnnotationConfigApplicationContext不限于只使用@Configuration注解的类,任何添加@Component或JSR-330注解的类都能被提供给这个构造方法
public static void main(String[] args) { //AppConfig是添加了@Configuration或 @Component 或 JSR-330注解的类 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
public static void main(String[] args) { //假设MyServiceImpl Dependency1和Dependency2使用Spring依赖注入,例如@Autowired ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class,Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
使用register(Class<?>...)编程构建容器
AnnotationConfigApplicationContext可以通过无参构造函数实例化,然后调用register()方法进行配置,这种方式在以编程的方式构建AnnotationConfigApplicationContext时特别有用。如下
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class,OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.register(MyServiceImpl.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
使用scan(String...)扫描组件
要启用组件扫描,只要向下面这样配置@Configuration类即可
@Configuration @ComponentScan(basePackages = "com.acme") public class AppConfig{ }
上面的配置等同于如下的xml配置
<beans> <context:component-scan base-package="com.acme"/> </beans>
上面的例子中com.acme包会被扫描,只要使用了@Component注解的类,都会被注册进容器中,同样的AnnotationConfigApplicationContext公开的scan(String,,,)方法也允许扫描类完成同样的功能。需要注意的是,@Configuration类是@Component的元注解的类,所以也会被扫描到
使用AnnonationConfigWebApplicationContext支持Web应用
WebApplicationContext与AnnonationConfigApplicationContext的变种是AnnonationConfigWebApplicationContext配置,这个实现可以用于配置SpringContextLoaderListenerservlet监听器,Spring MVC的DispatcherServlet等等
2.使用Bean注解
@Bean是一个方法级别注解,他与XIML中的<bean/>元素类似,注解支持<bean/>提供的一些属性,例如 init-method destory-method ,autowiring 和 name
开发者可以在@Configuration类或者@Component类中使用@Bean注解
要申明一个bean,只需要使用@Bean注解方法即可,使用此方法,将会在ApplicationContext内注册一个bean,bean的类型时方法的返回值类型,默认情况下,bean名称将于方法名称相同。
如下:
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferService(); } }
同时5.0版本还可以使用接口返回类型申明@Bean方法
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
但是。这会将预先类型预测的可见性限制为指定的接口类型(TransferService),然后在实例化受影响的单一bean时,只知道容器的完整类型(TransferServiceImpl)非延迟的单例bean根据他们的申明顺序进行实例化,因此开发者可能会看到不同类型的匹配结果。
Bean之间的依赖
一个使用@Bean注解的方法可以具有任意数量的参数描述构建该bean所需的依赖,例如如果TransferService需要AccountRepository依赖,那么可以通过注入方法参数来实现该依赖
@Configuration public class AppConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } }
bean接受生命周期回调
使用Bean注解定义的任意类都支持产常规的生命周期回调,并且可以使用JSR-250的@PostConstruct和@PreDestory注解
注解完全支持常规Spring生命周期回调,如果bean实现了InitializingBean,DisposableBean或者Lifecycle,他们各自的方法将由容器调用。
同样的,标准的*Aware接口,如BeanFactoryAware,BeanNameAware,MessageSourceAware以及ApplicationContextAware等等都完全支持
默认情况下,使用Java Config定义的bean中close方法或者shutdown方法会作为销毁回调而自动调用,若bean中有close,shutdown方法,却不是作为销毁回调使用,通过设置@Bean(destroyMethod="")即可关闭默认的自动匹配销毁回调模式
使用@Configuration注解
@Configuration注解是一个类级别的注解,表明该类将作为bean定义的元数据配置,@Configuration类会将有@Bean注解的公开方法声明为bean
注入内部依赖
@Bean public Foo foo(){ return new Foo(bar()); }
查找方法注入
查找方法注入是一种高级功能,开发者应该很少使用,但是在一个单例bean依赖原型作用域bean的场景中,这种方法将会非常有用。
public abstract class CommandManager{ public Object process(Object commandState){ Command command=createCommand(); command.setState(commandState); return command.execute(); } protected abstract Command createCommand(); }
使用Java配置支持,开发者可以创建一个CommandManager的子类,其中抽象方法createCommand()方法将会被覆盖,这样就可以让它查找到新的原型command对象。
@Bean @Scope("propotype") public AsyncCommand asyncCommand(){ AsyncCommand command=new AsyncCommand(); return command; } @Bean public CommandManager commandManager(){ return new CommandManager(){ protected Command createCommand(){ return asyncCommand(); } } }
更多关于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()在clientService1()实例中调用一次,在clientService2()中调用一次,由于这个方法创建一个新的ClientDaoImpl实例并返回,通常期望有两个实例(每个服务创建一个实例)这肯定会引起生命周期的问题。
在Spring中,实例化bean默认情况下都是单例作用域的。这就是他的神奇之处,所有@Configuration类在启动时,都会通过CGLIB创建一个子类,在子类中,在调用父类的方法并创建一个新的实例之前,子类中的方法首先会检查是否缓存过该bean实例。
注意这种行为可以根据bean的作用域而变化,这里我们只讨论单例。
实际上还会有一些限制,因为CGLIB是在启动的时候动态额添加这些特性,所以配置类不能是final的,但是从4.3开始,允许在配置类上使用任何构造函数,包括使用@Autowired或一个非默认构造函数声明作为注入。
如果想避免因CGLIB带来的限制,请考虑声明非@Configuration类的@Bean方法,如果在纯的@Component类,这样在@Bean方法之间的交叉方法调用将会被被拦截,此时必须在构造函数或方法级别上进行依赖注入。
bean在配置类之间相互依赖,@Bean方法可以使用任意的参数,用于描述其依赖,现在考虑一个真实的场景,使用多个@Configuration类,每个配置都依赖其它配置中的bean声明。
@Configuration public class ServiceConfig{ @Bean public TransferService transferService(AccountRepository accountRepository){ return new TransferServiceImpl(accountRepository); } }
还有另一种方法来实现相同的结果,记住@Configuration类也是容器中的一个bean,这意味着他们可以向任何其它bean一样使用@Autowired和@Value注解。
警告:确保你的注入依赖关系是最简单的类型,@Configuration类会在上下文初始化的早期被处理,所以他的依赖会在早期被初始化,如果可能的话,请像上面那样使用参数化注入。
同样的,对于通过@Bean声明的BeanPostProcessor和BeanFactoryPostProcessor请谨慎对待,这些通常应该声明为“静态的@Bean”方法,不会触发包含他们的配置类的实例化,否则@Autowired和@Value在配置类本身上是不起作用的,因为他们过早的被实例化。
有条件的包含@Configuration类或@Bean方法
根据一些任意的系统状态,有条件的启用或禁用一个完整的@Configuration类,甚至单个的@Bean方法通常都是有用的,一个常见的例子是,只有在Spring环境中启用了特定的配置文件时,才使用@Profile注释来激活bean。
@Profile注解实际上是使用一个更灵活的注解--@Conditional实现的。@Conditional注解表示特定的Condition实现,她表明@Bean被注册之前会先询问@Condition注解。
Condition接口的实现只有一个返回true或false的matches(...)方法
基于@Configuration混合XML的@ImportResource
在@Configuration类为配置容器的主要方式的应用程序中,也需要一些XML的配置,在这种情况下,只需要使用@ImportResource,并自定义所需的XML,这样做实现了一种以Java为主,的方法来配置容器并将尽可能少的使用XML。
@Configuration @ImportResource("classpath:/com/../properties-config.xml") public class AppConfig{ @Value("${jdbc.url}") private String url; @Value("${jdbc.password}") private String password; @Bean public DataSource datasource(){ return new DriverManagerDataSource(url,password); } }
<beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.password=123