Spring Boot學習筆記


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

spring-initializer

生成一個項目名為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

spring-initializer-tool

spring-initializer-tool2

使用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為例說明覆蓋自動化配置

  1. 指定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管理工具。
查看MBeanstab頁org.springframework.boot下面的內容。

spring-boot-jconsole

定制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提供了CounterServiceGaugeService兩個接口及其實現,會在應用程序中自動注入,用於簡單地記數和測值。
這兩個接口內容如下

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文件。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM