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 />
</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 packagejava -jar spring-0.0.1-snapshot.warjava -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,RedisMock测试
@RunWith(SpringRunner
.class)
@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
);
}
@Autowired
private TestRestTemplate restTemplate
= null
;
@Test
public void testGetUser() {
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);
BDDMockito
.given(this.productService
.getProduct(1L
))
.willReturn(mockProduct
);
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 端点描述
healthhttptrace 最新追踪信息(默认一百条)infomappings 所有映射路径scheduledtasks 显示定时任务shutdown
http 监控
http://localhost:8080/actuator/healthhttp://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
)
.withUser("admin")
.password("$2a$10$5OpFvQlTIbM9Bx2pfbKVzurdQXL9zndm1SrAjEkPyIuCcZ7CqR6je").roles("USER", "ADMIN")
.and()
.withUser("myuser")
.password("$2a$10$ezW1uns4ZV63FgCLiFHJqOI6oR6jaaPYn33jNrxnkHZ.ayAFmfzLS").roles("USER");
}
@Override
protected void configure(HttpSecurity http
) throws Exception
{
String
[] endPoints
= {"auditevents", "beans", "conditions", "configprops", "env", "flyway",
"httptrace", "loggers", "liquibase", "metrics", "mappings", "scheduledtasks",
"sessions", "shutdown", "threaddump"};
http
.authorizeRequests().antMatchers("/manage/**").hasRole("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
}
}
}
}
自定义端点
@Component
@Endpoint(
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
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 {
return InetAddress
.getByName(BAIDU_HOST
).isReachable(TIME_OUT
);
} catch (Exception ex
) {
return false;
}
}
}
JMX 监控
jconsole.exe
选择:org.springframework.boot——endpoint——health——点击 health