說到框架,就肯定無法避開configuration的議題,這篇將會來探討Spring有哪些default configuration以及如何設定custom configuration。
說到Spring的configuration,第一直覺一定是想到application.properties檔或者application.yaml檔,這邊也是主要來談這個方式的configuration,不過其實不只透過application.properties才能進行config,諸如JVM, OS env var, cmd-line args等等也都可以是SpringBootApplication能吃進來的config properties。SpringBoot會將這些來源全都透過抽象化的介面接起來,並實際注入到Spring application context的instances之中。
以下為設定tomcat server port的範例:
#application.properties
server.port = 8081
#application.yaml
server:
port: 8081
$ java -jar sample-0.0.5-SNAPSHOT.jar --server.port=8081
$ export SERVER_PORT=8081
先前有提到,我們可以放schema.sql以及data.sql在src/main/resources,Spring application啟動的時候會自動執行這兩個sql;但若不只有這兩個sql想在啟動時執行呢?可進行以下設定:
spring:
datasource:
schema:
- order-schema.sql
- ingredient-schema.sql
data:
- ingredients.sql
或可透過JNDI的方式來讀取:
spring:
datasource:
jndi-name: java:/comp/env/jdbc/demoAppDS
再來是介紹SSL的設定:
server:
port: 8443
ssl:
key-store: file://path/to/mykeys.jks
key-store-password: dummypwd
key-password: dummypwd
常常也會需要設定logging,SpringBoot預設是使用Logback來進行log詳細的設定,我們可以在src/main/resources中建立logback.xml來進行細部的設定。
不過若只是要設定level和將log產生在哪裡,可以直接透過application.properties來進行:
logging:
level:
root: WARN
org.springframework.security: DEBUG
file:
path: /var/logs
file: DemoApp.log
預設當log file達到10mb的時候就會rotate紀錄
在application.properties中也可以引用其他的properties當作變數來設定值:
greeting:
welcome: You are using ${spring.application.name}
除了預設可以設定的屬性外,我們還可以自定義屬性,並在java程式中引入這些appication.properties設定的屬性值進來,而要做到這點就是透過@ConfigurationProperties。
#application.yaml
demo:
orders:
pageSize: 10
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
@ConfigurationProperties(prefix="demo.orders")
public class OrderController {
private int pageSize = 20;
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
...
@GetMapping
public String ordersForUser(
@AuthenticationPrincipal User user, Model model) {
Pageable pageable = PageRequest.of(0, pageSize);
model.addAttribute("orders",
orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
return "orderList";
}
}
除了直接注入到所需的class中,我們可以將這些properties分隔出來管理:
package demo.web;
import org.springframework.boot.context.properties.
ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Component
@ConfigurationProperties(prefix="demo.orders")
@Data
public class OrderProps {
private int pageSize = 20;
}
private OrderProps props;
public OrderController(OrderRepository orderRepo,
OrderProps props) {
this.orderRepo = orderRepo;
this.props = props;
}
...
@GetMapping
public String ordersForUser(
@AuthenticationPrincipal User user, Model model) {
Pageable pageable = PageRequest.of(0, props.getPageSize());
model.addAttribute("orders",
orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
return "orderList";
}
這樣不論是java程式的屬性設定,還是@ConfigurationProperties的設定都可以集中在Props類別中管理。
而我們自定義的properties在apppication.properties會被IDE警告說這是未定義的properties,我們可以新增src/main/resources/META-INF/additional-spring-configuration-metadata.json
{"properties": [{
"name": "demo.orders.page-size",
"type": "java.lang.String",
"description": "A description for 'demo.orders.page-size'"
}]}
以上是透過Spring Suite Tool IDE功能自動生出來的,可能會不太準確,不過格式就是長這樣,當我們有設定這個後,在application.properties就不會有warning,而且當我們把鼠標移到properties上時,還會顯示出我們剛剛定義的內容變成說明。
我們在開發系統時,一定會有很多個環境的問題,不同的環境就可能會有不同的appication.properties,這時該怎麼辦呢?大致在SpringBoot有兩種方法。
一種是直接在檔名作區別:application-{env}.properties,不過運作規則不是只讀取這個有標註env的properties檔,而是會先讀取application.properties,然後再參考application-{env}.properties,看是否有多的屬性,或者重複的屬性會override。
另一種是在同一份application.properties裡面做區隔:
logging:
level:
tacos: DEBUG
---
spring:
profiles: prod
datasource:
url: jdbc:mysql:/ /localhost/tacocloud
username: tacouser
password: tacopassword
logging:
level:
tacos: WARN
不同env的設定會透過”- - -”分隔,並會有spring.profiles=prod標註是哪個env。
然後在機器上執行的時候打上:
% java -jar taco-cloud.jar --spring.profiles.active=prod
以上代表要使用prod的application-prod.properties,並會先讀取application.properties,若有重複的屬性會使用prod的進行override。
@Bean也可以設定只被哪個profile取用:
@Bean
@Profile("dev")
public CommandLineRunner dataLoader(IngredientRepository repo,
UserRepository userRepo, PasswordEncoder encoder) {
...
}
甚至還可以用"!”
@Bean
@Profile("!prod")
public CommandLineRunner dataLoader(IngredientRepository repo,
UserRepository userRepo, PasswordEncoder encoder) {
...
}