return list;
}
}
访问 /actuator/user 可以看到返回的用户信息如下:
[
{
“userName”: “zhangsan”,
“userId”: 1001
}
]
统一异常处理
对于接口的定义,我们通常会有一个固定的格式
{
“status”: true,
“code”: 200,
“message”: null,
“data”: [
{
“id”: “101”,
“name”: “jack”
},
{
“id”: “102”,
“name”: “jason”
}
]
}
如果调用方在请求我们的 API 时把接口地址写错了,就会得到一个 404 错误:
{
“timestamp”: 1492063521109,
“status”: 404,
“error”: “Not Found”,
“message”: “No message available”,
“path”: “/rest11/auth”
}
后端服务会告诉我们哪个地址没找到,其实也挺友好。但是因为我们上面自定义的数据格式跟下面的不一致,所以当用户拿到这个返回的时候是无法识别的,其中最明显的是 status 字段。
我们自定义的是 boolean 类型,用来表示请求是否成功,这里返回的就是 Http 的状态码,所以我们需要在发生这种系统错误时也能返回我们自定义的那种格式,那就要定义一个异常处理类(代码如下所示),通过这个类既可以返回统一的格式,也可以统一记录异常日志。
@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
}
最后贴上 ResponseData 的定义,代码如下。
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
最后讲一下线程池配置的拒绝策略。当我们的线程数量高于线程池的处理速度时,任务会被缓存到本地的队列中。队列也是有大小的,如果超过了这个大小,就需要有拒绝的策略,不然就会出现内存溢出。目前支持两种拒绝策略:
-
AbortPolicy:直接抛出 java.util.concurrent.RejectedExecutionException 异常。
-
CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,这样可以有效降低向线程池内添加任务的速度。
建议大家用 CallerRunsPolicy 策略,因为当队列中的任务满了之后,如果直接抛异常,那么这个任务就会被丢弃。
随机端口
在实际的开发过程中,每个项目的端口都是定好的,通过 server.port 可以指定端口。
当一个服务想要启动多个实例时,就需要改变端口,特别是在我们后面进行 Spring Cloud习的时候,服务都会注册到注册中心里去,为了能够让服务随时都可以扩容,在服务启动的时候能随机生成一个可以使用的端口是最好不过的。
在 Spring Boot 中,可以通过 ${random} 来生成随机数字,我们可以这样使用:
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);
}
}
编译打包
传统的 Web 项目在部署的时候,是编译出一个 war 包放到 Tomcat 的 webapps 目录下。而在 Spring Boot 构建的 Web 项目中则打破了这一传统部署的方式,它采用更加简单的内置容器方式来部署应用程序,只需要将应用编译打包成一个 jar 包,直接可以通过 java–jar 命令启动应用。
在项目的 pom.xml 中增加打包的 Maven 插件,代码如下所示。
org.springframework.boot
spring-boot-maven-plugin
true
net.biancheng.spring_boot_example.App
org.apache.maven.plugins
maven-compiler-plugin
1.81.8
mainClass 配置的是我们的启动入口类,配置完成后可以通过 Maven 的 mvn clean package 命令进行编译打包操作。编译完成后在 target 目录下会生成对应的 jar 包,部署的时候直接调用 java–jar xx.jar 即可启动应用。
相关springboot实战项目推荐
基于java ssm springboot+VUE疫情防疫系统系统前后端分离设计和实现
基于java springboot+mybatis电影售票网站管理系统前台+后台设计和实现
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
动应用。
相关springboot实战项目推荐
基于java ssm springboot+VUE疫情防疫系统系统前后端分离设计和实现
基于java springboot+mybatis电影售票网站管理系统前台+后台设计和实现
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-KkOjpMgx-1710847486223)]
[外链图片转存中…(img-XHE7SAIB-1710847486223)]
[外链图片转存中…(img-eNhDviQ0-1710847486224)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-qTkkVs7u-1710847486225)]