深入淺出boot2.0 16章 部署,測試 和 監控

  • JUnit 測試,Mockito的使用

打包

  • 使用war創建目錄後,IDE 會幫助 生成關於 web 應用所 需要的目錄
    • webapp目錄
    • 還會在 pom.xml 中添加 一些內容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
	<modelVersion>4.0.0</modelVersion>

	<groupId>springboot</groupId>
	<artifactId>chapter15</artifactId>
	<version>0.0.1-SNAPSHOT</version>
    
	<packaging>war</packaging>

	<name>chapter15</name>
	<description>chapter15 project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
  • mvn package
  • java -jar spring-0.0.1-snapshot.war
  • java -jar spring-0.0.1-snapshot.war --server.port=9080
  • 使用第三方非內嵌服務器,需要自己初始化 Dispatcher

    public class ServletInitializer extends SpringBootServletInitializer {
    
    	@Override
    	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    		return application.sources(Chapter15Application.class);
    	}
    
    }
    
    • mvc提供 ServletContainerinitializer的 實現類: SpringServletContainerInitializer
    • 此類:會遍歷 WebApplicationInitializer 接口的實現類。
    • 其中:SprigBootServletInitializer 就是其 實現類
  • 只需要將 xxx.war複製到 tomcat的 webapps目錄下,即可。

熱部署

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>
  • true 依賴不會傳遞,別的項目依賴當先項目,這個熱部署不會再該項目生效。
  • 熱部署通過,LiveReload進行支持的。
  • 熱部署 有很多配置,自己看吧

測試

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
  • 支持 Jpa ,MongoDB,Rest,Redis
  • Mock測試
@RunWith(SpringRunner.class) //所載入的類 是Spring 結合 JUnit的運行

//使用隨機端口啓動測試服務。配置測試的相關功能
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

public class Chapter16ApplicationTests {

	// 注入用戶服務類
    @Autowired
    private UserService userService = null;

    @Test
    public void contextLoads() {
        User user = userService.getUser(1L);
        // 判斷用戶信息是否爲空
        Assert.assertNotNull(user);
    }
    
 // REST測試模板,Spring Boot自動提供
    @Autowired
    private TestRestTemplate restTemplate = null;

    // 測試獲取用戶功能
    @Test
    public void testGetUser() {
        // 請求當前啓動的服務,請注意URI的縮寫
        User user = this.restTemplate.getForObject("/user/{id}",
                User.class, 1L);
        Assert.assertNotNull(user);
    }
    
    
    
    @MockBean
    private ProductService productService = null;

    @Test
    public void testGetProduct() {
        // 構建虛擬對象
        Product mockProduct = new Product();
        mockProduct.setId(1L);
        mockProduct.setProductName("product_name_" + 1);
        mockProduct.setNote("note_" + 1);
        // 指定Mock Bean方法和參數
        BDDMockito.given(this.productService.getProduct(1L))
                // 指定返回的虛擬對象
                .willReturn(mockProduct);
        
        // 進行Mock測試
        Product product = productService.getProduct(1L);
        Assert.assertTrue(product.getId() == 1L);
    }

}


	public Product getProduct(Long id) {
		throw new RuntimeException("未能支持該方法");
	}

mock測試

  • 在測試過程中,用一個虛擬的對象 來創建 以便測試的測試方法
  • getProduct(1L) 當前無法調度產品微服務,mock可以給一個虛擬的產品
  • @MockBean 對那個bean 進行 Mock測試

actuator 監控端點

		<dependency>
			<groupId>org.springframework.hateoas</groupId>
			<artifactId>spring-hateoas</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
  • hateoas 是 REST 架構風格中 複雜的約束,構建成熟REST服務的依賴。

actuator 端點描述

  • health
  • httptrace 最新追蹤信息(默認一百條)
  • info
  • mappings 所有映射路徑
  • scheduledtasks 顯示定時任務
  • shutdown

