在Spring-IOC基礎(chǔ)中我們學(xué)習(xí)了核心的bean裝配技術(shù),但是bean裝配所涉及的領(lǐng)域并不僅僅局限于Spring-IOC基礎(chǔ)中我們學(xué)習(xí)的內(nèi)容,Spring提供了多種技巧,借助他們可以實(shí)現(xiàn)更為高級的bean裝配功能
環(huán)境與profile
Spring提供了profile解決多個(gè)環(huán)境之間的切換問題
-
JavaConfig中使用profile
在Java配置中可以使用@Profile注解指定某個(gè)bean屬于哪個(gè)profile,@Profile注解源碼如下:
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Profiles;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
為了測試效果,我們可以定義一個(gè)DataSource類如下:
package com.tp.datasource;
/**
* FileName: DataSource
* Author: TP
* Description:
*/
public class DataSource {
public DataSource() {
System.out.println("無參構(gòu)造:DataSource被實(shí)例化");
}
private String userName;
private String passWord;
private String url;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "DataSource{" +
"userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", url='" + url + '\'' +
'}';
}
}
通過JavaConfig聲明這個(gè)bean:
@Bean("dataSource")
@Profile("dev")
DataSource devDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev");
return dataSource;
}
@Bean("dataSource")
@Profile("test")
DataSource testDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_test");
return dataSource;
}
@Bean("dataSource")
@Profile("prod")
DataSource prodDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod");
return dataSource;
}
如上我們分別為dev、test、prod環(huán)境設(shè)置了對應(yīng)的數(shù)據(jù)源配置,測試類如下:
public class ProfileTestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.getEnvironment().setActiveProfiles("dev");
System.out.println("=============================");
ctx.refresh();
Object dataSource = ctx.getBean("dataSource");
System.out.println("=============================");
System.out.println(dataSource);
}
}
測試結(jié)果:
=============================
無參構(gòu)造:DataSource被實(shí)例化
=============================
DataSource{userName='tp123456', passWord='null', url='jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev'}
Process finished with exit code 0
由此可以看出,dev環(huán)境的DataSource被實(shí)例化了
-
Spring XML中使用profile
在Spring xml中使用profile你可以選擇為每個(gè)環(huán)境設(shè)置一個(gè)單獨(dú)的xml配置文件,在xml的<beans> 標(biāo)簽下指定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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
profile="test">
<!--針對test環(huán)境的配置信息-->
<!--...-->
</beans>
這樣配置下來可能配置文件從命名看來顯得更明確,但是引發(fā)的問題是重復(fù)定義會(huì)很多。
另外一種方式是在<bean></beans>內(nèi)部再添加<bean profile="dev"></beans>,這樣你就可以不必為每個(gè)環(huán)境單獨(dú)創(chuàng)建xml配置文件了:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="course" class="com.tp.beans.Course">
<property name="name" value="Java"/>
<property name="durations" value="24"/>
<property name="id" value="222"/>
</bean>
<!--測試多環(huán)境-->
<beans profile="dev">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev"/>
</beans>
<beans profile="test">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_test"/>
</beans>
<beans profile="prod">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod"/>
</beans>
</beans>
再次運(yùn)行上面的測試類,效果是一致的。
條件化的Bean
假設(shè)你希望一個(gè)bean或者多個(gè)bean在某種特定條件下才被創(chuàng)建,例如應(yīng)用的類路徑下包含特定的庫才創(chuàng)建,或者某個(gè)bean在另一個(gè)bean已經(jīng)存在時(shí)才被創(chuàng)建,在Spring4之前很難實(shí)現(xiàn)這種級別的條件化配置,Spring4之后引入了@Conditional注解,它可以用到帶有@Bean注解的方法上,如果給定的條件計(jì)算為true,那么Spring就會(huì)為我們創(chuàng)建這個(gè)bean,否則這個(gè)bean會(huì)被忽略。
@Conditional可以給定一個(gè)任意實(shí)現(xiàn)了org.springframework.context.annotation.Condition的類,Condition的源碼如下
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
這個(gè)接口只有一個(gè)matches()方法,返回一個(gè)boolean類型表示是否滿足條件。
為了測試條件化的bean,我們定義3個(gè)類實(shí)現(xiàn)Condition接口,用來判斷當(dāng)前系統(tǒng)屬于哪種操作系統(tǒng),并為不同的操作系統(tǒng)創(chuàng)建不同的bean:
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: LinuxCondition
* Author: TP
* Description:
*/
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: MacCondition
* Author: TP
* Description:
*/
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("mac os x");
}
}
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: WindowsCondition
* Author: TP
* Description:
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
我們有個(gè)類Cat如下:
package com.tp.beans.condition;
/**
* FileName: Cat
* Author: TP
* Description:用于條件注解的bean
*/
public class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
在JavaConfig聲明bean如下:
@Bean("cat")
@Conditional(MacCondition.class)
Cat macCat(){
Cat cat = new Cat();
cat.setName("Mac Cat");
return cat;
}
@Bean("cat")
@Conditional(WindowsCondition.class)
Cat windowsCat(){
Cat cat = new Cat();
cat.setName("Windows Cat");
return cat;
}
@Bean("cat")
@Conditional(LinuxCondition.class)
Cat linuxCat(){
Cat cat = new Cat();
cat.setName("Linux Cat");
return cat;
}
測試:
package com.tp.test;
import com.tp.beans.condition.Cat;
import org.junit.Test;
/**
* FileName: OnConditionalBeanStatement
* Author: TP
* Description:基于特定條件的JavaBean聲明測試
*/
public class OnConditionalBeanStatementTest extends BaseTestCase{
@Test
public void conditionalBeanTest(){
Cat cat = (Cat) applicationContext.getBean("cat");
System.out.println(">>>基于JavaConfig的JavaBean聲明:" + cat);
}
}
運(yùn)行結(jié)果:

