SpringBoot注入資料的方式

胖先森發表於2018-12-23

關於注入資料說明

專案目錄

1.不通過配置檔案注入資料

通過@Value將外部的值動態注入到Bean中,使用的情況有:

  • 注入普通字串
  • 注入作業系統屬性
  • 注入表示式結果
  • 注入其他Bean屬性:注入Student物件的屬性name
  • 注入檔案資源
  • 注入URL資源

輔助程式碼

package com.hannpang.model;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "st")//對student進行例項化操作
public class Student {
    @Value("悟空")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
複製程式碼

測試@Value的程式碼

package com.hannpang.model;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

@Component
public class SimpleObject {

    @Value("注入普通字串")
    private String normal;

    //關於屬性的KEY可以檢視System類說明
    @Value("#{systemProperties['java.version']}")//-->使用了SpEL表示式
    private String systemPropertiesName; // 注入作業系統屬性

    @Value("#{T(java.lang.Math).random()*80}")//獲取隨機數
    private double randomNumber; //注入表示式結果

    @Value("#{1+2}")
    private double sum; //注入表示式結果 1+2的求和

    @Value("classpath:os.yaml")
    private Resource resourceFile; // 注入檔案資源

    @Value("http://www.baidu.com")
    private Resource testUrl; // 注入URL資源

    @Value("#{st.name}")
    private String studentName;

    //省略getter和setter方法

    @Override
    public String toString() {
        return "SimpleObject{" +
                "normal='" + normal + '\'' +
                ", systemPropertiesName='" + systemPropertiesName + '\'' +
                ", randomNumber=" + randomNumber +
                ", sum=" + sum +
                ", resourceFile=" + resourceFile +
                ", testUrl=" + testUrl +
                ", studentName='" + studentName + '\'' +
                '}';
    }
}
複製程式碼

Spring的測試程式碼

package com.hannpang;

import com.hannpang.model.SimpleObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo04BootApplicationTests {

    @Autowired
    private SimpleObject so;

    @Test
    public void contextLoads()  {
        System.out.println(so);
    }
}
複製程式碼
執行結果為:SimpleObject{normal='注入普通字串', systemPropertiesName='1.8.0_172', randomNumber=56.631954541947266, sum=3.0, resourceFile=class path resource [os.yaml], testUrl=URL [http://www.baidu.com], studentName='悟空'}
複製程式碼

2.通過配置檔案注入資料

通過@Value將外部配置檔案的值動態注入到Bean中。配置檔案主要有兩類:

  • application.properties、application.yaml application.properties在spring boot啟動時預設載入此檔案
  • 自定義屬性檔案。自定義屬性檔案通過@PropertySource載入。@PropertySource可以同時載入多個檔案,也可以載入單個檔案。如果相同第一個屬性檔案和第二屬性檔案存在相同key,則最後一個屬性檔案裡的key啟作用。載入檔案的路徑也可以配置變數,如下文的${anotherfile.configinject},此值定義在第一個屬性檔案config.properties

  • 在application.properties中加入如下測試程式碼

    app.name=一步教育
    複製程式碼
  • 在resources下面新建第一個屬性檔案config.properties內容如下

    book.name=西遊記
    anotherfile.configinject=system
    複製程式碼
  • 在resources下面新建第二個屬性檔案config_system.properties內容如下

    我的目的是想system的值使用第一個屬性檔案中定義的值

    book.name.author=吳承恩
    複製程式碼

下面通過@Value(“${app.name}”)語法將屬性檔案的值注入bean屬性值,詳細程式碼見:

package com.hannpang.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
@PropertySource(value = {"classpath:config.properties","classpath:config_${anotherfile.configinject}.properties"})
public class LoadPropsTest {

    @Value("${app.name}")
    private String appName; // 這裡的值來自application.properties,spring boot啟動時預設載入此檔案

    @Value("${book.name}")
    private String bookName; // 注入第一個配置外部檔案屬性

    @Value("${book.name.author}")
    private String author; // 注入第二個配置外部檔案屬性

    @Autowired
    private Environment env;  // 注入環境變數物件,儲存注入的屬性值

    //省略getter和setter方法


    public void setAuthor(String author) {
        this.author = author;
    }


    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("bookName=").append(bookName).append("\r\n")
                .append("author=").append(author).append("\r\n")
                .append("appName=").append(appName).append("\r\n")
                .append("env=").append(env).append("\r\n")
                // 從eniroment中獲取屬性值
                .append("env=").append(env.getProperty("book.name.author")).append("\r\n");
        return sb.toString();
    }

}
複製程式碼

測試程式碼

package com.hannpang;

import com.hannpang.model.SimpleObject;
import com.hannpang.test.LoadPropsTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo04BootApplicationTests {

    @Autowired
    private LoadPropsTest lpt;

    @Test
    public void loadPropertiesTest()  {

        System.out.println(lpt);
    }
}
複製程式碼
執行結果為:
bookName=西遊記
author=吳承恩
appName=一步教育
env=StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, MapPropertySource {name='Inlined Test Properties'}, MapPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}, ResourcePropertySource {name='class path resource [config_system.properties]'}, ResourcePropertySource {name='class path resource [config.properties]'}]}
env=吳承恩
複製程式碼

3. #{...}和${...}的區別演示

A.${…}的用法

