- 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