由于本人的操作系統(tǒng)是Mac,所以從控制臺可以看出為我們創(chuàng)建類Mac對應(yīng)的bean。
Condition接口的matches()方法會(huì)得到ConditionContext和
AnnotatedTypeMetadata這兩個(gè)對象,我們可以通過這兩個(gè)對象用來做決策,其中ConditionContext是一個(gè)接口,源碼如下:
public interface ConditionContext {
/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match.
* @throws IllegalStateException if no registry is available (which is unusual:
* only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
*/
BeanDefinitionRegistry getRegistry();
/**
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match, or {@code null} if the bean factory is
* not available (or not downcastable to {@code ConfigurableListableBeanFactory}).
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* Return the {@link Environment} for which the current application is running.
*/
Environment getEnvironment();
/**
* Return the {@link ResourceLoader} currently being used.
*/
ResourceLoader getResourceLoader();
/**
* Return the {@link ClassLoader} that should be used to load additional classes
* (only {@code null} if even the system ClassLoader isn't accessible).
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}
通過ConditionContext,我們可以做到如下幾點(diǎn):
- 借助getRegistry()方法返回的BeanDefinitionRegistry檢查bean定義
- 借助getBeanFactory()方法返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性
- 借助getEnvironment()方法返回的Environment檢查當(dāng)前應(yīng)用所在的運(yùn)行環(huán)境信息檢查環(huán)境變量是否存在
- 讀取并探查getResourceLoader()返回的ResourceLoader所加載的資源
- 借助getClassLoader()返回的ClassLoader加載并檢查類是否存在
AnnotatedTypeMetadata則能夠讓我們檢查帶有@Bean注解的方法上個(gè)還有什么其他的注解,像AnnotatedTypeMetadata一樣AnnotatedTypeMetadata也是一個(gè)接口:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
- 借助isAnnotated()方法我們可以判斷帶有@Bean注解的方法是不是還有其他特定的注解
- 借助其他的那些方法我們能夠檢查@Bean注解的方法上其他注解的屬性。
其實(shí)前面介紹的@Profile也是基于@Conditional實(shí)現(xiàn)的
處理自動(dòng)裝配的歧義性
在IOC基礎(chǔ)的學(xué)習(xí)中我們知道Spring的自動(dòng)裝配能夠?yàn)槲覀儨p少顯式配置的數(shù)量,不過僅有一個(gè)bean匹配所需的結(jié)果時(shí),自動(dòng)裝配才是有效的,如果有不止一個(gè)bean能夠匹配結(jié)果的話,這種歧義性會(huì)阻礙Sprig自動(dòng)裝配屬性、構(gòu)造器參數(shù)、方法參數(shù)。
歧義性例子:
為了演示這種歧義性,我們新建一個(gè)類AnimalServiceImpl2,同AnimalServiceImpl一樣,它也實(shí)現(xiàn)了上節(jié)課的AnimalService接口
具體如下:
@Service
public class AnimalServiceImpl2 implements AnimalService {
@Override
public void eat() {
System.out.println("AnimalServiceImpl2:動(dòng)物在吃飯....");
}
}
我們的User類中引入了AnimalService接口:
@Component("componentUser")
public class User {
public User() {
System.out.println("無參構(gòu)造:User被實(shí)例化");
}
// 構(gòu)造器注入
// @Autowired
public User(Book book) {
System.out.println("帶參構(gòu)造:User被實(shí)例化");
this.book = book;
}
public User(String name, String[] favorites) {
System.out.println("帶參構(gòu)造:User被實(shí)例化");
this.name = name;
this.favorites = favorites;
}
private Integer id;
private String name;
private Integer age;
private String[] favorites;
private Book book;
// 接口注入
@Autowired
private AnimalService animalService;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String[] getFavorites() {
return favorites;
}
public void setFavorites(String[] favorites) {
this.favorites = favorites;
}
public Book getBook() {
return book;
}
// Setter注入
// @Autowired
public void setBook(Book book) {
this.book = book;
}
// 非Setter注入
// @Autowired
public void injectBook(Book book) {
this.book = book;
}
public AnimalService getAnimalService() {
return animalService;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", favorites=" + Arrays.toString(favorites) +
", book=" + book +
'}';
}
}
這個(gè)時(shí)候我們再運(yùn)行一次之前正常的測試類:
/**
* 打開User成員變量上的@Autowired注解,演示屬性注入
*/
@Test
public void simpleComponentScanInterfaceInject() {
User user = (User) applicationContext.getBean("componentUser");
System.out.println(">>>基于注解掃描的JavaBean聲明:" + user);
System.out.println(">>>基于注解掃描的JavaBean聲明,注入的book:" + user.getBook());
System.out.println(user.getAnimalService());
if(null != user.getAnimalService()){
user.getAnimalService().eat();
}
}
發(fā)現(xiàn)報(bào)錯(cuò) :
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.tp.service.AnimalService' available: expected single matching bean but found 2: animalServiceImpl,animalServiceImpl2
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1171)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
... 42 more
大概意思是期望只有一個(gè)匹配的bean,但是Spring在容器中找到了2個(gè)符合條件的bean,因此報(bào)錯(cuò)。
發(fā)生這個(gè)問題的原因是,@Autowired注解是按照類型注入,因?yàn)锳nimalServiceImpl和AnimalServiceImpl2都實(shí)現(xiàn)了AnimalService這個(gè)類,所以Spring不知道用哪個(gè)好,解決的辦法有3種:
設(shè)置首選Bean(不推薦)
我們可以利用@Primary注解將其中一個(gè)bean設(shè)置為首選的bean:
@Service
@Primary
public class AnimalServiceImpl2 implements AnimalService {
@Override
public void eat() {
System.out.println("AnimalServiceImpl2:動(dòng)物在吃飯....");
}
}
這個(gè)時(shí)候再次運(yùn)行測試類,效果如下:

