深入浅出boot2.0 16章 部署,测试 和 监控

    技术2025-11-26  22

    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 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) //所载入的类 是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 端点描述

    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) // 注册用户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

    Processed: 0.017, SQL: 9