<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }启动类使用了 @SpringBootApplication 注解,这个注解表示该类是一个 Spring Boot 应用。直接运行 App 类即可启动,启动成功后在控制台输出信息,默认端口是 8080,如图 2 所示。
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }@RestController 是 @Controller 和 @ResponseBody 的组合注解,可以直接返回 Json 格式数据。
@RestController public class HelloController { // 注入对象 @Autowired private Environment env; @GetMapping("/hello") public String hello() { // 读取配置 String port = env.getProperty("server.port"); return port; } }
@RestController public class HelloController { // 注入配置 @Value("${server.port}") private String port; @GetMapping("/hello") public String hello() { return port; } }
@ConfigurationProperties(prefix = "net.biancheng") @Component public class MyConfig { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }读取配置的方法代码如下所示。
@RestController public class HelloController { @Autowired private MyConfig myConfig; @GetMapping("/hello") public String hello() { return myConfig.getName(); } }定义配置 application.properties 的方法如下:
net.biancheng.name=zhangsan
application.properties | 通用配置,不区分环境 |
application-dev.properties | 开发环境 |
application-test.properties | 测试环境 |
application-prod.properties | 生产环境 |
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
{
"status": "UP"
}
Http方法 | 路径 | 描述 | Http默认暴露 |
---|---|---|---|
GET | /actuator/conflgprops | 查看配置属性,包含默认配置 | false |
GET | /actuator/beans | 查看bean及其关系列表 | false |
GET | /actuator/heapdump | 打印线程栈 | false |
GET | /actuator/env | 查看所有环境变量 | false |
GET | /actuator/env/ {name} | 查看具体变量值 | true |
GET | /actuator/health | 查看应用健康指标 | true |
GET | /actuator/info | 查看应用信息 | false |
GET | /actuator/mappings | 查看所有 URL 映射 | false |
GET | /actuator/metrics | 查看应用基本指标 | false |
GET | /actuator/metrics/{name} | 查看具体指标 | false |
POST | /actuator/shutdown | 关闭应用 | false |
GET | /actuator/httptrace | 查看基本追踪信息 | false |
GET | /actuator/loggers | 显示应用程序中 loggers 配置 | false |
GET | /actuator/scheduledtasks | 显示定时任务 | false |
management.endpoint.health.show-details=ALWAYS
再次访问 /actuator/health,就可以得到健康状态的详细信息:
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 491270434816,
"free": 383870214144,
"threshold": 10485760
}
}
management.endpoints.web.exposure.include=configprops,beans
如果想全部端点都暴露的话直接配置成下面的方式:management.endpoints.web.exposure.include=*
关于这些监控的信息不再赘述,大家可以自行了解。后面我们会介绍如何使用 Spring Boot Admin 在页面上更加直观地展示这些信息,目前都是 Json 格式的数据,不方便查看。@Component public class UserHealthIndicator extends AbstractHealthIndicator { @Override protected void doHealthCheck(Builder builder) throws Exception { builder.up().withDetail("status", true); // builder.down().withDetail("status", false); } }通过 up 方法指定应用的状态为健康,down 方法指定应用的状态为不健康。withDetail 方法用于添加一些详细信息。访问 /actuator/health,可以得到我们自定义的健康状态的详细信息:
{
"status": "UP",
"details": {
"user": {
"status": "UP",
"details": {
"status": true
}
},
"diskSpace": {
"status": "UP",
"details": {
"total":
249795969024,
"free": 7575375872,
"threshold": 10485760
}
}
}
}
@Component @Endpoint(id = "user") public class UserEndpoint { @ReadOperation public List<Map<String, Object>> health() { List<Map<String, Object>> list = new ArrayList<>(); Map<String, Object> map = new HashMap<>(); map.put("userId", 1001); map.put("userName", "zhangsan"); list.add(map); return list; } }访问 /actuator/user 可以看到返回的用户信息如下:
[
{
"userName": "zhangsan",
"userId": 1001
}
]
{
"status": true,
"code": 200,
"message": null,
"data": [
{
"id": "101",
"name": "jack"
},
{
"id": "102",
"name": "jason"
}
]
}
{
"timestamp": 1492063521109,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/rest11/auth"
}
@ControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(value = Exception.class) @ResponseBody public ResponseData defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { logger.error("", e); ResponseData r = new ResponseData(); r.setMessage(e.getMessage()); if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) { r.setCode(404); } else { r.setCode(500); } r.setData(null); r.setStatus(false); return r; } }ResponseData 是我们返回格式的实体类,其发生错误时也会被捕获到,然后封装好返回格式并返回给调用方。最后关键的一步是,在 Spring Boot 的配置文件中加上如下代码所示配置。
# 出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为我们工程中的资源文件建立映射
spring.resources.add-mappings=false
{
"status": false, "code": 404,
"message": "No handler found for GET /rest11/auth", "data": null
}
public class ResponseData { private Boolean status = true; private int code = 200; private String message; private Object data; // get set ... }
ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(() -> { try { // 业务逻辑 } catch (Exception e) { e.printStackTrace(); } finally { } });这种方式尽管使用了 Java 的 Lambda,但看起来没那么优雅。在 Spring 中有一种更简单的方式来执行异步操作,只需要一个 @Async 注解即可,代码如下所示。
@Async public void saveLog() { System.err.println(Thread.currentThread().getName()); }我们可以直接在 Controller 中调用这个业务方法,它就是异步执行的,会在默认的线程池中去执行。需要注意的是,一定要在外部的类中去调用这个方法,如果在本类调用则不起作用,比如 this.saveLog()。最后在启动类上开启异步任务的执行,添加 @EnableAsync 即可。
@Configuration @ConfigurationProperties(prefix = "spring.task.pool") public class TaskThreadPoolConfig { // 核心线程数 private int corePoolSize = 5; // 最大线程数 private int maxPoolSize = 50; // 线程池维护线程所允许的空闲时间 private int keepAliveSeconds = 60; // 队列长度 private int queueCapacity = 10000; // 线程名称前缀 private String threadNamePrefix = "FSH-AsyncTask-"; // get set ... }然后我们重新定义线程池的配置,代码如下所示。
@Configuration public class AsyncTaskExecutePool implements AsyncConfigurer { private Logger logger = LoggerFactory.getLogger(AsyncTaskExecutePool.class); @Autowired private TaskThreadPoolConfig config; @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(config.getCorePoolSize()); executor.setMaxPoolSize(config.getMaxPoolSize()); executor.setQueueCapacity(config.getQueueCapacity()); executor.setKeepAliveSeconds(config.getKeepAliveSeconds()); executor.setThreadNamePrefix(config.getThreadNamePrefix()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initia lize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { // 异步任务中异常处理 return new AsyncUncaughtExceptionHandler() { @Override public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) { logger.error("==========================" + arg0.getMessage() + "=======================", arg0); logger.error("exception method:" + arg1.getName()); } }; } }配置完之后我们的异步任务执行的线程池就是我们自定义的了,我们可以在属性文件里面配置线程池的大小等信息,也可以使用默认的配置:
spring.task.pool.maxPoolSize=100
最后讲一下线程池配置的拒绝策略。当我们的线程数量高于线程池的处理速度时,任务会被缓存到本地的队列中。队列也是有大小的,如果超过了这个大小,就需要有拒绝的策略,不然就会出现内存溢出。目前支持两种拒绝策略:server.port=${random.int[2000,8000]}
通过 random.int 方法,指定随机数的访问,生成一个在 2000 到 8000 之间的数字,这样每次启动的端口就都不一样了。public class StartCommand { private Logger logger = LoggerFactory.getLogger(StartCommand.class); public StartCommand(String[] args) { Boolean isServerPort = false; String serverPort = ""; if (args != null) { for (String arg : args) { if (StringUtils.hasText(arg) && arg.startsWith("--server.port")) { isServerPort = true; serverPort = arg; break; } } } // 没有指定端口, 则随机生成一个可用的端口 if (!isServerPort) { int port = ServerPortUtils.getAvailablePort(); logger.info("current server.port=" + port); System.setProperty("server.port", String.valueOf(port)); } else { logger.info("current server.port=" + serverPort.split("=")[1]); System.setProperty("server.port", serverPort.split("=")[1]); } } }通过对启动参数进行遍历判断,如果有指定启动端口,后续就不自动生成了;如果没有指定,就通过 ServerPortUtils 获取一个可以使用的端口,然后设置到环境变量中。在 application.properties 中通过下面的方式获取端口:
server.port=${server.port}
关于获取可用端口的代码如下所示。public static int getAvailablePort() { int max = 65535; int min = 2000; Random random = new Random(); int port = random.nextInt(max)%(max-min+1) + min; boolean using = NetUtils.isLoclePortUsing(port); if (using) { return getAvailablePort(); } else { return port; } }获取可用端口的主要逻辑是指定一个范围,然后生成随机数字,最后通过 NetUtils 来检查端口是否可用。如果获取到可用的端口则直接返回,没有获取到可用的端口则执行回调逻辑,重新获取。检测端口是否可用主要是用 Socket 来判断这个端口是否可以被链接。
public class FshHouseServiceApplication { public static void main(String[] args) { // 启动参数设置, 比如自动生成端口 new StartCommand(args); SpringApplication.run(FshHouseServiceApplication.class, args); } }
<build> <plugins> <!-- 打包插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> <mainClass>net.biancheng.spring_boot_example.App</mainClass> </configuration> </plugin> <!-- 编译插件, 指定JDK版本 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>mainClass 配置的是我们的启动入口类,配置完成后可以通过 Maven 的 mvn clean package 命令进行编译打包操作。编译完成后在 target 目录下会生成对应的 jar 包,部署的时候直接调用 java–jar xx.jar 即可启动应用。
Copyright © 广州京杭网络科技有限公司 2005-2025 版权所有 粤ICP备16019765号
广州京杭网络科技有限公司 版权所有