但是這種方式是不推薦的,@Primary只能設(shè)置優(yōu)先,但是無法保證唯一,因?yàn)榭赡芏鄠€(gè)bean中有不止一個(gè)bean設(shè)置了@Primary,那樣的話程序還是會(huì)發(fā)生錯(cuò)誤
使用@Qualifier配合@Autowired
@Autowired
@Qualifier("animalServiceImpl2")
private AnimalService animalService;
@Qualifier注解是使用限定符的主要方式,它可以與@Autowired和@Inject注解協(xié)同使用,@Qualifier注解的參數(shù)就是想要注入的bean的ID
在JavaConfig中,@Qualifier也可以與@Bean一起使用
再次運(yùn)行測試類:

使用@Resource注解
@Resource有2個(gè)參數(shù):name、type,它默認(rèn)按照名稱進(jìn)行裝配
@Resource裝配順序
- 如果同時(shí)指定了name和type,則從Spring上下文中找到唯一匹配的bean進(jìn)行裝配,找不到則拋出異常
- 如果指定了name,則從上下文中查找名稱(id)匹配的bean進(jìn)行裝配,找不到則拋出異常
- 如果指定了type,則從上下文中找到類型匹配的唯一bean進(jìn)行裝配,找不到或者找到多個(gè),都會(huì)拋出異常
- 如果既沒有指定name,又沒有指定type,則自動(dòng)按照byName方式進(jìn)行裝配;如果沒有匹配,則回退為一個(gè)原始類型進(jìn)行匹配,如果匹配則自動(dòng)裝配
修改User.java:
@Resource(name = "animalServiceImpl2")
private AnimalService animalService;
運(yùn)行測試類:

