深入浅出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

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