http://blog.csdn.net/gezhonglei2007/article/details/51686094
***********************************
spring Boot帶來的四大特性
- 自動配置(Automatic configuration)
- Starter依賴(Starter dependencies)
- CLI(Command-line interface)
- Actuator: 在運行時查看Spring Boot項目的內部信息
注:Spring Boot除了下面介紹的基本內容外,還包括Groovy和Grails等工具帶來的許多新特性,但是為了掌握Spring Boot核心功能,這些基本功能已經夠用,等日后根據需要學習了groovy部分再補充。
開發Spring Boot應用程序示例
使用Spring Initializer初始化Spring Boot項目
初始化Spring Boot項目有以下四種方式:
- 使用網站接口 (http://start.spring.io)
- 通過Spring Tool Suite工具
- 使用IntelliJ IDEA
- 使用Spring Boot CLI
這幾種方式都需要聯網下載一個空的Demo項目源碼。
使用網站接口
在瀏覽器中輸入http://start.spring.io,輸入項目依賴和其它信息,點擊按鈕生成並下載一個zip項目壓縮包。
重要輸入項如下:
- 構建工具:gradle或maven
- Spring Boot版本
- 項目元數據:Group和Artifact
- 依賴的Spring Starters
生成一個項目名為com.example.demo的maven項目,依賴於Web、Thymeleaf、JPA、H2,生成的project基本結構,如下:
readinglist
+-- pom.xml
+-- src
+-- main
+-- java
+-- readinglist
+-- ReadingListApplication.java
+-- resources
+-- application.properties
+-- static
+-- templates
+-- test
+-- java
+-- readinglist
+-- ReadingListApplicationTests.java
ReadingListApplication.Java文件內容如下:
package readinglist; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ReadingListApplication { public static void main(String[] args) { SpringApplication.run(ReadingListApplication.class, args); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意兩點:
1. @SpringBootApplication
由@Configuration
、@ComponentScan
、@EnableAutoConfiguration
三個注解組成,使Spring能夠自動掃描bean和自動化配置。
2. SpringApplication.run
將啟動應用程序。
使用Spring Tool Suite或IDEA
在Eclipse開發工具,選擇File -> New -> Spring Starter Project
使用CLI命令
示例如下:
spring init -dweb,data-jpa,h2,thymeleaf --build gradle readinglist
- 1
- 1
使用Starter依賴——編輯Maven或Gradle
指定基於門面模式的依賴
Spring Boot提供了starter項目依賴,極大地簡化了項目依賴的配置。
一個starter依賴就是一個maven pom,用於將完成某項功能的所有依賴組織到一起。
starter依賴是多個jar包的集合,不用擔心starter中jar包版本及jar間的兼容性問題,它已經過充分的測試。
Sring Boot提供的starter列表:http://docs.spring.io/spring-boot/docs/1.4.0.M3/reference/htmlsingle/#using-boot-starter-poms
查看項目的所有依賴
gradle dependencies
- 1
- 1
mvn dependency:tree
- 1
- 1
顯示地覆蓋start依賴
在某些特殊原因,我們還是需要指定自己的jar包(例如用於解決某個bug的最新版本jar包),在使用starter時,能夠覆蓋starterjar包指定我們需要的jar包。
# build.gradle compile("org.springframework.boot:spring-boot-starter-web") { exclude group: 'com.fasterxml.jackson.core' } compile("com.fasterxml.jackson.core:jackson-databind:2.4.3")
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> </exclusion> </exclusions> </dependency> <!-- 指定版本的jackson jar包 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.3</version> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
自動化配置
Spring Boot自動化配置是指在程序啟動時決定Spring哪些配置應用與不應用的過程。
每次啟動應用程序時,執行近200項(覆蓋安全、集成、持久化和Web開發等多個模塊)這樣的判斷。
Spring的自動化配置讓我們從復雜的程序配置中解脫出來,更加關注應用業務邏輯。
例如:
1. 如果在classpath路徑下的JdbcTemplate是否可用?如果存在DataSource bean,將會自動配置一個JdbcTemplate bean
2. classpath下是否存在Thymeleaf?如果存在,將自動配置一個Thymeleaf模板resolver、view resolver和 template engine。
3. classpath下是否存在Spring Security?如果存在,配置一個基本web安全模式。
應用程序功能
# /src/main/java/readinglist/Book.java
package readinglist; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Book { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String reader; private String isbn; private String title; private String author; private String description; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getReader() { return reader; } public void setReader(String reader) { this.reader = reader; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
# /src/main/java/readinglist/ReadingListRepository.java
package readinglist; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface ReadingListRepository extends JpaRepository<Book, Long> { List<Book> findByReader(String reader); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
# /src/main/java/readinglist/ReadingListController.java
package readinglist; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.util.List; @Controller @RequestMapping("/") public class ReadingListController { private ReadingListRepository readingListRepository; @Autowired public ReadingListController( ReadingListRepository readingListRepository) { this.readingListRepository = readingListRepository; } @RequestMapping(value="/{reader}", method=RequestMethod.GET) public String readersBooks(@PathVariable("reader") String reader, Model model) { List<Book> readingList = readingListRepository.findByReader(reader); if (readingList != null) { model.addAttribute("books", readingList); } return "readingList"; } @RequestMapping(value="/{reader}", method=RequestMethod.POST) public String addToReadingList(@PathVariable("reader") String reader, Book book) { book.setReader(reader); readingListRepository.save(book); return "redirect:/{reader}"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
<!-- src/main/resources/templates/readingList.html --> <html> <head> <title>Reading List</title> <link rel="stylesheet" th:href="@{/style.css}"></link> </head> <body> <h2>Your Reading List</h2> <div th:unless="${#lists.isEmpty(books)}"> <dl th:each="book : ${books}"> <dt class="bookHeadline"> <span th:text="${book.title}">Title</span> by <span th:text="${book.author}">Author</span> (ISBN: <span th:text="${book.isbn}">ISBN</span>) </dt> <dd class="bookDescription"> <span th:if="${book.description}" th:text="${book.description}">Description</span> <span th:if="${book.description eq null}">No description available</span> </dd> </dl> </div> <div th:if="${#lists.isEmpty(books)}"> <p>You have no books in your book list</p> </div> <hr/> <h3>Add a book</h3> <form method="POST"> <label for="title">Title:</label> <input type="text" name="title" size="50"></input><br/> <label for="author">Author:</label> <input type="text" name="author" size="50"></input><br/> <label for="isbn">ISBN:</label> <input type="text" name="isbn" size="15"></input><br/> <label for="description">Description:</label><br/> <textarea name="description" cols="80" rows="5"> </textarea><br/> <input type="submit"></input> </form> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
/* src/main/resources/static/style.css */ body { background-color: #cccccc; font-family: arial,helvetica,sans-serif; } .bookHeadline { font-size: 12pt; font-weight: bold; } .bookDescription { font-size: 10pt; } label { font-weight: bold; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.3 運行程序
運行應用程序,有以下幾種方式
Gradle: bootRun
Maven: spring-boot:run Spring Suit Tools: Run As -> Spring Boot App
- 1
- 2
- 3
- 1
- 2
- 3
2.4 程序打包
打包格式:jar、war
Gradle: Maven: mvn clean package CLI:
- 1
- 2
- 3
- 1
- 2
- 3
配置定制(Customizing configuration)
配置定制有兩種方式:明確地覆蓋自動化配置
和基於屬性的擴展配置
覆蓋Spring自動化配置的原理
在添加Spring Boot到應用程序中時,會添加spring-boot-autoconfigure.jar
,它包含大量地配置類。
這些配置類在應用程序的classpath環境都可用,除非你明確指定了這些配置覆蓋它們。
那些實現對這些配置類中的配置的覆蓋呢?——使用條件注解@Condition
例如在應用程序中指定了JdbcTemplate,就會使用用戶自定義,否則使用默認配置類中的JdbcTemplate。
實現這一目標的自定義Condition注解如下:
package readinglist; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class JdbcTemplateCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { try { context.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate"); return true; } catch (Exception e) { return false; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
// 如果在classpath路徑下JdbcTemplate可用,就會創建MyService bean,否則不創建。 @Conditional(JdbcTemplateCondition.class) public MyService myService() { //... }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
Spring Boot定義了很多這樣的條件類
Conditional annotation | Configuration applied if…? |
---|---|
@ConditionalOnBean | …the specified bean has been configured |
@ConditionalOnMissingBean | …the specified bean has not already been configured |
@ConditionalOnClass | …the specified class is available on the classpath |
@ConditionalOnMissingClass | …the specified class is not available on the classpath |
@ConditionalOnExpression | …the given Spring Expression Language (SpEL) expression evaluates to true |
@ConditionalOnJava | …the version of Java matches a specific value or rangeof versions |
@ConditionalOnJndi | …there is a JNDI InitialContext available and optionally given JNDI locations exist |
@ConditionalOnProperty | …the specified configuration property has a specific value |
@ConditionalOnResource | …the specified resource is available on the classpath |
@ConditionalOnWebApplication | …the application is a web application |
@ConditionalOnNotWebApplication | …the application is not a web application |
使用Spring Security為例說明覆蓋自動化配置
- 指定spring sercurity starter:
gradle構建時,在build.gradle中添加:
compile("org.springframework.boot:spring-boot-starter-security")
- 1
- 1
maven構建時,在pom.xml文件中添加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
程序運行時,在控制台會輸出隨機生成的密碼用於程序運行測試,如下
Using default security password: d9d8abe5-42b5-4f20-a32a-76ee3df658d9
- 1
- 1
默認的安全配置幾乎不可用,我們需要定義自己的安全配置類,能夠配置頁面權限以及獲取用戶權限。我們定義了安全配置類時,運行應用時會自動覆蓋安全模塊jar包中的默認配置。
// src/main/java/readinglist/SecurityConfig.java package readinglist; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ReaderRepository readerRepository; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").access("hasRole('READER')") .antMatchers("/**").permitAll() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error=true"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return readerRepository.findOne(username); } }); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
// src/main/java/readinglist/ReaderRepository.java package readinglist; import org.springframework.data.jpa.repository.JpaRepository; public interface ReaderRepository extends JpaRepository<Reader, String> { }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
// src/main/java/readinglist/Reader.java package readinglist; import java.util.Arrays; import java.util.Collection; import javax.persistence.Entity; import javax.persistence.Id; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity public class Reader implements UserDetails { private static final long serialVersionUID = 1L; @Id private String username; private String fullname; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } // UserDetails methods @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Arrays.asList(new SimpleGrantedAuthority("READER")); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
再看如何覆蓋SpringBoot的自動化配置
通過以下兩個示例說明,覆蓋SpringBoot自動化配置的工作原理
例一
@Bean @ConditionalOnMissingBean(JdbcOperations.class) public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(this.dataSource); }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
ConditionalOnMissingBean指定用於覆蓋JdbcTemplate的條件:在
如果未配置JdbcOperations類型的Bean,將從jdbcTemplate()方法中獲取JdbcTemplate的Bean對象
如配置了JdbcTemplate Bean的同時會自動配置JdbcOperations。
因此,如果我們定義了jdbcTemplate-Bean,SpringBoot自動化配置(這里的jdbcTemplate())將不會生效。
例二
@Configuration @EnableConfigurationProperties @ConditionalOnClass({ EnableWebSecurity.class }) @ConditionalOnMissingBean(WebSecurityConfiguration.class) @ConditionalOnWebApplication public class SpringBootWebSecurityConfiguration { //... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
SpringBootWebSecurityConfiguration配置能夠生效的條件如下:
① EnableWebSecurity類有效
② 沒用定義WebSecurityConfiguration-Bean
③ 必須是Web應用程序
使用屬性配置
默認配置屬性,請參考:http://docs.spring.io/spring-boot/docs/1.4.0.M3/reference/htmlsingle/#common-application-properties
在屬性配置中指定配置屬性,可以覆蓋自動化的默認配置。
屬性的指定方式:
- 命令行參數
- 來自java:comp/env的JNDI屬性
- JVM系統屬性
- 操作系統環境變量
- 以
random.*
為前綴的隨機生成屬性 - 應用程序外部的application.properties或application.yml文件
- 應用程序內部的application.properties或application.yml文件
- 使用
@PropertySource
指定的屬性源 - 默認屬性
其中,application.properties或application.yml文件可以存在於四個地方
- 應用程序運行目錄的
/config
子目錄 - 應用程序運行目標
- 在以
config
命名的包中 - 在classpath的根目錄
優先級:從上到下依次降低
示例:在命令行中運行Spring Boot時會出現Spring Boot這幾個大的藝術字,如何禁用它?
只需要指定spring.main.show-banner為false即可。
可以在application.yaml中指定
spring:
main:
show-banner: false
- 1
- 2
- 3
- 1
- 2
- 3
可以在application.properties指定
spring.main.show-banner=false
- 1
- 1
也可以在命令行中運行程序時以參數指定屬性
java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.main.show-banner=false
- 1
- 1
如命令行中不支持參數,在運行命令之前指定系統環境變量也行(注意:環境變量不支持點分隔,所以用下划線代替)
export spring_main_show_banner=false
- 1
- 1
還有一些常用屬性配置項如下:
禁用模板緩存
# 測試環境中禁用模板緩存 # spring.thymeleaf.cache=false # spring.freemarker.cache=false # spring.groovy.template.cache=false # spring.velocity.cache=false # 以thymeleaf為例 spring: thymeleaf: cache: false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
指定內嵌服務器端口
# 指定服務器端口 server port:8000 ##### 內嵌服務器配置ssl ##### # 先用jdk的keytool工具生成jks文件 # keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA # 在application.yaml文件中添加 server: port: 8443 ssl: key-store: file:///path/to/mykeys.jks key-store-password: letmein key-password: letmein #############################
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
配置日志
# 將日志寫到文件中 logging.path=/var/logs/ logging.file=BookWorm.log # 指定日志級別(默認INFO級別) logging.level.root=WARN logging.level.root.org.springframework.security=DEBUG # 指定自己日志配置文件 logging.config.classpath:logging-config.xml
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
也可以yaml寫法如下
logging:
level:
root: WARN org: springframework: security: DEBUG
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
另一個收縮寫法(混合寫法)
logging: level: root: WARN org.springframework.security: DEBUG
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
配置數據源
spring.datasource.url=jdbc:mysql://localhost/readinglist spring.datasource.username=dbuser spring.datasource.password=dbpass # 無需指定driver,可根據數據庫url推斷 # spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 使用JNDI數據源(設置JNDI后,其它數據庫連接配置將被忽略) spring.datasource.jndi-name=java:/comp/env/jdbc/readingListDS
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
自定義屬性配置Bean
假設要在readinglist.html中使用屬性文件中的amazonID配置屬性
<a th:href="'http://www.amazon.com/gp/product/'+ ${book.isbn} + '/tag=' + ${amazonID}" th:text="${book.title}">Title</a>
- 1
- 2
- 3
- 1
- 2
- 3
需要在ReadingListController中返回view前在model中指定amazonID屬性。
而Controller中的associateId屬性來自配置文件。
...
@Controller @RequestMapping("/") @ConfigurationProperties(prefix="amazon") public class ReadingListController { // readersBooks方法修改如下 @RequestMapping(method=RequestMethod.GET) public String readersBooks(Reader reader, Model model) { List<Book> readingList =readingListRepository.findByReader(reader); if (readingList != null) { model.addAttribute("books", readingList); model.addAttribute("reader", reader); model.addAttribute("amazonID", associateId); } return "readingList"; } private String associateId; public void setAssociateId(String associateId) { this.associateId = associateId; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
@ConfigurationProperties指定待注入配置中以amazon為前綴的屬性。
# application.properties amazon.associateId=habuma-20
- 1
- 2
- 1
- 2
注:其一,Spring Boot自動化配置已經配置了@EnableConfigurationPropertiess,因此這里可以直接使用@ConfigurationProperties是沒有問題的
其二,Spring Boot的屬性解析器,能夠自動識別駝峰標識和不同分隔符的屬性,例如amazon.associate_id和amazon.associate-id,都可以識別並注入到Bean的associateId屬性
可以將屬性單獨注入到一個類實體中,然后將實體注入到Controller,從實體取出所有屬性。
@Component @ConfigurationProperties("amazon") public class AmazonProperties { private String associateId; public void setAssociateId(String associateId) { this.associateId = associateId; } public String getAssociateId() { return associateId; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
@Controller @RequestMapping("/") public class ReadingListController { private ReadingListRepository readingListRepository; private AmazonProperties amazonProperties; @Autowired public ReadingListController(ReadingListRepository readingListRepository, // 將AmazonProperties實體注入進來,后面直接從AmazonProperties中屬性值 AmazonProperties amazonProperties) { this.readingListRepository = readingListRepository; this.amazonProperties = amazonProperties; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
配置Profile
在不同的運行環境,開發、測試或生產環境,應用程序的配置可能有所不同,例如數據庫配置、安全策略、緩存等。
創建好多個環境下的不同配置,然后在配置文件或命令行中指定特定的運行環境,啟動特定環境下的配置。
@Profile("production") @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //... }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
在配置文件或命令行中指定屬性spring.profiles.active=production
,運行程序時,就會啟動上述配置的Bean。
以上是通過@Profile
注解定義的不同運行環境下的不同配置,還可以通過配置文件來定義不同運行環境下的配置。
屬性文件定義不同運行環境下的配置
不同運行環境下的屬性配置文件命名規則:application-{profile}.properties
application.properties中配置屬性作為默認屬性生效。根據spring.profiles.active屬性(可以來自屬性配置文件中,也可以來自命令行),
選擇相應運行環境的屬性配置文件覆蓋application.properties中的默認屬性。
Yaml文件定義不同運行環境下的配置
YAML文件也可以跟屬性配置一樣使用application-{profile}.yml
模式來定義不同運行環境的配置。
此外,YAML可以根據自身特性,在一個文件中通過---
分段來定義不同運行環境下的配置。
logging: level: root: INFO --- spring: profiles: development logging: level: root: DEBUG --- spring: profiles: production logging: path: /tmp/ file: BookWorm.log level: root: WARN
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
自定義錯誤頁面
Spring Boot自動化配置,默認提供了一個whitelabel的錯誤頁面。
Spring Boot自動配置的error Handler,查找名稱為error的view,如果找不到,則會顯示whitelabel錯誤頁面。
error視圖,最終取決於視圖解析的結果。能夠被視圖解析內容包括:
- ID為error,實現了View接口的Bean
- 名稱為”error.html”的Thymeleaf模板(如果配置了Thymeleaf)
- 名稱為”error.ftl”的FreeMarker模板(如果配置了Velocity)
- 名稱為”error.jsp”的jsp模板(如果使用jsp作為視圖)
在error視圖中可用屬性:
- timestamp:The time that the error occurred
- status:The HTTP status code
- error:The error reason
- exception:The class name of the exception
- message:The exception message (if the error was caused by an exception)
- errors:Any errors from a BindingResult exception (if the error was causedby an exception)
- trace:The exception stack trace (if the error was caused by an exception)
- path:The URL path requested when the error occurred
示例:src/main/resource/template/error.html
<html> <head> <title>Oops!</title> <link rel="stylesheet" th:href="@{/style.css}"></link> </head> <body> <div class="errorPage"> <span class="oops">Oops!</span><br/> <img th:src="@{/MissingPage.png}"></img> <p>There seems to be a problem with the page you requested (<span th:text="${path}"></span>).</p> <p th:text="${'Details: ' + message}"></p> </div> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
測試
Spring Boot在運行應用程序時提供自動化配置,同樣,在測試時也需要由Spring Boot完成這些基礎自動化配置。
測試Spring Boot應用程序時,Spring Boot通過執行自動化配置和啟動web服務器,對Spring的集成測試提供支持。
示例:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=AddressBookConfiguration.class) public class AddressServiceTests { @Autowired private AddressService addressService; @Test public void testService() { Address address = addressService.findByLastName("Sheman"); assertEquals("P", address.getFirstName()); assertEquals("Sherman", address.getLastName()); assertEquals("42 Wallaby Way", address.getAddressLine1()); assertEquals("Sydney", address.getCity()); assertEquals("New South Wales", address.getState()); assertEquals("2000", address.getPostCode()); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
@RunWidth
中指定SpringJUnit4ClassRunner
類,表示啟用集成測試,此類會加載Spring應用程序的context,並將context中的Bean注入到測試環境中。
@ContextConfiguration
指定如何加載context。
多數情況下,使用@SpringApplicationConfiguration
取代@ContextConfiguration
,它可使用SpringApplication跟生產環境一樣加載應用的context,
它比@ContextConfiguration
提供更多特性,例如啟用日志、加載屬性文件(application.properties或application.yml)。
Web應用測試
Spring MVC代碼示例:
@RequestMapping(method=RequestMethod.POST) public String addToReadingList(Book book) { book.setReader(reader); readingListRepository.save(book); return "redirect:/readingList"; }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
測試web應用正確方式是:發起HTTP請求的方式,並很好地評估它正確地處理了請求。
Spring Boot提供了兩種方式:
- Spring Mock MVC:在不需要啟動web應用服務器的情況下,最大限度地模擬servlet容器,實現對controller測試
- Web集成測試:在內嵌servlet容器(Tomcat或jetty)中啟動應用進行測試
前者因為不需要啟動web server,不需要啟動瀏覽器,所以速度更快,但測試不夠完整。而后者更接近真實環境,但是缺點也是明顯的。
Mocking Spring MVC
從Spring 3.2開始,Spring Framework就可以使用mocking Spring MVC來測試web應用。
它模擬HTTP請求,訪問Controller。
可以使用MockMvcBuilders
啟動Mock MVC
。MockMvcBuilders提供了以下兩個靜態方法:
- standaloneSetup():構建一個Mock MVC服務一個或多個手動創建和手動配置的controller
- webAppContextSetup():使用Spring應用的context來構建一個Mock MVC
這兩個方法最大不同是,前者需要手動地實例化controller,並手動注入測試環境中。它只適合對單個controller集中測試的場景。
后者依靠Spring加載controllers以及它的依賴。
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration( classes = ReadingListApplication.class) @WebAppConfiguration public class MockMvcWebTests { @Autowired private WebApplicationContext webContext; private MockMvc mockMvc; @Before public void setupMockMvc() { mockMvc = MockMvcBuilders.webAppContextSetup(webContext).build(); } @Test public void homePage() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/readingList")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.view().name("readingList")) .andExpect(MockMvcResultMatchers.model().attributeExists("books")) .andExpect(MockMvcResultMatchers.model().attribute("books", Matchers.is(Matchers.empty()))); } @Test public void postBook() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/readingList") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("title", "BOOK TITLE") .param("author", "BOOK AUTHOR") .param("isbn", "1234567890") .param("description", "DESCRIPTION")) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", "/readingList")); Book expectedBook = new Book(); expectedBook.setId(1L); expectedBook.setReader("craig"); expectedBook.setTitle("BOOK TITLE"); expectedBook.setAuthor("BOOK AUTHOR"); expectedBook.setIsbn("1234567890"); expectedBook.setDescription("DESCRIPTION"); mockMvc.perform(MockMvcRequestBuilders.get("/readingList")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.view().name("readingList")) .andExpect(MockMvcResultMatchers.model().attributeExists("books")) .andExpect(MockMvcResultMatchers.model().attribute("books", hasSize(1))) .andExpect(MockMvcResultMatchers.model().attribute("books", contains(samePropertyValuesAs(expectedBook)))); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
Web安全測試
對Spring Security安全測試需要添加額外的jar包:spring-security-test
# build.gradle testCompile("org.springframework.security:spring-security-test")
- 1
- 2
- 1
- 2
<!-- pom.xml --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
在創建MockMvc實例之前,指定使用Spring Security。
@Before public void setupMockMvc() { mockMvc = MockMvcBuilders .webAppContextSetup(webContext) .apply(SecurityMockMvcConfigurers.springSecurity()) .build(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Spring Security提供了兩個注解用於執行授權的請求
- @WithMockUser:給定username、password、roles組成的UserDetails來加載security context
- @WithUserDetails:通過給定的username查找UserDetails對象來加載security context
@Test //@WithMockUser(username="craig",password="password",roles="READER") @WithUserDetails("craig") public void homePage_authenticatedUser() throws Exception { Reader expectedReader = new Reader(); expectedReader.setUsername("craig"); expectedReader.setPassword("password"); expectedReader.setFullname("Craig Walls"); mockMvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(view().name("readingList")) .andExpect(model().attribute("reader", samePropertyValuesAs(expectedReader))) .andExpect(model().attribute("books", hasSize(0))) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
集成測試
集成測試環境中,Spring Boot不僅要為測試創建應用context,還要啟動一個內嵌的servlet Container。
在應用運行在內嵌容器中,就可以發送一個真實的HTTP請求來評估結果。
示例:使用@WebIntegrationTest
在內嵌容器中啟動應用,並使用RestTemplate來發送HTTP請求,請求一個不存在的網頁返回HTTP 404錯誤。
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=ReadingListApplication.class) @WebIntegrationTest public class SimpleWebTest { @Test(expected=HttpClientErrorException.class) public void pageNotFound() { try { RestTemplate rest = new RestTemplate(); rest.getForObject("http://localhost:8080/bogusPage", String.class); fail("Should result in HTTP 404"); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); throw e; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
執行測試時,默認會在8080端口啟動Tomcat(如果classpath下存在Jetty或Undertow,將啟動這些容器)。
端口設定
server默認監聽端口是8080,對於一個機器上的單個測試沒有問題,但是如果被會導致測試失敗。
可在@WebIntegrationTest
中指定隨機端口來解決:
@WebIntegrationTest(value={"server.port=0"}) // 或簡寫如下 @WebIntegrationTest("server.port=0") //或指定屬性 @WebIntegrationTest(randomPort=true)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
指定server啟動時使用隨機端口,如何使用呢?
// 注入到成員變量中 @Value("${local.server.port}") private int port; // 使用成員變量 rest.getForObject("http://localhost:{port}/bogusPage", String.class, port);
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
使用Selenium測試HTMl網頁
添加Selenium依賴
# build.gradle testCompile("org.seleniumhq.selenium:selenium-java:2.45.0")
- 1
- 2
- 1
- 2
<!-- pom.xml --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>2.45.0</version> <scope>test</scope> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
測試代碼如下:
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration( classes=ReadingListApplication.class) @WebIntegrationTest(randomPort=true) public class ServerWebTests { @Value("${local.server.port}") private int port; private static FirefoxDriver browser; @BeforeClass public static void openBrowser() { // 使用Firefox驅動,也可以使用IE、Chrome等驅動,在應用啟動時自動打開相應的瀏覽器。 browser = new FirefoxDriver(); browser.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); } @AfterClass public static void closeBrowser() { browser.quit(); } @Test public void addBookToEmptyList() { String baseUrl = "http://localhost:" + port; browser.get(baseUrl); assertEquals("You have no books in your book list", browser.findElementByTagName("div").getText()); browser.findElementByName("title").sendKeys("BOOK TITLE"); browser.findElementByName("author").sendKeys("BOOK AUTHOR"); browser.findElementByName("isbn").sendKeys("1234567890"); browser.findElementByName("description").sendKeys("DESCRIPTION"); browser.findElementByTagName("form").submit(); WebElement dl = browser.findElementByCssSelector("dt.bookHeadline"); assertEquals("BOOK TITLE by BOOK AUTHOR (ISBN: 1234567890)", dl.getText()); WebElement dt = browser.findElementByCssSelector("dd.bookDescription"); assertEquals("DESCRIPTION", dt.getText()); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
Actuator
Actuator在Spring Boot應用程序中提供各種endpoints,用於查看應用程序的內部信息,以及用於生產環境的監控和計量指標。
Actuator以REST endpoints、遠程shell、JMX(Java Manager Extension)等三種方式提供這些特性。
這三種方式中,REST endpoints提供最完整的信息。
endpoints
能夠查看的Actuator Endpoints信息如下:
HTTP method | Path | Description |
---|---|---|
GET | /autoconfig | 提供自動化配置報告,描述自動化配置哪些條件通過哪些失敗 |
GET | /configprops | 顯示beans注入了哪些配置屬性(包括默認值) |
GET | /beans | 顯示應用程序context的所有beans以及它們之間的依賴關系 |
GET | /dump | 查看線程活動快照 |
GET | /env | 查看所有環境變量屬性 |
GET | /env/{name} | 查看指定名稱的環境變量 |
GET | /health | 查看關於應用程序的各類健康指標(由HealthIndicator的實現類提供的) |
GET | /info | 查看關於應用程序以info為前綴的自定義信息 |
GET | /mappings | 顯示URI與controller對應關系,包括Actuator endpoints |
GET | /metrics | 顯示關於應用程序的多種統計信息,像內存使用、http請求統計等 |
GET | /metrics/{name} | 根據名稱顯示某項統計信息 |
POST | /shutdown | 在endpoints.shutdown.enabled設置true的情況下,訪問些endpoints會立即關閉應用程序 |
GET | /trace | 提供HTTP請求的基本追蹤信息(像timestamp、headers等) |
所有這些endpoints可被分成三類:
- 配置類endpoints
- 計量類endpoints(metrics endpoints)
- 混雜類endpoints
查看方式
瀏覽器訪問REST
Spring Boot應用中添加Actuator相關jar包
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
# build.gradle compile 'org.springframework.boot:spring-boot-starter-actuator'
- 1
- 2
- 1
- 2
例如應用程序啟動時,訪問路徑:http://localhost:8080/readinglist
,你可以訪問beans信息如下:
http://localhost:8080/beans
遠程shell訪問Actuator
Spring Boot集成了CRaSH,內嵌於應用中,擴展了一些命令用於訪問endpoints。
Spring Boot應用中添加Actuator相關jar包
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-remote-shell</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
# build.gradle compile("org.springframework.boot:spring-boot-starter-remote-shell")
- 1
- 2
- 1
- 2
然后運行應用程序,在(控制台)日志中可以看到用於遠程SSH登陸密碼,默認用戶名user
:
Using default security password: efe30c70-5bf0-43b1-9d50-c7a02dda7d79
- 1
- 1
使用SSH工具,連接到應用的2000端口,用上面提供的密碼登陸
ssh user@localhost -p 2000
- 1
- 1
遠程ssh能夠訪問命令如下:
命令 | 描述 |
---|---|
autoconfig | 以純文件顯示自動化配置的信息,類似於/autoconfig enpoint |
beans | 類似於/beans endpoint |
endpoint | 觸發Actuator的endpoint,使用endpint list 查看可執行的endpoint |
metrics | 與/metrics endpoint類似 |
使用endpoint可以用endpint list
查看可執行的endpoint,然后執行endpoint invoke health
(例如執行health)
使用JMX監控應用程序
Java的JMX工具利用對MBeans管理實現對Java應用的監控。而Actuator將所有的endpoints作為MBeans,可在JMX工具中查看。
安裝JDK時,可以找到Jconsole.exe程序(程序路徑\JDK-Root\bin\JConsole.exe),將JConsoole.exe用作JMX管理工具。
查看MBeans
tab頁org.springframework.boot
下面的內容。
定制Actuator
可以定制Actuator的哪些內容?
- 重命名endpoints
- 啟用或禁用endpints
- 自定義metrics和gauges
- 為trace data創建自在定義的存儲方式
- 添加自定義的健康指標(health indicators)
重命名endpoints
在配置屬性中指定屬性(無論用properties文件還是YAML文件)。
例如,將shutdown endpoint更名為kill,修改如下:
endpoints.shutdown.id=kill
- 1
- 1
啟用與禁用endpoints
示例:
1. 禁用metrics: endpoints.metrics.enable=false
2. 禁用所有endpoints,而只開啟metrics:
endpoints.enable=false endpoints.metrics.enable=true
- 1
- 2
- 1
- 2
添加自定義metrics和gauges
Actuator提供了CounterService
和GaugeService
兩個接口及其實現,會在應用程序中自動注入,用於簡單地記數和測值。
這兩個接口內容如下
package org.springframework.boot.actuate.metrics; public interface CounterService { void increment(String metricName); void decrement(String metricName); void reset(String metricName); }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
package org.springframework.boot.actuate.metrics; public interface GaugeService { void submit(String metricName, double value); }
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
在Controller中應用示例如下:
@Controller @RequestMapping("/") @ConfigurationProperties("amazon") public class ReadingListController { ... private CounterService counterService; @Autowired public ReadingListController( ReadingListRepository readingListRepository, AmazonProperties amazonProperties, // 自動注入actuator提供的實現 CounterService counterService, GaugeService gaugeService) { this.readingListRepository = readingListRepository; this.amazonProperties = amazonProperties; this.counterService = counterService; this.gaugeService = gaugeService; } ... @RequestMapping(method=RequestMethod.POST) public String addToReadingList(Reader reader, Book book) { book.setReader(reader); readingListRepository.save(book); counterService.increment("books.saved"); gaugeService.submit("books.last.saved", System.currentTimeMillis()); return "redirect:/"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
Actuator也提供了PublicMetrics
接口,用於復雜數據計量,接口內容如下:
package org.springframework.boot.actuate.endpoint; public interface PublicMetrics { Collection<Metric<?>> metrics(); }
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
示例:
package readinglist; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; @Component public class ApplicationContextMetrics implements PublicMetrics { private ApplicationContext context; @Autowired public ApplicationContextMetrics(ApplicationContext context) { this.context = context; } @Override public Collection<Metric<?>> metrics() { List<Metric<?>> metrics = new ArrayList<Metric<?>>(); metrics.add(new Metric<Long>("spring.context.startup-date", context.getStartupDate())); metrics.add(new Metric<Integer>("spring.beans.definitions", context.getBeanDefinitionCount())); metrics.add(new Metric<Integer>("spring.beans", context.getBeanNamesForType(Object.class).length)); metrics.add(new Metric<Integer>("spring.controllers", context.getBeanNamesForAnnotation(Controller.class).length)); return metrics; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
創建自定義trace存儲
trace endpoint默認是由內存存儲的,且存儲個數限制在100個以內。僅適用於開發環境,在生產環境就會因內存存儲限制而丟失。
1.修改限制數
@Configuration public class ActuatorConfig { @Bean public InMemoryTraceRepository traceRepository() { InMemoryTraceRepository traceRepo = new InMemoryTraceRepository(); traceRepo.setCapacity(1000); return traceRepo; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.修改存儲方式:實現TraceRepository
接口(例如使用MongoDB存儲)
@Service public class MongoTraceRepository implements TraceRepository { private MongoOperations mongoOps; @Autowired public MongoTraceRepository(MongoOperations mongoOps) { this.mongoOps = mongoOps; } @Override public List<Trace> findAll() { return mongoOps.findAll(Trace.class); } @Override public void add(Map<String, Object> traceInfo) { mongoOps.save(new Trace(new Date(), traceInfo)); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
添加健康指標:實現HealthIndicator
接口
示例如下:
@Component public class AmazonHealth implements HealthIndicator { @Override public Health health() { try { RestTemplate rest = new RestTemplate(); rest.getForObject("http://www.amazon.com", String.class); return Health.up().build(); } catch (Exception e) { return Health.down().withDetail("reason", e.getMessage()).build(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
查看到AmazonHealth
健康指標如下:
{
"amazonHealth": { "reson": "I/O error on GET request for ...", "status": "DOWN" } }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
保護Actuator安全
1.限制只有管理員權限才可訪問某些endpoint(如shutdown), 並在內存中指定管理員
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").access("hasRole('READER')") //.antMatchers("/shutdown", "/metrics", "/configprops").access("hasRole('ADMIN')") .antMatchers("/shutdown").access("hasRole('ADMIN')") .antMatchers("/**").permitAll() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error=true"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails user = readerRepository.findOne(username); if (user != null) { return user; } throw new UsernameNotFoundException("User '" + username + "' not found."); } }) .and() .inMemoryAuthentication() .withUser("admin").password("s3cr3t") .roles("ADMIN", "READER"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
2.修改endpoint的context路徑
默認路徑是根路徑’/’,不帶項目名的。此路徑可以修改,示例如下:
management.context-path=/mgmt
- 1
- 1
然后設置訪問權限
.antMatchers("/mgmt/**").access("hasRole('ADMIN')")
- 1
- 1
部署
* Spring-Boot應用程序運行方式 *
- 1、在IDE中運行(IDE包括Spring ToolSuite或IntelliJ IDEA), Run As -> Spring Boot App
- 2、在Maven或Gradle的命令中運行
- Maven: spring-boot:run
- Gradle: bootRun
- 3、使用Maven或Gradle生成jar包,通過jar命令運行
- 4、在命令行中使用Spring Boot CLI運行Groovy腳本
- 5、使用Spring Boot CLI(將Groovy腳本)生成一個可在命令行中運行的jar文件
將Spring Boot項目生成war包
不考慮Groovy腳本,使用maven或gradle將應用程序打包成war包或jar包更適合。
如果打包為jar包內嵌java web容器(Tomcat或Jetty,默認Tomcat),可直接使用jar命令運行。
如果打包為war包,直接部署到已經存在的web容器中(Tomcat或Jetty),但是Spring Boot項目是自動化配置沒有web.xml,需要作額外處理才能打包war使用(見下文)。
在使用maven或gradle工具生成的war包前需要如下幾步:
1.配置SpringBootServletInitializer,用於代替web.xml
ReadingListServletInitializer.java
package readinglist; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.web.SpringBootServletInitializer; public class ReadingListServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.配置打包類型為war
,並運行打包指令
—- Maven —-
# pom.xml
<packaging>war</packaging>
- 1
- 2
- 1
- 2
運行maven命令
mvn package
- 1
- 1
—- gradle —-
apply plugin: 'war'
war {
baseName = 'readinglist'
version = '0.0.1-SNAPSHOT'
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
運行gradle命令
gradle build
- 1
- 1
3.運行
直接將war放置於web容器指定目錄,即可運行。
例如在Tomcat啟動時,將war包放置於<tomcat-root>/webapps
目錄下,Tomcat會檢測到war包,並立即自動解壓運行。
注意:上述打war包過程中,未移除Application的main函數,因此war實際上還可作為jar包直接運行(它在內嵌的tomcat或jetty中運行)。
例如,在命令行中運行
java -jar readinglist-0.0.1-SNAPSHOT.war
- 1
- 1
各種運行環境中運行
運行環境,一般分為開發環境、測試環境、生產環境等。
在不同的運行環境中,運行端口、數據庫配置、日志配置、緩存配置等可能不一樣。
如果不想在每個運行環境中都配置一次,可以提前配置好這些運行環境所需的配置,然后在運行時指定運行環境即可。
前面在介紹Spring Boot自動化配置中講到profile
,就是用來定義多種運行環境配置用的。
定義各運行環境的配置
- 使用
@Profile
注解 - 使用properties文件:使用application.properties定義共享配置,
application-{env}.properties
定義各個環境的差異配置 - 使用YAML文件:在一個yaml文件中用’—-‘分隔多個運行環境下的配置
* 指定運行環境 *
- 使用注解
@ActiveProfile
- 在properties或yaml文件中指定
spring.profile.active=prod
配置 - 定義環境變化:spring_profile_active=prod
- 運行jar的命令行參數:
jave -jar readinglist.jar -Dspring.profiles.active=prod
或
java -jar myapp.jar --spring.profiles.active=dev
示例:代碼中使用
@Profile('dev') @ActiveProfile('dev')
- 1
- 2
- 1
- 2
示例:在程序配置API指定spring.profiles.active
@Configuration @EnableAutoConfiguration @ComponentScan public class ProfileApplication { public static void main(String[] args) throws Exception { new SpringApplicationBuilder(ProfileApplication.class) .profiles("dev") .run(args); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
示例:配置文件
application.properties文件中屬性值 spring.profiles.active=dev 多個配置文件 application.properties (默認配置或公共配置) application-dev.properties application-prod.properties
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
集成maven和Spring boot的profile功能
原文地址:http://blog.csdn.net/lihe2008125/article/details/50443491
原理:
(1)maven命令行接受profile參數 -P
mvn clean package -Dmaven.test.skip=true -P dev -e
- 1
- 1
(2)maven配置文件pom.xml的build元素配置
<profiles> <profile> <id>dev</id> <properties> <!-- 自定義屬性profileActive --> <profileActive>dev</profileActive> </properties> <activation> <!-- 當前是默認profile --> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>test</id> <properties> <profileActive>test</profileActive> </properties> </profile> </profiles> <!-- 在子項目中引用自定義屬性來指定不同的配置 --> <build> <resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <excludes> <exclude>application-dev.properties</exclude> <exclude>application-test.properties</exclude> <exclude>application-prod.properties</exclude> </excludes> </resource> <resource> <!-- 處理文件時需要對文件進行變量替換 --> <filtering>true</filtering> <directory>src/main/resources</directory> <includes> <!-- 此處會動態替換${profileActive} --> <include>application-${profileActive}.properties</include> <include>application.properties</include> </includes> </resource> </resources> </build>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
(3)在配置文件中使用@符號引用來自maven配置的屬性變量
spring.profile.active=@profileActive@ env.info=@profileActive@
- 1
- 2
- 1
- 2
Spring配置多種數據源
* 定義數據源 *
# 主數據源,默認的 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 # 更多數據源 custom.datasource.names=ds1,ds2 custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1 custom.datasource.ds1.username=root custom.datasource.ds1.password=123456 custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2 custom.datasource.ds2.username=root custom.datasource.ds2.password=123456
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
使用:在需要DataSource的地方使用注解
@Autowired @Qualifier("ds1") private DataSource dataSource1; @Resource(name = "ds2") private DataSource dataSource2;
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
數據庫遷移
使用hibernate
Hibernate提供了hibernate.hbm2ddl.auto選項,可選擇none, create, create-drop,update
三種策略用於數據庫結構的創建與變更。
在Spring Boot環境中可為Hibernate配置spring.jpa.hibernate.ddl-auto
屬性。
這種由Hibernate提供的數據結構遷移方案,不太合適在生產環境中使用。其中create-drop
相當危險,會導致已有數據全部刪除。
定義schema.sql文件
(待完成)
使用數據庫遷移庫
- Flyway (http://flywaydb.org)
- Liquibase (www.liquibase.org)
Flyway使用簡單,使用sql腳本定義數據庫結構,因此不能兼容多個數據庫
Liquibase使用自己的語法定義數據庫結構,較繁瑣,支持的文件結構包括xml、yaml、json、sql等。
Flyway
使用Flyway前,先禁用hibernate的dll-auto功能:spring.jpa.hibernate.ddl-auto=none
然后添加flyway依賴(以maven為例),Spring boot自動化配置會檢測到它的存在,並啟動它。
<dependency> <groupId>org.flywayfb</groupId> <artifactId>flyway-core</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
再創建flyway的數據庫遷移腳本(多個文件),將它放在classpath的/db/migration
目錄下(src/main/resource/db/migration)
Flyway的腳本命名規則,示例:V1_initialize.sql
字母V后的數字,表示版本號,每次執行都會記錄每個文件的執行狀態,下次執行就不會重復執行了。
第一次執行版本是v1,后面數據庫結構有變化時,新建sql文件命名以v2,v3,…為前綴。
liquebase
添加依賴
<dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
Spring Boo自動化配置時檢測到它的依賴時,會自動啟用它。
默認查找在classpath根路徑下找/db/changelog/db.changelog-master.yaml
文件。