{}裡面的內容必須符合SpEL表示式,通過@Value(“${app.name}”)可以獲取屬性檔案中對應的值,但是如果屬性檔案中沒有這個屬性,則會報錯。可以通過賦予預設值解決這個問題,如@Value("${app.name:胖先森}")

部分程式碼

// 如果屬性檔案沒有app.name,則會報錯
//  @Value("${app.name}")
//  private String name;

// 使用app.name設定值,如果不存在則使用預設值
@Value("${app.name:胖先森}")
private String name;
複製程式碼

B.#{...}的用法

部分程式碼直接演示

// SpEL:呼叫字串Hello World的concat方法
@Value("#{'Hello World'.concat('!')}")
private String helloWorld;

// SpEL: 呼叫字串的getBytes方法,然後呼叫length屬性
@Value("#{'Hello World'.bytes.length}")
private String helloWorldbytes;

複製程式碼

C.#{...}${...}混合使用

${...}和#{...}可以混合使用,如下文程式碼執行順序:通過${server.name}從屬性檔案中獲取值並進行替換,然後就變成了 執行SpEL表示式{‘server1,server2,server3’.split(‘,’)}。

// SpEL: 傳入一個字串,根據","切分後插入列表中, #{}和${}配置使用(注意單引號,注意不能反過來${}在外面,#{}在裡面)
@Value("#{'${server.name}'.split(',')}")
private List<String> servers;
複製程式碼

在上文中在#{}外面,${}在裡面可以執行成功,那麼反過來是否可以呢${}在外面,#{}在裡面,如程式碼

// SpEL: 注意不能反過來${}在外面,#{}在裡面,這個會執行失敗
@Value("${#{'HelloWorld'.concat('_')}}")
private List<String> servers2;
複製程式碼
答案是不能。
因為spring執行${}是時機要早於#{}。
在本例中,Spring會嘗試從屬性中查詢#{‘HelloWorld’.concat(‘_’)},那麼肯定找到,由上文已知如果找不到,然後報錯。所以${}在外面,#{}在裡面是非法操作
複製程式碼

D.用法總結

  • #{…} 用於執行SpEl表示式,並將內容賦值給屬性
  • ${…} 主要用於載入外部屬性檔案中的值
  • #{…} 和${…} 可以混合使用,但是必須#{}外面,${}在裡面

4.@Value獲取值和@ConfigurationProperties獲取值比較

@ConfigurationProperties @Value
功能 批量注入配置檔案中的屬性 一個個指定
鬆散繫結(鬆散語法) 支援 不支援
SpEL 不支援 支援
JSR303資料校驗 支援 不支援
複雜型別封裝 支援 不支援

配置檔案yml還是properties他們都能獲取到值;

  • 如果說,我們只是在某個業務邏輯中需要獲取一下配置檔案中的某項值,使用@Value;

  • 如果說,我們專門編寫了一個javaBean來和配置檔案進行對映,我們就直接使用@ConfigurationProperties;

關於資料校驗的部分程式碼

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
   //lastName必須是郵箱格式
    @Email
    private String lastName;

複製程式碼

5. @ImportResource引入配置檔案

不推薦的使用方式

Spring Boot裡面沒有Spring的配置檔案,我們自己編寫的配置檔案,也不能自動識別;

想讓Spring的配置檔案生效,載入進來;@ImportResource標註在一個配置類上

@ImportResource(locations = {"classpath:beans.xml"})
匯入Spring的配置檔案讓其生效
複製程式碼

編寫配置檔案資訊

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="helloService" class="com.hanpang.springboot.service.HelloService"></bean>
</beans>
複製程式碼

大概瞭解就好,我們基本上不使用這種方式

6.@Configuration註解

SpringBoot推薦給容器中新增元件的方式;推薦使用全註解的方式

1、配置類@Configuration作用於類上,相當於一個xml配置檔案

2、使用@Bean給容器中新增元件,作用於方法上

/**
 * @Configuration:指明當前類是一個配置類;就是來替代之前的Spring配置檔案
 *
 * 在配置檔案中用<bean><bean/>標籤新增元件
 * <bean id="helloService" class="com.hanpang.springboot.service.HelloService"></bean>
 */
@Configuration
public class MyAppConfig {

    //將方法的返回值新增到容器中;容器中這個元件預設的id就是方法名
    @Bean
    public HelloService helloService02(){
        System.out.println("配置類@Bean給容器中新增元件了...");
        return new HelloService();
    }
}
複製程式碼

使用Bean注入太麻煩,我們更加喜歡使用掃描的方式


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
import com.wx.dao.IUserDao;
import com.wx.dao.UserDaoImpl;
 
//通過該註解來表明該類是一個Spring的配置,相當於一個傳統的ApplicationContext.xml
@Configuration
//相當於配置檔案裡面的<context:component-scan/>標籤,掃描這些包下面的類的註解
@ComponentScan(basePackages="com.hanpang.dao,com.hanpang.service")
public class SpringConfig {
	// 通過該註解來表明是一個Bean物件,相當於xml中的<bean>
	//bean的id值預設是方法名userDao
	/*
	@Bean
    public HelloService helloService02(){
        System.out.println("配置類@Bean給容器中新增元件了...");
        return new HelloService();
    }
	*/
}
複製程式碼

附錄

隨機數

${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
複製程式碼

相關文章