前言
前面講解了
bean
的核心裝配技術,其可應付很多中裝配情況,但Spring
提供了高級裝配技術,以此實現更為高級的bean
裝配功能。
高級裝配
配置profile bean
將所有不同
bean
定義放置在一個或多個profile
中,在將應用部署到每個環境時,要確保對應的profile
處於激活
狀態。如配置了如下數據源,並使用profile
注解定義。
JavaConfig配置profile
- 開發環境中的數據源配置
package com.hust.grid.leesf.ch3;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuratoin
@Profile("dev")
public class DevelopmentProfileConcifg {
@Bean(destroyMethod = "shutdown")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
- 生產環境下的數據源配置
package com.hust.grid.leesf.ch3;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;
@Configuration
public class ProductionProfileConfig {
@Bean
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
只有在
prod profile
激活時,才會創建對應的bean
。在Spring 3.1
之前只能在類級別上使用@Profile
注解,從Spring 3.2
之后,可以從方法級別上使用@Profile
注解,與@Bean
注解一起使用,上述放在兩個不同配置類可以轉化為兩個方法放在同一個配置類中。
package com.hust.grid.leesf.ch3;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
注意:盡管配置類中配置了不同的
Profile
,但只有規定的profile
激活時,對應的bean
才會被激活。
XML配置profile
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
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
。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<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:driverClassName="org.h2.Driver"
p:username="sa"
p:password="password"
p:initialSize="20"
p:maxActive="30" />
</beans>
<beans profile="prod">
<jee:jndi-lookup id="dataSource"
jndi-name="jdbc/myDatabase"
resource-ref="true"
proxy-interface="javax.sql.DataSource" />
</beans>
</beans>
三個
bean
的ID
都是dataSource
,在運行時會動態創建一個bean
,這取決激活的哪個profile
。
激活profile
Spring
依賴spring.profiles.active
和spring.profiles.default
兩個屬性確定哪個profile
處於激活狀態,如果設置了spring.profiles.active
,那么其值用於確定哪個profile
是激活狀態,如果未設置,則查找spring.profiles.defaults
的值;如果均未設置,則沒有激活的profile
,只會創建那些沒有定義在profile
中的bean
。如下是在web.xml
中設置spring.profiles.default
。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
...>
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<servlet>
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
Spring
提供了@ActiveProfiles
注解啟用profile
。
@Runwith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
...
}
條件化的bean
使用
@Conditional
注解,如果給定條件計算結果為true
,那么創建bean
,否則不創建。
- MagicBean
@Bean
@Condition(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
- MagicExistsCondition
package com.hust.grid.leesf.ch3;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
public class MagicExistsCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
處理自動裝配的歧義性
如下代碼
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
其中Dessert
為一個接口,其有多個子類。
@Component
public class Cake implements Dessert {}
@Component
public class Cookies implements Dessert {}
@Component
public class IceCream implements Dessert {}
此時,會發現不止一個
bean
可以匹配,Spring
會拋出異常,可以將某個bean
設置為首選的bean
或使用限定符。
標識首選bean
使用
Primary
注解標識首選bean。
@Component
@Primary
public class IceCream implements Dessert {}
或者使用xml配置首選bean
<bean id="iceCream"
class="com.dessertteater.IceCream"
primary="true" />
如果配置多個首選
bean
,那么也將無法工作。
限定自動裝配的bean
使用
@Qualifier
注解進行限定。
@Autowired
@Qualifier("iceCream")
pulbic void setDessert(Dessert dessert) {
this.dessert = dessert;
}
- 創建自定義限定符
可以為
bean
設置自己的限定符,而不依賴將bean ID
作為限定符,在bean
的聲明上使用@Qualifier
注解,其可以與@Component
組合使用。
@Component
@Qualifier("cold")
public class IceCream implements Dessert {}
這樣,使用如下。
@Autowired
@Qualifier("cold")
pulbic void setDessert(Dessert dessert) {
this.dessert = dessert;
}
- 使用自定義的限定符注解
如果多個
bean
都具備相同特性的話,那么也會出現問題,無法確定唯一bean
,如定義@Cold
注解
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
#Qualifier
public @interface Cold {}
這樣就可以使用如下注解進行定義
@Component
@Cold
@Creamy
public class IceCream implements Dessert {}
通過自定義注解后,然后可以通過多個注解的組合確定唯一一個符合條件的
bean
。
@Autowired
@Cold
@Creamy
pulbic void setDessert(Dessert dessert) {
this.dessert = dessert;
}
bean的作用域
默認情況下,
Spring
上下文中所有bean
都是作為以單例形式創建的。但有時候需要多個不同的bean
實例,Spring
定義了多種作用域,包括:
- 單例,整個應用中,只創建一個
bean
實例。 - 原型,每次注入或者通過
Spring
應用上下文獲取時,都會創建一個新的bean
實例。 - 會話,在
Web
應用中,為每個會話創建一個bean
實例。 - 請求,在
Web
應用中,為每個請求創建一個bean
實例。
使用@Scope
注解確定bean
的作用域,如將如下bean
聲明為原型。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class NotePad {}
當使用xml文件配置時如下
<bean id="notepad"
class="com.hust.grid.leesf.Notepad"
scope="prototype" />
使用會話和請求作用域
在
Web
應用中,可能需要實例化在會話和請求范圍內共享的bean
,如電商網站,需要會話作用域。
@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {}
需要將
ShoppingCart bean
注入到單例StoreService bean
中。
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
}
此時,由於
ShoppingCart
是會話作用域,直到某個用戶創建了會話后,才會出現ShoppingCart
實例,並且Spring
會注入一個代理至StoreService
中,這個代理與ShoppingCart
有相同的方法,當處理時需要將調用委托給會話作用域內真正的ShoppingCart
。
在XML中聲明作用域代理
需要使用
Spring aop
命名空間的新元素
<bean id="cart"
class="com.hust.grid.leesf.ShoppingCart"
scope="session">
<aop:scoped-proxy />
</bean>
上述情況會使用
CGLib
創建目標類的代理,但也可將proxy-target-class
屬性設置為false
,進而要求它生成基於接口的代理。
<bean id="cart"
class="com.hust.grid.leesf.ShoppingCart"
scope="session">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
為使用
<aop:scoped-proxy>
元素,需要在XML中聲明spring-aop.xsd
命名空間。
運行時值注入
不使用硬編碼注入,想讓值在運行時確定,
Spring
提供了如下兩種方式。
- 屬性占位符
- Spring表達式語言
注入外部的值
聲明屬性源並通過
Spring
的Environment
來檢索屬性。
...
@Configuration
@PropertySource("classpath:/com/hust/gird/leesf/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public BlankDisc disc() {
return new BlankDisc(
env.getProperty("disc.title"),
env.getProperty("disc.artist"));
}
}
通過在
app.properties
中配置對應的屬性完成注入。還可使用占位符完成注入。
public BlankDisc(
@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
為使用占位符,需要配置
PropertySourcesPlaceholderConfigurer
。
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
或者在XML配置文件中使用
<context:property-placeholder />
,這樣會生成一個PropertySourcesPlaceholderConfigurer
的bean
。
總結
本篇學習了更為高級的裝配技巧,如
Spring profile
,還有條件化裝配bean
,以及bean
的作用域等等。