一. spring boot中傳參的方法
1、自動化配置
spring Boot 對於開發人員最大的好處在於可以對 Spring 應用進行自動配置。Spring Boot 會根據應用中聲明的第三方依賴來自動配置 Spring 框架,而不需要進行顯式的聲明。比如
當聲明了對 HSQLDB 的依賴時,Spring Boot 會自動配置成使用 HSQLDB 進行數據庫操作。
Spring Boot 推薦采用基於 Java 注解的配置方式,而不是傳統的 XML。只需要在主配置 Java 類上添加“@EnableAutoConfiguration”注解就可以啟用自動配置。Spring Boot 的自動配置
功能是沒有侵入性的,只是作為一種基本的默認實現。開發人員可以通過定義其他 bean 來替代自動配置所提供的功能。比如當應用中定義了自己的數據源 bean 時,自動配置所提供的
HSQLDB 就不會生效。這給予了開發人員很大的靈活性。既可以快速的創建一個可以立即運行的原型應用,又可以不斷的修改和調整以適應應用開發在不同階段的需要。可能在應用最開始的時
候,嵌入式的內存數據庫(如 HSQLDB)就足夠了,在后期則需要換成 MySQL 等數據庫。Spring Boot 使得這樣的切換變得很簡單。
2、外部化的配置
在應用中管理配置並不是一個容易的任務,尤其是在應用需要部署到多個環境中時。通常會需要為每個環境提供一個對應的屬性文件,用來配置各自的數據庫連接信息、服務器信息和第
三方服務賬號等。通常的應用部署會包含開發、測試和生產等若干個環境。不同的環境之間的配置存在覆蓋關系。測試環境中的配置會覆蓋開發環境,而生產環境中的配置會覆蓋測試環境。
Spring 框架本身提供了多種的方式來管理配置屬性文件。Spring 3.1 之前可以使用 PropertyPlaceholderConfigurer。Spring 3.1 引入了新的環境(Environment)和概要信息(Profile)
API,是一種更加靈活的處理不同環境和配置文件的方式。不過 Spring 這些配置管理方式的問題在於選擇太多,讓開發人員無所適從。Spring Boot 提供了一種統一的方式來管理應用的配置
,允許開發人員使用屬性文件、YAML 文件、環境變量和命令行參數來定義優先級不同的配置值。
Spring Boot 所提供的配置優先級順序比較復雜。按照優先級從高到低的順序,具體的列表如下所示。
(1) 命令行參數
(2) java:comp/env 里的JNDI屬性
(3) JVM系統屬性
(4) 操作系統環境變量
(5) 隨機生成的帶 random.* 前綴的屬性(在設置其他屬性時,可以引用它們,比如 ${random.
long} )
(6) 應用程序以外的application.properties或者appliaction.yml文件
(7) 打包在應用程序內的application.properties或者appliaction.yml文件
(8) 通過 @PropertySource 標注的屬性源
(9) 默認屬性
這個列表按照優先級排序,也就是說,任何在高優先級屬性源里設置的屬性都會覆蓋低優先級的相同屬性。例如,命令行參數會覆蓋其他屬性源里的屬性。
application.properties和application.yml文件能放在以下四個位置。
(1) 外置,在相對於應用程序運行目錄的/config子目錄里。
(2) 外置,在應用程序運行的目錄里。
(3) 內置,在config包內。
(4) 內置,在Classpath根目錄。
同樣,這個列表按照優先級排序。也就是說,/config子目錄里的application.properties會覆蓋應用程序Classpath里的application.properties中的相同屬性。
此外,如果我們在同一優先級位置同時有application.properties和application.yml,那么application.yml里的屬性會覆蓋application.properties里的屬性。
命令行參數
通過Java -jar app.jar –name=”Spring” –server.port=9090方式來傳遞參數。
SpringApplication 類默認會把以“–”開頭的命令行參數轉化成應用中可以使用的配置參數,如 “–name=Alex” 會設置配置參數 “name” 的值為 “Alex”.
可以使用的參數可以是我們自己定義的,也可以是Spring Boot中默認的參數。
注意:命令行參數在app.jar的后面!
可以通過SpringApplication.setAddCommandLineProperties(false)禁用命令行配置。
Java系統屬性
注意Java系統屬性位置java -Dname=”isea533″ -jar app.jar,可以配置的屬性都是一樣的,優先級不同。
例如java -Dname=”isea533″ -jar app.jar –name=”Spring!”中name值為Spring.
有些系統,關於一些數據庫或其他第三方賬戶等信息,由於安全問題,其配置並不會提前配置在項目中暴露給開發人員。
對於這種情況,我們在運行程序的時候,可以通過參數指定一個外部配置文件。
以 demo.jar 為例,方法如下: java -jar demo.jar –spring.config.location=/opt/config/application.properties
其中文件名隨便定義,無固定要求。
RandomValuePropertySource
RandomValuePropertySource 可以用來生成測試所需要的各種不同類型的隨機值,從而免去了在代碼中生成的麻煩。RandomValuePropertySource 可以生成數字和字符串。數字的類型
包含 int 和 long,可以限定數字的大小范圍。以“random.”作為前綴的配置屬性名稱由 RandomValuePropertySource 來生成:
系統中用到隨機數的地方,例如 使用 RandomValuePropertySource 生成的配置屬性:
user.id=${random.value}
user.count=${random.int}
user.max=${random.long}
user.number=${random.int(100)}
user.range=${random.int[100, 1000]
random.int*支持value參數和,max參數,當提供max參數的時候,value就是最小值
3. 讀取屬性
讀取application.yml中的localhost配置的值
package com.gwc.context;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Created by gwcheng on 2017/3/6.
*/
@Component
@ConfigurationProperties
public class SystemProperties
{
private String localhost;
public String getLocalhost() {
return localhost;
}
public void setLocalhost(String localhost) {
this.localhost = localhost;
}
}
還可以在Bean中使用
@Value(“${server.port}”)
private String serverPort;
來讀取配置文件中的屬性值
當前目錄的“/config”子目錄。
當前目錄。
classpath 中的“/config”包。
classpath
上面的順序也表示了該位置上包含的屬性文件的優先級。優先級按照從高到低的順序排列。 即:/config優先於classpath根目錄
可以通過“spring.config.name”配置屬性來指定不同的屬性文件名稱。也可以通過“spring.config.location”來添加額外的屬性文件的搜索路徑。如果應用中包含多個 profile,
可以為每個 profile 定義各自的屬性文件,按照“application-{profile}”來命名。
@PropertySource優先級比較
這個注解可以指定具體的屬性配置文件,優先級比較低。
SpringApplication.setDefaultProperties
例如:
SpringApplication application = new SpringApplication(Application.class);
Map<String, Object> defaultMap = new HashMap<String, Object>();
defaultMap.put(“name”, “Isea-Blog”);
//還可以是Properties對象
application.setDefaultProperties(defaultMap);
application.run(args);
應用程序使用屬性@Value(“${xxx}”)
對於配置屬性,可以在代碼中通過“@Value”來使用,如:
@RestController
@EnableAutoConfiguration
public class Application {
@Value(“${name}”)
private String name;
@RequestMapping(“/”)
String home() {
return String.format(“Hello %s!”, name);
}
}
變量 name 的值來自配置屬性中的“name”屬性。
比如application.properties有 port=8081
則
@Value(“${port:8082}”)
private String port;
即可獲取8081這個值
屬性占位符
例如:
app.name=MyApp
app.description=${app.name} is a Spring Boot application
可以在配置文件中引用前面配置過的屬性(優先級前面配置過的這里都能用)。
通過如${app.name:默認名稱}方法還可以設置默認值,當找不到引用的屬性時,會使用默認的屬性。
由於${}方式會被Maven處理。如果你pom繼承的spring-boot-starter-parent,Spring Boot 已經將maven-resources-plugins默認的${}方式改為了@ @方式,例如@name@。
如果你是引入的Spring Boot,你可以修改使用其他的分隔符
通過屬性占位符還能縮短命令參數
例如修改web默認端口需要使用–server.port=9090方式,如果在配置中寫上:
server.port=${port:8080}
那么就可以使用更短的–port=9090,當不提供該參數的時候使用默認值8080。
屬性名匹配規則
例如有如下配置對象:
@Component
@ConfigurationProperties(prefix=”person”)
public class ConnectionSettings {
private String firstName;
}
firstName可以使用的屬性名如下:
person.firstName,標准的駝峰式命名
person.first-name,虛線(-)分割方式,推薦在.properties和.yml配置文件中使用
PERSON_FIRST_NAME,大寫下划線形式,建議在系統環境變量中使用
屬性驗證
可以使用JSR-303注解進行驗證,例如:
@Component
@ConfigurationProperties(prefix=”connection”)
public class ConnectionSettings {
@NotNull
private InetAddress remoteAddress;
// … getters and setters
}
這些內容已經足夠本文使用來做一個可以配置的連接mongodb的程序了, 網絡上還有更多的介紹關於spring boot屬性文件的, 大家可以去百度搜搜。
二. 改造 spring boot的mongodb項目
1. 改造中遇到的一個問題
經過上面的關於spring boot的摸索, 編寫了如下一個代碼
package cn.iigrowing.study.mongodb; ... 代碼詳情后面會提供 @Component // 這個很重要, 否則類不會被掃描 public class MyMongoClient { @Bean // 生命這個是一個bean @ConditionalOnMissingBean(MongoClient.class) // 說明這個替換那個默認的 public MongoClient getMongodbClients() { ..... MongoCredential credential = MongoCredential.createMongoCRCredential(myUsername, database, myPassword.toCharArray()); MongoClient client = new MongoClient(addresses, Arrays.asList(credential)); return client; } // mongodb://192.168.128.189:27017,192.168.128.132:27017,192.168.128.133:27017/mytest03 @Value("${mongodb.uri}") // 獲取uri然后進行解析 private String myUri; @Value("${mongodb.username}") // 獲取用戶名 private String myUsername; @Value("${mongodb.password}") // 獲取密碼 private String myPassword; }
然后啟動程序, 最后報如下異常:
java.lang.IllegalStateException: Failed to execute CommandLineRunner at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:779) at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760) at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:747) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151) at cn.iigrowing.study.mongodb.Application.main(Application.java:14) Caused by: org.springframework.data.mongodb.UncategorizedMongoDbException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 }; nested exception is com.mongodb.MongoCommandException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 } at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:107) at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2135) at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:481) at org.springframework.data.mongodb.core.MongoTemplate.insertDBObject(MongoTemplate.java:1046) at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:855) at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:796) at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:80) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy45.save(Unknown Source) at cn.iigrowing.study.mongodb.Application.run(Application.java:28) at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776) ... 6 common frames omitted Caused by: com.mongodb.MongoCommandException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 } at com.mongodb.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:115) at com.mongodb.connection.WriteCommandProtocol.receiveMessage(WriteCommandProtocol.java:268)
為了查明原因仔細檢查了前文: spring boot訪問mongodb副本集方法1
里面的訪問方法, 並且運行了那個程序, 程序正確執行, 說明用戶名和密碼等都正確, 那到底是哪里的問題?
最后發現兩個項目的默認配置文件不同, 如下圖
原因分析, 在spring boot中 ,程序會自動掃描配置, 根據這些配置屬性, 來啟動一系列的相關程序, 在剛剛有問題程序中, 由於沒有采用默認的spring的mongodb的配置名稱, 可能造成spring boot的mongodb環境不完整,因此有問題, 然后修改配置問題后, 啟動程序, 程序正確運行了。
另外程序運行過程中有大量日志輸出, 不利觀察程序運行情況, 對日志進行了設置, 請參考: Spring Boot日志管理
然后在application.properties 文件中, 添加 logging.level.root=INFO
修改了默認的日志級別
2. 修正后的改造
修改應用的 application.properties 配置文件如下
spring.data.mongodb.username=mytest03 spring.data.mongodb.password=password spring.data.mongodb.uri=mongodb://192.168.128.189:27017,192.168.128.132:27017,192.168.128.133:27017/mytest03 logging.level.root=INFO
以及讀取的配置文件的部分
@Value("${spring.data.mongodb.uri}") private String myUri; @Value("${spring.data.mongodb.username}") private String myUsername; @Value("${spring.data.mongodb.password}") private String myPassword;
然后從新編譯了程序, 啟動程序后運行正確