<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号
广州松河信息科技有限公司 版权所有