bean的作用域
默認(rèn)情況下,Spring上下文中的beaen都是以單例(singleton)的形式創(chuàng)建的,也就是說不管給定的一個(gè)bean被注入到其他bean多少次,每次注入的都是同一個(gè)實(shí)例。
但是在某些情況下你使用的類是易變的,他們會(huì)保持一些狀態(tài),因此重用是不安全的,這種情況下將class聲明為單例bean就不是什么好主意了,因?yàn)閷ο髸?huì)被污染,稍后重用的時(shí)候會(huì)出現(xiàn)意想不到的問題,Spring針對了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:
-
單例(Singleton):在整個(gè)應(yīng)用中,只創(chuàng)建bean的一個(gè)實(shí)例,
Spring默認(rèn)的作用域 - 原型(Prototype):每次注入或者通過Spring應(yīng)用上下文獲取的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例
- 會(huì)話(Session):在Web應(yīng)用中,為每個(gè)會(huì)話創(chuàng)建一個(gè)bean實(shí)例
- 請求(Request):在Web應(yīng)用中,為每個(gè)請求創(chuàng)建一個(gè)bean實(shí)例
如何設(shè)置bean的作用域?
-
組件掃描聲明bean
如果我們使用組件掃描聲明的bean,那么我們可以使用注解@Scope("xxx")來指定bean的作用域
@Scope的值我們可以用ConfigurableBeanFactory提供的常量,當(dāng)然也可以自己手寫
例如聲明為原型:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component("componentUser")
public class User {
}
-
xml聲明bean
在XMl中聲明bean的時(shí)候,我們可以使用<bean>元素的scope屬性來設(shè)置:
<!--構(gòu)造器注入:構(gòu)造器參數(shù)為bean情形-->
<bean id="xmlUser0" class="com.tp.beans.componentscan.User" scope="prototype">
<constructor-arg name="book" ref="javaConfigBook"/>
</bean>
- JavaConfig聲明bean
@Bean("javaConfigBook")
@Scope("prototype")
public Book book() {
Book book = new Book();
book.setId(222);
book.setPrice(33.88);
book.setName("簡書");
return book;
}
運(yùn)行時(shí)值注入
Spring提供了2種在運(yùn)行時(shí)求值的方式:
- 屬性占位符(Property placeholder)
- Spring表達(dá)式(SpEL)
1.注入外部的值
在Spring中處理外部值最簡單的方式是聲明屬性源并通過Spring的Environment來檢索屬性
聲明屬性源可以使用@PropertySource注解,其值為屬性文件路徑信息,例如:
@Configuration
@PropertySource("classpath:config/app.properties")
public class ValueInjectConfig {
@Autowired
Environment environment;
@Bean
public Jaguar jaguar(){
Jaguar jaguar = new Jaguar();
jaguar.setName(environment.getProperty("jaguar.name"));
jaguar.setColor(environment.getProperty("jaguar.color"));
return jaguar;
}
}
Jaguar.java:
package com.tp.beans.valueinject;
/**
* FileName: Jaguar
* Author: TP
* Description:
*/
public class Jaguar {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Jaguar{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
在上述代碼中,我們使用@PropertySource注解并指定類屬性源為類路徑下的config/app.properties文件,文件內(nèi)容如下:
jaguar.name=捷豹XEL
jaguar.color=white
civic.name=思域
civic.color=yellow
這個(gè)文件會(huì)被加載到Spring的Environment中,我們就可以借助Environment的getPropery()方法獲取出對應(yīng)的值,Environment的getPropery()方法有4種:
- String getPropery(String key);
- String getPropery(String key, String defaultValue);
- T String getPropery(String key, Class<T> type);
- T String getPropery(String key, Class<T> type, T defaultValue);
運(yùn)行測試類:
public class ValueInjectTest extends BaseTestCase {
/**
* "@Properties"注解測試
*/
@Test
public void propertyResourceInjectTest() {
Jaguar jaguar = (Jaguar) applicationContext.getBean("jaguar");
System.out.println(">>>>@PropertyResource:" + jaguar);
}
}
測試結(jié)果:

2.解析屬性占位符
直接從Environment中獲取屬性是比較方便的,特別是在Java配置中裝配bean的時(shí)候,但是Spring也為我們提供了占位符裝配bean屬性的方法,這些占位符的值來源于一個(gè)屬性源,為了使屬性占位符生效,我們需要配置一個(gè)PropertySourcesPlaceholderConfigurer,它能夠基于Spring Environment及其屬性源來解析占位符,我們可以在Spring的配置文件中配置PropertySourcesPlaceholderConfigurer:
<context:property-placeholder location="classpath*:/config/*.properties"
file-encoding="utf-8"/>
準(zhǔn)備就緒后,我們分別以注解掃描和xml形式聲明一個(gè)bean
- xml聲明bean使用屬性占位符:
<bean id="xmlPlaceholderJaguar" class="com.tp.beans.valueinject.Jaguar">
<property name="name" value="${jaguar.name}"/>
<property name="color" value="${jaguar.color}"/>
</bean>
測試類:
/**
* 屬性占位符xml形式測試
*/
@Test
public void xmlPlaceholderInjectTest() {
Jaguar jaguar = (Jaguar) applicationContext.getBean("xmlPlaceholderJaguar");
System.out.println(">>>>@PropertyPlaceholder of xml:" + jaguar);
}
結(jié)果:

- 組件掃描聲明bean使用占位符
package com.tp.beans.valueinject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* FileName: Honda
* Author: TP
* Description:
*/
@Component
public class Civic {
@Value("${civic.name}")
private String name;
@Value("${civic.color}")
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Civic{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
測試類:
@Test
public void scanPlaceholderInjectTest() {
Civic civic = (Civic) applicationContext.getBean("civic");
System.out.println(">>>>@PropertyPlaceholder of componentScan:" + civic);
}
運(yùn)行結(jié)果:

SpEl
Spring 3引入了Spring表達(dá)式(Spring Expression Language),它能夠以一種強(qiáng)大和簡潔的方式將值裝配到bean的屬性和構(gòu)造器參數(shù)中,在這個(gè)過程中所使用的表達(dá)式會(huì)在運(yùn)行時(shí)計(jì)算到值。
SpEl擁有很多特性,包括:
- 使用bean的ID來引用bean
- 調(diào)用方法和訪問對象的屬性
- 對值進(jìn)行算術(shù)、關(guān)系和邏輯運(yùn)算
- 正則表達(dá)式匹配
- 集合操作
SpEL表達(dá)式需要放在:#{}之中,形為#{xxx},這與屬性占位符類似,屬性占位符為${...}之中。
SpEl用法示例
-
表示字面值
#{3.1415926}表示一個(gè)浮點(diǎn)值
#{'Hello'}表示一個(gè)String類型的字面值
#{false}表示一個(gè)boolean類型的值 -
引用bean屬性、方法
SpEl能夠通過ID獲取到這個(gè)bean,我們就可以使用SpEL對bean的屬性或者方法實(shí)現(xiàn)引用
例如:
1.獲取屬性
#{jaguar.name}:這個(gè)表達(dá)式會(huì)獲取ID為jaguar的這個(gè)bean的name屬性
2.方法引用
#{jaguar.getSlogan()}:這個(gè)表達(dá)式會(huì)獲取ID為jaguar的這個(gè)bean,并返回其getSlogan()方法的返回值
對于方法的返回值我們還可以繼續(xù)調(diào)用返回值的函數(shù),例如:#{jaguar.getSlogan().toUpperCase()},當(dāng)然方法的返回值可能為null,這樣就有可能引發(fā)空指針問題,為了避免空指針問題,我們可以使用類型安全的運(yùn)算符:#{jaguar.getSlogan()?.toUpperCase()},這里我們使用了"?",這個(gè)運(yùn)算符能夠在訪問它右邊的內(nèi)容之前確保左側(cè)內(nèi)容不為null,如果為null則不會(huì)執(zhí)行后面的內(nèi)容,直接返回null。 -
在表達(dá)式中使用類型
如果要在SpEL中訪問類作用域的方法和常量的話,要依賴T()這個(gè)關(guān)鍵的運(yùn)算符,例如:為了在SpEL中使用Java的Math類,需要按照如下的方式使用T()運(yùn)算符:T(java.lang.Math),此時(shí)T()運(yùn)算符會(huì)得到一個(gè)Class對象,代表了java.lang.Math,假如你想將PI值裝配到bean的屬性中,可以這樣寫:
${T(java.lang.Math).PI},當(dāng)然我們不僅可以獲取訪問目標(biāo)的常量,我們還可以調(diào)用其方法:${T(java.lang.Math).random()*10} -
SpEL運(yùn)算符
SpEL提供了多種運(yùn)算符,這些運(yùn)算符可以用在SpEL表達(dá)式上:
| 運(yùn)算符類型 | 運(yùn)算符 |
|---|---|
| 算術(shù)運(yùn)算 | +、-、*、/、%、^ |
| 比較運(yùn)算符 | <、>、==、<=、>=、lt、gt、eq、le、ge |
| 邏輯運(yùn)算符 | and、or、not、| |
| 條件運(yùn)算符 | 三元運(yùn)算符 |
| 正則表達(dá)式 | matches |
-
計(jì)算集合
SpEL還可以操作集合和數(shù)組
例如:#{jukebox.songs[4].tile},這個(gè)表達(dá)式會(huì)計(jì)算jukebox這個(gè)bean的songs屬性(是個(gè)集合,集合裝的Song這個(gè)javabean)中第五個(gè)元素Song的title屬性,[]用來從集合或者數(shù)組中按照索引獲取數(shù)據(jù)
拋出幾個(gè)簡單的測試類,其他用法測試讀者自行測試吧:
package com.tp.beans.valueinject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* FileName: Benz
* Author: TP
* Description:用于測試SpEl的實(shí)體
*/
@Component
public class Benz {
//SpEL引用bean屬性
@Value("#{xmlSpelBenz.name}")
private String name;
//SpEL引用bean方法
@Value("#{xmlSpelBenz.getColor()}")
private String color;
//SpEL表達(dá)式中使用類型
@Value("#{T(java.lang.Math).random()*10}")
private int carAge;
//字面常量
@Value("#{'買奔馳嗎?這次不漏油哦~'}")
private String slogan;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getCarAge() {
return carAge;
}
public void setCarAge(int carAge) {
this.carAge = carAge;
}
public String getSlogan() {
return slogan;
}
public void setSlogan(String slogan) {
this.slogan = slogan;
}
@Override
public String toString() {
return "Benz{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", carAge=" + carAge +
", slogan='" + slogan + '\'' +
'}';
}
}
app.properties:
jaguar.name=捷豹XEL
jaguar.color=white
civic.name=思域
civic.color=yellow
benz.slogan=買奔馳嗎?漏油的那種?。?!
applicationContext.xml:
<!--############################SpEL############################-->
<bean id="xmlSpelBenz" class="com.tp.beans.valueinject.Benz">
<property name="name" value="C260L"/>
<property name="color" value="富士白"/>
<property name="carAge" value="#{T(Math).random()*10}"/>
<property name="slogan" value="${benz.slogan}"/>
</bean>
測試類:
//=========================SpEl=========================
@Test
public void xmlSpelInjectTest() {
Benz benz = (Benz) applicationContext.getBean("xmlSpelBenz");
System.out.println(">>>>SpEL of xml:" + benz);
}
@Test
public void componentScanSpelInjectTest() {
Benz benz = (Benz) applicationContext.getBean("benz");
System.out.println(">>>>SpEL of componentScan:" + benz);
}
測試結(jié)果:

