Spring 装配 Bean 的方式
来源:Spring in Action 第2,3章
任何一个成功的应用都是由多个为了实现某一个业务目标而相互协作的组件构成的。Spring中对象无需自己查找或创建与其关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件,订单管理组件只要表面自己两手空空,容器就会主动赋予它一个信用卡认证组件。创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI) 的本质
一、常用的装配方式:
Spring有多种方式来装配Bean,作者的建议是尽可能使用自动配置的机制。显示配置越少越好。当必须使用显示配置Bean的时候,作者推荐使用类型安全并且比XML更加强大的JavaConfig。
1、自动化装配(隐式)
Spring从两个角度来实现自动化装配:
组件扫描(component scanning): Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring): Spring自动满足bean之间的依赖。
1.1、创建可被发现的bean
//CompactDisc接口在Java中定义CD的概念
package soundsystem;
public interface CompactDisc{
void play();
}
//带有@Component注解的CompactDisc实现类SgtPeppers
package soundsystem;
import org.springframework.stereotype.Component
@Component
public class SgtPeppers implements CompactDisc{
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play(){
System.out.println("Playing " + title + " by " + artist);
}
}
@Component这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显示配置SgtPeppers bean, 因为这个类使用了@Component注解。
不过,组件扫描默认是不启用的,所以还需要显示配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
//@ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.componentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig{}
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包
1.2、为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID,默认给bean指定的ID为类名的第一个字母变为小写。也可以将ID作为值传给@Component注解,比如
@Component("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
...
}
还有另外一种bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注入规范提供的@Named注解来为bean设置ID:
package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
...
}
1.3、设置组件扫描的基础包
可以扫描不同的包,只要在@ComponentScan的value属性中指明包的名称
@Configuration
@ComponentScan("soundsystem")
public CDPlayerConfig{}
或者
@Configuration
@ComponentScan(basePackages={"soundsystem","video"})
public CDPlayerConfig{}
如果所有对象都是独立的,彼此之间没有任何依赖,那么所需要的可能就是组件扫描而已。但是,很多对象会依赖其他对象才能完成任务。这样的话,就需要一种方法能够将组件扫描得到的bean和它们的依赖装配到一起。下面了解一下Spring自动化装配的另外一方面内容,自动装配。
1.4、通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助于@Autowired注解。
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer{
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play(){
cd.play;
}
}
这个程序在构造器上添加了@Autowired注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
在Spring初始化bean之后,它会尽可能得去满足bean的依赖。在本例中依赖是通过带有@Autowired注解的方法进行声明的。不管是构造器,Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,这个bean将会被装配进来。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常,可以通过将@Autowired的required属性设置为false来避免异常的出现。如果有多个可匹配的bean,将会产生歧义,后面会讨论。
2、使用Java代码装配Bean(显式)
如果要将第三方库的组件装配到自己的应用中,是没有办法在它的类上添加@Component和@Autowired注解的,此时可以用显示配置。进行显示配置时,JavaConfig时更好的方案,因为它更为强大,类型安全并且对重构友好。同时JavaConfig与其他Java代码有所不同,它与应用程序中的业务逻辑和领域代码是不同的。JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码中去。通常将JavaConfig放到单独的包中,与应用程序逻辑区分开来。下面看一下如何通过JavaConfig显示配置Spring。
2.1、创建配置类
package soundsystem;
import org.springframework.context.annotation.Configureation;
@Configuration
public class CDPlayerConfig{
}
创建JavaConfig的关键在于为其添加@Configuration注解,这个注解表明这是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。(注意:此时将前面例子中CDPlayerConfiguration的@ComponentScan注解移除掉)
2.2、声明简单的bean
要在JavaConfig中声明bean,需要编写一个方法来创建所需类型的实例,然后给这个方法添加@Bean注解,比如说下面的方法代码声明了CompactDisc bean:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean注解会告诉Spring这个方法会返回一个对象,该对象要注册为Spring应用上下文中的bean。
2.3、借助JavaConfig实现注入
上面的例子非常简单,因为它本身没有其他的依赖,但是如果声明一个CDPlayer bean,需要依赖于CompactDisc。在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
cdPlayer()方法就像是sgtPeppers()方法一样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。(注意:cdPlayer()的方法体并没有使用默认的构造器创建实例,而是调用了需要传入CompactDisc对象的创建器来创建CDPlayer实例。因为sgtPepper()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。默认情况下,Spring中的bean都是单例的。)
需要提醒的是,我们在这里使用CDPlayer的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说通过Setter方法注入CompactDisc:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
再次强调一遍,带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例,构造器和Setter方法只是@Bean方法的两个简单阳历,这里所存在的可能性仅仅受到Java语言的限制。
3、通过XML装配Bean(显示)
二、高级装配
前面介绍了最为核心的bean装配技术,但是bean装配所涉及的领域不仅仅局限于那些,这里讲深入介绍一些这样的高级技术。(很有必要)
1、Spring profile
在开发软件的时候,一个很大的挑战就是将应用程序从一个环境迁移到另一个环境。开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即使迁移过去也无法正常工作。比如数据库配置。在开发环境中我们可能会使用嵌入式数据库,并预先加载测试数据:
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql").build();
}
尽管EmbeddedDatabaseBuilder创建的DataSource非常适合于开发环境,但是对于生产环境来说这会是一个糟糕的选择。在生产环境的配置中,你可能会希望使用JNDI从容器中获取一个DataSource,比如
@Bean
public DataSource dataSource(){
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResouceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource)jndiObjectFactoryBean.getObject();
}
通过JNDI获取DataSource能够让容器决定该如何创建这个DataSource,甚至包括切换为容器管理的连接池。即便如此,JNDI管理的DataSource更加适合于生产环境,对于简单的集成和开发测试环境来说,这会带来不必要的复杂性。
在QA环境中,可以选择不同的DataSource配置,可以配置为Commons DBCP连接池:
@Bean(destroyMethod="close")
public DataSouce dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUsername("sa");
dataSource.setPassword("password");
dataSource.setInitialSize(20);
dataSource.stMaxActive(30);
return dataSource;
}
1.1、配置profile bean
未完待续