http 監控

  • http://localhost:8080/actuator/health
  • http://localhost:8080/actuator/beans 需要開啓
  • 默認值暴露 info 和 health
# 暴露所有端點 info,health,beans
management.endpoints.web.exposure.include=*

#不暴露這個端點
management.endpoints.web.exposure.exclude=env


# 默認情況下所有端點都不啓用,此時你需要按需啓用端點
management.endpoints.enabled-by-default=false
# 啓用端點 info
management.endpoint.info.enabled=true
# 啓用端點 beans
management.endpoint.beans.enabled=true
management.endpoint.health.enabled=true
management.endpoint.dbcheck.enabled=true
# Actuator端點前綴
management.endpoints.web.base-path=/manage

management.endpoint.health.show-details=when-authorized

management.health.db.enabled=true

查看敏感信息

  • 上面全暴露了,很不完全
@SpringBootApplication(scanBasePackages = "com.springboot.chapter16")
@MapperScan(basePackages = "com.springboot.chapter16", annotationClass = Mapper.class)
public class Chapter16Application extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 密碼編碼器
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 使用內存存儲
        auth.inMemoryAuthentication()
                // 設置密碼編碼器
                .passwordEncoder(passwordEncoder)
                // 註冊用戶admin,密碼爲abc,並賦予USER和ADMIN的角色權限
                .withUser("admin")
                // 可通過passwordEncoder.encode("abc")得到加密後的密碼
                .password("$2a$10$5OpFvQlTIbM9Bx2pfbKVzurdQXL9zndm1SrAjEkPyIuCcZ7CqR6je").roles("USER", "ADMIN")

                // 連接方法and
                .and()

                // 註冊用戶myuser,密碼爲123456,並賦予USER的角色權限
                .withUser("myuser")
                // 可通過passwordEncoder.encode("123456")得到加密後的密碼
                .password("$2a$10$ezW1uns4ZV63FgCLiFHJqOI6oR6jaaPYn33jNrxnkHZ.ayAFmfzLS").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 需要Spring Security保護的端點
        String[] endPoints = {"auditevents", "beans", "conditions", "configprops", "env", "flyway",
                "httptrace", "loggers", "liquibase", "metrics", "mappings", "scheduledtasks",
                "sessions", "shutdown", "threaddump"};

        // 定義需要驗證的端點
        // http.requestMatcher(EndpointRequest.to(endPoints))
        http.authorizeRequests().antMatchers("/manage/**").hasRole("ADMIN")

                // 請求關閉頁面需要ROLE_ADMIN橘色
                .antMatchers("/close").hasRole("ADMIN")

                .and().formLogin()

				.and()
				
                // 啓動HTTP基礎驗證
                .httpBasic();
    }

    public static void main(String[] args) {
        SpringApplication.run(Chapter16Application.class, args);
    }
}

http.
    requestMatcher(EndpointRequest.to(endPoints)).authorizeRequests().anyRequest().hasRole("ADMIN").
				and()
				.antMatchers("/close").authorizeRequests().anyRequest().hasRole("ADMIN");


.authorizeRequests().anyRequest() //簽名登錄後

shutdown端點

management.endpoint.shutdown.enabled=true
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <!-- 加載Query文件-->
    <script src="https://code.jquery.com/jquery-3.2.0.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#submit").click(function () {
                // 請求shutdown端點
                $.post({
                    url: "./actuator/shutdown",
                    // 成功後的方法
                    success: function (result) {
                        // 檢測請求結果
                        if (result != null || result.message != null) {
                            // 打印消息
                            alert(result.message);
                            return;
                        }
                        alert("關閉Spring Boot應用失敗");
                    }
                });
            });
        });
    </script>
    <title>測試關閉請求</title>
</head>
<body>
<input id="submit" type="button" value="關閉應用"/>
</body>
</html>


@RestController
public class CloseController {
	@GetMapping("/close")
	public ModelAndView close(ModelAndView mv) {
		// 定義視圖名稱爲close,讓其跳轉到對應的JSP中去
		mv.setViewName("close");
		return mv;
	}
}

