上一节提到Spring之装配bean(一),我们已经了解到了装配的基础知识,这部分是更为高级的bean装配技术。
高级装配内容:
- spring profile
- 有条件的bean
- 处理自动装配的歧义性
- bean的作用域
1.spring profile
应用程序从一个环境迁移到另一个环境。开发阶段,某些环境相关做法可能并不适合迁移到生产环境中,甚至几遍迁移过去也无法正常工作,所以在不同的环境中使用的bean可能不同。下面以datasource为例:
在Java配置类中配置(“dev”和“prod”生产环境):
@Configuration
public class DataSourceConfig{
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sqsl")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource dataSource(){
JndiObjectFactoryBean jndi=new JndiObjectFactoryBean();
jndi.setJndiName("jdbc/myDS");
jndi.setResourceRef(true);
jndi.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndi.getObject();
}
}
在xml中配置:
<beans xmlns=".......">
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
<beans profile="qa">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close"
p:url="jdbc:h2:tcp://dbserver/~/test"
.......
p:maxActive="30"/>
</beans>
</beans>
激活(使用)哪一个profile:
<!--为应用山下文设置默认的profile-->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-name>dev</param-name>
</context-param>
<!--为Servlet设置默认的profile-->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
2.有条件的bean
假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后才会创建某个bean。在Spring4之前,很难实现这种级别的条件化配置,但是Spring4引入了一个新的@Conditioinal注解,他可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean就会被忽略。
有条件的bean一般在java配置注入bean的方法中使用到。
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(MagicExistsConditional.class) //条件化的创建bean
public MagicBean magicBean() {
return new MagicBean();
}
}
public class MagicExistsConditional implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment evn = context.getEnvironment();
return evn.containsProperty("magic");
}
}
如你所见,设置给@Conditional的类可以是任意实现了Condition接口的的类型。而实现这个接口只需要实现matches方法,如果matches方法返回true就创建该bean,如果返回false则不创建bean,上例中我们就是根据环境变量中是否存在magic变量,来决定matches的返回值,进而决定是否创建MagicBean的。
但是在实际生产中,ConditionContext context, AnnotatedTypeMetadata metadata这两个参数可以对类中的其他信息进行判断,有兴趣可以查一下这两个类的api。
3.处理自动装配的歧义性
如果不仅有一个bean能够匹配结果的话,这种歧义性阻碍Spring自动装配属性、构造器参数或方法参数。 下面是一个例子:
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
@Component
public class Cake implements Dessert{...}
@Component
public class Cookies implements Dessert{...}
@Component
public class IceCream implements Dessert{...}
在这里,当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。因而,Spring会抛出NoUniqueBeanDefinitionException;
解决方案1:将其中一个bean设置为首选
@Component
@Primary
public class Cake implements Dessert{...}
或者在xml中
<bean id = "iceCream"
class = "com.desserteater.Icecream"
primary = "true">
</bean>
解决方法2:使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小
@Autowired
@Qualifier("cake")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
4、bean的作用域
- 单例(singleton):在整个应用中,只创建bean的一个实例。
- 原型(prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Request):在Web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,他可以与@Component(自动装配)或@Bean(JavaConfig)一起使用。
例如,如果你使用组件扫描来发现声明bean,那么你可以在bean的类上使用@Scope注解,将其声明为原型bean,如下:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//@Scope("prototype")
public class Notepad{...}
或者
<bean id="notepad"
class="com.myapp.Notepad"
scope="prototype" />
使用会话和请求作用域
@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){...}
这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean 实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例。
@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。作用域代理能够延迟注入请求和会话作用域的bean。
在xml中注入:
<bean id="cart"
class="com.myapp.ShoppingCart"
scope="session">
<aop:scoped-proxy />
</bean>