Spring-IOC高級

在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é)果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容