配置端點

management.server.port=8080

# 暴露所有端點
management.endpoints.web.exposure.include=*

# management.endpoints 是公共的

# 默認情況下所有端點都不啓用,此時你需要按需啓用端點
.enabled-by-default=false

# 啓用端點 info
.info.enabled=true

# 啓用端點 beans
.beans.enabled=true

# 啓用config端點
.configprops.enabled=true

# 啓動env
.env.enabled=true

# 啓用health
.health.enabled=true

# 啓用mappings
.mappings.enabled=true

# 啓用shutdown
.shutdown.enabled=true

# Actuator端點前綴
.web.base-path=/manage

# 將原來的 mapping端點 的請求路徑 修改爲 urlMapping
.web.path-mapping.mappings=request_mappings
  • http://localhost:8000/manage/health

    {
        "status":"UP",
        "details":{
            "www":{
                "status":"UP",
                "details":{
                    "message":"當前服務器可以訪問萬維網。"
                }
            },
            "diskSpace":{
                "status":"UP",
                "details":{
                    "total":302643146752,
                    "free":201992957952,
                    "threshold":10485760
                }
            },
            "db":{
                "status":"UP",
                "details":{
                    "database":"MySQL",
                    "hello":1
                }
            }
        }
    }
    

自定義端點

// 讓Spring掃描類
@Component
// 定義端點
@Endpoint(
		// 端點id
		id = "dbcheck",
		// 是否默認的情況下是否啓用端點
		enableByDefault = true)
public class DataBaseConnectionEndpoint {
	
	private static final String DRIVER = "com.mysql.jdbc.Driver";
	
	@Value("${spring.datasource.url}")
	private String url = null;
	
	@Value("${spring.datasource.username}")
	private String username = null;

	@Value("${spring.datasource.password}")
	private String password = null;

	// 一個端點只能存在一個@ReadOperation標註的方法
	// 它代表的是HTTP的GET請求
	@ReadOperation
	public Map<String, Object> test() {
		Connection conn = null;
		Map<String, Object> msgMap = new HashMap<>();
		try {
			Class.forName(DRIVER);
			conn = DriverManager.getConnection(url, username, password);
			msgMap.put("success", true);
			msgMap.put("message", "測試數據庫連接成功");
		} catch (Exception ex) {
			msgMap.put("success", false);
			msgMap.put("message", ex.getMessage());
		} finally {
			if (conn != null) {
				try {
					conn.close(); // 關閉數據庫連接
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return msgMap;
	}

}
management.endpoint.dbcheck.enabled=true
{"success":true,"message":"測試數據庫連接成功"}

自定義萬維網健康指標

http://localhost:8080/manage/health 上面已經訪問

// 監測服務器是能能夠訪問萬維網
@Component
public class WwwHealthIndicator extends AbstractHealthIndicator {
	// 通過監測百度服務器,看能否訪問互聯網
	private final static String BAIDU_HOST = "www.baidu.com";
	// 超時時間
	private final static int TIME_OUT = 3000;

	@Override
	protected void doHealthCheck(Builder builder) throws Exception {
		boolean status = ping();
		if (status) {
			// 健康指標爲可用狀態,並添加一個消息項
			builder.withDetail("message", "當前服務器可以訪問萬維網。").up();
		} else {
			// 健康指標爲不再提供服務,並添加一個消息項
			builder.withDetail("message", "當前無法訪問萬維網").outOfService();
		}
	}

	// 監測百度服務器能夠訪問,用以判斷能否訪問萬維網
	private boolean ping() throws Exception {
		try {
			// 當返回值是true時,說明host是可用的,false則不可。
			return InetAddress.getByName(BAIDU_HOST).isReachable(TIME_OUT);
		} catch (Exception ex) {
			return false;
		}
	}

}

JMX 監控

jconsole.exe

選擇:org.springframework.boot——endpoint——health——點擊 health

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章