spring5入門與實踐第三講Spring的其他特性
spring整合測試
spring可以和junit很好的進行整合,首先我們需要新增junit和spring和test的依賴
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
建立一個配置類
@Configuration
public class BaseConfig {
@Bean
public String hello() {
return "hello";
}
}
建立基於spring 的測試類,首先在測試類上新增@RunWith(SpringJUnit4ClassRunner.class),說明這個類是一個spring的測試類,宣告之後該測試類就可以直接注入spring容器中的物件,之後通過@ContextConfiguration(classes=BaseConfig.class)來說明具體的配置配置類是哪個。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BaseConfig.class)
public class BaseTest {
@Autowired
private String hello;
@Test
public void test01() {
System.out.println(hello);
}
}
在BaseConfig中建立了一個String的Bean物件,使用的是hello方法建立的,此時注入物件名稱就是hello,所以在BaseTest中直接注入一個hello的物件即可。
spring基於Annotation的配置
spring4之後,spring就支援完全基於java的Annotation的配置,這裡需要特別拿出來講解幾個比較常用的方法,首先是如何分割多個配置檔案,通過import既可以完成分割,建立兩個配置類
@Configuration
public class AConfig {
@Bean("a")
public String a() {
return "a";
}
}
@Configuration
public class BConfig {
@Bean
public String b() {
return "b";
}
}
在BaseConfig中通過@Import可以匯入這些配置類
@Configuration
@Import({AConfig.class,BConfig.class})
public class BaseConfig {
@Bean("hello")
public String hello() {
return "hello";
}
}
在測試類中只要引入BaseConfig,其他兩個測試類也會被匯入
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BaseConfig.class)
public class BaseTest {
@Autowired
private String hello;
@Autowired
private String a;
@Autowired
private String b;
@Test
public void test01() {
System.out.println(hello);
System.out.println(a);
System.out.println(b);
}
}
在@Bean中除了預設的value之外,還提供了initMethod和destoryMethod來執行初始化和銷燬操作,建立一個BaseObject來執行
public class BaseObject {
private void init() {
System.out.println("begin run....");
}
private void destory() {
System.out.println("destory program!!");
}
}
在Config類中加入該bean
@Bean(initMethod = "init",destroyMethod = "destory")
public BaseObject baseObject() {
return new BaseObject();
}
此時只要注入這個物件就會首先執行init和destroy方法。
spring的Profile
spring從3之後就加入了Profile的處理,Profile可以分階段和分使用者來設定配置,該功能在多使用者管理中特別的好用,我們從java的配置資訊和properties的資訊讀取兩方面來進行處理。
假設有這樣一種需要,專案中需要設定靜態資原始檔的路徑,首先建立一個SystemPath的介面和兩個不同的實現類,一個用來指定開發時的路徑,一個用來指定釋出後的路徑
public interface SystemPath {
String getRealPath();
}
public class DevPath implements SystemPath {
public String getRealPath() {
return "dev:path";
}
}
public class QaPath implements SystemPath {
public String getRealPath() {
return "qa:path";
}
}
下一步在具體的配置類中建立這兩個bean並且指定Profile
@Configuration
@Import({AConfig.class,BConfig.class})
public class BaseConfig {
....
@Bean("path")
@Profile("dev")
public SystemPath devPath() {
return new DevPath();
}
@Bean("path")
@Profile("qa")
public SystemPath qaPath() {
return new QaPath();
}
}
最後就是在使用的時候啟用,在基於web的應用的程式中,可以通過web.xml來進行設定,通過spring.profiles.active和spring.profiles.default來配合使用,spring.profiles.active用來指定當前啟用的配置,如果沒有設定spring.profiles.active這個的值,會自動去找spring.profiles.default,可以在web.xml中通過<context>
來進行設定。此處會在web專案時介紹。
如果需要在maven中的使用Profile,可以將這兩個引數加在maven的命令之後,如果希望使用在測試類中,使用@ActiveProfiles來進行啟用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = BaseConfig.class)
@ActiveProfiles("dev")
public class PathTest {
@Autowired
private SystemPath path;
@Test
public void testPath() {
System.out.println(path.getRealPath());
}
}
以上就是Profile的思路,在實際應用中,這種需求更多的是運用在properties的環境中,當需要多個使用者同時開發時難免檔案的路徑,資料庫的使用者名稱和密碼這些有不同,如果同時使用svn或者git這些版本管理工具,只有一個properties檔案會很難處理,所以可以根據使用者建立多個不同的配置檔案,根據不同的需求進行載入,下面將介紹基於不同配置檔案的實現方式。
首先建立三個配置檔案application.properties,application-kh.properties,application-ls.properties
#### application.properties
profile.name = default.profile
realpath = /project/test
jdbc.username = ok
jdbc.password = 111111
#### application-kh.properties
profile.name = kh.profile
realpath = d:/project/test
jdbc.username = kh
jdbc.password = 123456
#### application-ls.properties
profile.name = ls.profile
realpath = d:/project/test
jdbc.username = ls
jdbc.password = 666666
建立兩個類,一個類模擬資料庫的配置,一個類模擬環境的配置
@Component
public class DataSourceProperties {
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
..省略getter和setter...
}
@Component
public class EnvProperties {
@Value("${profile.name}")
private String profileName;
@Value("${realpath}")
private String realpath;
..省略getter和setter...
}
這兩類都是spring的Component,一個儲存了資料庫的基本資訊,一個儲存了環境的資訊,都是通過properties來獲取這兩個值,下面就需要在BaseConfig這個配置類中新增相應的Properties,雖然spring提供了@PropertySource,但是這種方式並不能很好的實現Profile,它只會把每一個properties檔案都載入進去,如果有相同的值會用最後一個來替換,這顯然無法實現基於Profile的Properties。所以此處需要開發人員根據ActiveProfile手動把相應的配置檔案匯入,通過建立一個init的方法來執行
@Configuration
@Import({AConfig.class,BConfig.class})
@ComponentScan("org.konghao")
public class BaseConfig {
@Autowired
private ConfigurableEnvironment env;
private String prefix = "application";
@PostConstruct//該方法在建構函式之後執行
public void init() {
try {
//判斷環境引數中是否有ActiveProfiles
if(env.getActiveProfiles().length>0) {
//如果加了Profile,就將該Profile的字首新增到spring的Property資源中
for(String activeProfile:env.getActiveProfiles()) {
//獲取字首的資原始檔
ClassPathResource cpr = new ClassPathResource(prefix+"-"+activeProfile+".properties");
if(cpr.exists()) {
//新增到環境的資原始檔中
env.getPropertySources().addLast(new ResourcePropertySource(cpr));
}
}
} else {
//如果沒有Profile,就把預設的檔案新增進去
ClassPathResource cpr = new ClassPathResource(prefix+".properties");
if(cpr.exists()) {
env.getPropertySources().addLast(new ResourcePropertySource(cpr));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用這種方式就可以有效的解決properties檔案的Profile問題。
條件化Bean
在Spring4之後,spring提供了一種基於條件來建立Bean的方式,這可以用於某種特殊的需求,如只有在某個Bean建立成功了才建立該Bean,或者說只有在配置了某個環境變數之後才建立這個Bean,這些需求通過單純的配置是不太容易實現的。
通過@Conditional 來設定建立該Bean的條件,@Conditional 中要傳入Condition 的物件,所以需要我們手動實現這個物件
public class HelloBeanCheck implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//conditionContext.getBeanFactory()//獲取BeanFactory
// conditionContext.getRegistry()//獲取Bean的定義物件
// conditionContext.getBeanFactory()//獲取Bean的工廠
// conditionContext.getClassLoader()//獲取ClassLoader
// conditionContext.getEnvironment()//獲取環境變數
// conditionContext.getResourceLoader()//獲取資源資訊
// annotatedTypeMetadata可以用來檢測自己的Annotation資訊
//如果有hello這個bean才會建立HelloBean
return conditionContext.getRegistry().containsBeanDefinition("hello");
}
}
Condition中需要實現一個matches方法,該方法非常強大,可以通過該方法兩個引數做很多檢查操作,大家可以自行測試,最後如果返回true就會建立,否則就不會建立。
動態注入值
在原來的程式碼中,基本都是以硬編碼的方法為Bean注入值的,如果希望動態的注入值也是可以實現的,有兩種具體的解決方案,一種是基於配置檔案的方式,另外一種就是基於spring的表示式來實現。
基於配置檔案的實現方式非常簡單,首先通過@PropertySource新增一個資原始檔,其次注入一個Environment物件即可,上一小節所使用的ConfigurableEnvironment是Environment的子類。建立一個User物件,並且建立一個base.properties的資原始檔
user.username = zs
user.nickname = zhangsan
下面建立User物件
public class User {
private String username;
private String nickname;
public User(){}
public User(String username,String nickname) {
this.username = username;
this.nickname = nickname;
}
//........省略getter和setter.....
}
在BaseConfig中注入該物件
@Configuration
@Import({AConfig.class,BConfig.class})
@ComponentScan("org.konghao")
@PropertySource("base.properties")
public class BaseConfig {
@Autowired
private ConfigurableEnvironment env;
//...省略多餘程式碼..
@Bean
public User user() {
return new User(env.getProperty("user.username"),
env.getProperty("user.nickname"));
}
}
如果不使用env,也可以使用${xxx.xxx}的佔位符來指定
public class User {
@Value("${user.username}")
private String username;
@Value("${user.nickname}")
private String nickname;
}
此時在BaseConfig中直接使用不帶引數的構造方法來建立,都會給username和nickname設定到相應的值
@Bean
public User user() {
// return new User(env.getProperty("user.username"),
// env.getProperty("user.nickname"));
return new User();
}
基於配置的方法非常簡單,下面看看基於spring表示式SpEL的方式。
spring表示式是使用#{...}
來編寫,表示式非常的強大,可以是單個的值,也可以是物件,還能是物件的某個屬性,並且這些值還可以進行運算,spring的SpEL是使用@Value來注入,建立三個類來模擬一下SpEL。
@Component
public class Student {
@Value("#{systemProperties['user.name']}")
private String name;
private List<Book> books;
public Student() {
books = Arrays.asList(
new Book("b1",12),
new Book("b2",33),
new Book("b3",44));
}
//...省略getter和setter....
}
此處就使用了一個SpEL,從系統變數中獲取使用者名稱,下面看看Book這個類
@Component
public class Book {
private String name;
private double price;
public Book() {}
public Book(String name,double price) {
this.name = name;
this.price = price;
}
//...省略getter和setter...
}
通過一個StudentDto來模擬幾種常用的SpringSPEL
@Component
public class StudentDto {
@Autowired
private Student student;
@Value("#{student.name}")
private String name;
@Value("#{student.books.![name]}")
private List<String> books;
@Value("#{student.books.size()}")
private int bookCount;
@Value("#{T(org.konghao.spring.model.BookUtil).calPrice(student.books)}")
private double bookPrice;
//...省略來的getter和setter...
}
name這個屬性使用的是Student的name,books屬性使用的是一組books的name列表,bookCount通過list的size求和,而bookPrice是呼叫了BookUtil中 方法求所有書的價格,只要是需要使用某個類的靜態方法或者引用常量都需要使用T()來表示,下面看看BookUtil,
public class BookUtil {
public static double calPrice(List<Book> books) {
return books.stream().mapToDouble(s->s.getPrice()).sum();
}
}
以上使用了Lamdba表示式來完成求和,強烈建議大家將來使用List都通過Lamdba來處理。這一部分的內容就到此為止了,主要講解了spring的一些用法,下一部分進入spring的web程式設計講解。
相關文章
- Spring Boot Docker入門模板與4個最佳實踐Spring BootDocker
- Spring Security系列之極速入門與實踐教程Spring
- Kafka 入門與實踐Kafka
- TypeScript入門與實踐TypeScript
- Docker 入門與實踐Docker
- locsut 入門與實踐
- Spring Boot 最佳實踐(一)快速入門Spring Boot
- GitOps快速入門與實踐Git
- Spring入門(七):Spring Profile使用講解Spring
- Git與Github入門實踐(上)Github
- 微信小程式入門與實踐微信小程式
- Kubeflow實戰: 入門介紹與部署實踐
- Spring Cloud Alibaba入門實踐(三十八)-引入Seata元件SpringCloud元件
- 最火的容器技術|Docker入門與實踐Docker
- 單元測試的入門實踐與應用
- 《Django入門與實踐教程》完整版Django
- 《Kafka入門與實踐》讀書筆記Kafka筆記
- Kafka v2.3 快速入門與實踐Kafka
- Docker入門實踐Docker
- BoltDB 入門實踐
- MQ 入門實踐MQ
- Spring5的框架概述Spring框架
- Spring5 MVCSpringMVC
- 原創:spring入門介紹第一講Spring
- 直播預約丨《實時湖倉實踐五講》第三講:實時湖倉在袋鼠雲的落地實踐之路
- babel 修改抽象語法樹——入門與實踐Babel抽象語法樹
- Docker基礎、Machine、Compose、Swarm入門與實踐DockerMacSwarm
- 入門看看?Chrome外掛初窺與實踐Chrome
- Spring Boot Enable* 的原理與實踐Spring Boot
- Pytorch入門下 —— 其他PyTorch
- redux 入門到實踐Redux
- GitHub Actions 入門實踐Github
- Docker入門實踐(三)Docker
- Docker入門實踐(四)Docker
- Nginx入門實踐(二)Nginx
- 2.2 spring5原始碼 -- ioc載入的整體流程Spring原始碼
- Spring5基礎Spring
- 5.3 Spring5原始碼--Spring AOP使用介面方式實現Spring原始碼