- 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