Spring系列开发常见问题
Spring系列开发常见问题
tomcat 项目启动失败排查思路(idea)
tomcat项目启动失败排查思路
1)检查tomcat是否正常启动/关闭
确认tomcat是否正常启动或关闭,通过查看端口号监听等进行排查(这种情况一般出现在同一个tomcat部署了多个不同的项目,而端口号冲突导致tomcat启动或者关闭异常。tomcat常用的默认端口号http port:8080、关闭端口:8005、ajx连接器:8009)
如果在同一个tomcat中部署了多个不同的项目,先后启动关闭的时候会出现端口号被占用的情况,那么极有可能出现8005端口占用而导致tomcat无法正常关闭。排查tomcat启动关闭,通过监听端口(一般直接查看tomcat启动端口的情况进行查杀)
# windows下查看端口号监听
netstat -ano | findstr 8080
# 杀死指定进程的tomcat
taskkill /PID [指定进程号] /F
# mac 下查看端口号监听
ps -ef | grep tomcat
ps -ef | grep 8080
排查上述因素,然后重启tomcat
2)检查tomcat启动日志
此处tomcat的启动日志具体要看idea中的配置,默认是存储在idea的工作目录下的/tomcat/目录(便于隔离环境,避免在同一个tomcat下发布多个不同的项目的冲突导致启动异常),如果在tomcat配置中指定的发布在tomcat的webapps下则去对应tomcat安装目录下的webapps查看。
windows:idea工作目录默认是在用户/AppData/Local/JetBrains/IntelliJIdea2024.1/tomcat(具体看个人配置)
- 按照ID分类,每个项目在不同的服务器部署,互不影响
自定义指定tomcat的webapps(在idea中的tomcat配置下的Deployment选项卡,编辑相应的发布目录),选择tomcat安装目录下的webapps,这样项目就会发布早webapps下,如果多个项目发布则可能出现一些冲突影响启动失败的问题,或者是一些残存项目启动失败而导致整个tomcat启动失败(有一些不是自身项目原因,而是其他项目启动失败导致tomcat启动失败)
或者是通过File=》Project Structure=》Artifacts 编辑,指定Output directory目录(对应到tomcat安装目录下的webapps)
Web应用程序开发
1.解决Spring MVC中的HttpMediaTypeNotAcceptableException异常
在使用Springboot构建项目,基于SpringMVC开发web应用程序可能会遇到HttpMediaTypeNotAcceptableException异常:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
这个异常通常是在处理Restful API请求的时候出现,表示服务器无法找到适合客户端请求的可接受的表示形式(媒体类型)。这种情况一般有两种排查思路,一是服务器无法提供客户端所需的媒体类型;二是客户端请求中的Accept
字段不正确或不匹配服务器的响应类型。在排查的时候按照上述思路进行分析,一步步排查。
异常复现&排查过程
在自定义全局异常处理器的时候,返回一个自定义的实体时发现提示上述错误。全局异常处理器定义如下所示:
@RestControllerAdvice
public class GlobalException {
/**
* @ExceptionHandler:限定对何种异常进行处理
* @ResponseBody:处理返回的格式(SpringMVC会响应一个json格式信息)
*/
@ExceptionHandler(value = BusinessException.class)
public String handler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("发生异常的处理器:" + handler + "- 具体异常信息:" + ex.getMessage());
// 返回结果
return "{\n" +
" \"code\":\"-1\",\n" +
" \"msg\":\"服务器出现异常,请联系管理员处理...\",\n" +
" \"data\":null\n" +
"}";
}
// 异常处理器:针对参数校验设定相应的handler
/**
* 方法参数校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getBindingResult().getFieldError().getDefaultMessage());
}
/**
* ValidationException
*/
@ExceptionHandler(ValidationException.class)
public RspDTO handleValidationException(ValidationException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getCause().getMessage());
}
/**
* ConstraintViolationException
*/
@ExceptionHandler(ConstraintViolationException.class)
public RspDTO handleConstraintViolationException(ConstraintViolationException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getMessage());
}
// ---------- 其他异常处理 ---------
@ExceptionHandler(NoHandlerFoundException.class)
public RspDTO handlerNoFoundException(Exception e) {
return new RspDTO(404, "路径不存在,请检查路径是否正确");
}
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public RspDTO handlerHttpMediaTypeNotAcceptableException(Exception e) {
return new RspDTO(500, "客户端请求中的Accept字段不正确或不匹配服务器的响应类型");
}
@ExceptionHandler(Exception.class)
public ResponseEntity<RspDTO> handleException(Exception e) {
return new ResponseEntity<>(new RspDTO(500, "系统繁忙,请稍后再试"), HttpStatus.OK);
}
}
异常情况说明:当在controller层提供一个通过Validator校验的接口,访问时如果传入参数不满足校验条件则会抛出MethodArgumentNotValidException异常,此时会受到全局异常拦截器进行拦截处理,按照上述全局异常处理器的逻辑分析,正常情况下会抛出RspDTO响应实体(这点也符合全局异常处理器可支持的返回类型Model,这个Model可以是任意实体)
但是在测试接口的时候却发现,异常可以正常捕获,却在内部抛出HttpMediaTypeNotAcceptableException,且这个异常并没有再次被全局异常处理器拦截,而是直接在内部抛出,抛出提示org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
,基于抛出的异常提示跟踪解决思路,一开始初步考虑是客户端或者服务端没有定义适配的接收或者返回类型(这点在Restful接口交互中特别常见),基于这个方向进一步进行排查:
【1】确认接口请求的accept参数(apifox、浏览器)
确认accept参数,可以看到apifox、浏览器接收的类型都符合目前服务器支持的类型
【2】确认服务器端的适配
服务器无法提供所需的媒体类型:当客户端请求的媒体类型在服务器端不可用时,就会触发HttpMediaTypeNotAcceptableException
异常。这通常是由于服务器端没有配置适当的媒体类型转换器或缺少相应的依赖库导致的。为了解决这个问题,需要确保服务器端正确配置了适当的媒体类型转换器。Spring MVC通过ContentNegotiationConfigurer
类提供了配置媒体类型转换器的方式。
正常情况下为了适配前端的accept请求,会自定义WebConfig去自定义适配
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}
但是上述配置确认之后,返回类型还是渲染失败导致错误。
【3】确认渲染逻辑
在全局异常处理器中,如果返回类型是String,则会被ViewResolver接管(例如如果程序中引入了thymeleaf,则会根据指定逻辑渲染对应的模板页面)
此处返回类型设定为RspDTO,正常逻辑希望它被渲染为相应的JSON数据并响应(通过@RestControllerAdvice(@ControllerAdvice + @ResponseBody)来进行限制),但实际上却抛出了上述错误。
一开始思考是否是因为返回的类型指定错误,因此通过设定返回类型为ResponseEntity<RspDTO>
进行校验,发现还是存在上述问题。为了进一步定位问题(排查是否因全局异常处理器配置问题导致的错误),在Controller层定义接口进行校验,确认Controller是否可以正常渲染响应,做了如下调整:
- 全局异常处理器:新增handler专门对HttpMediaTypeNotAcceptableException进行拦截处理
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<String> handlerHttpMediaTypeNotAcceptableException(Exception e) {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("客户端请求中的Accept字段不正确或不匹配服务器的响应类型");
}
- Controller:接口测试
@GetMapping("/testExc")
public void testExc() throws HttpMediaTypeNotAcceptableException {
throw new HttpMediaTypeNotAcceptableException("参数异常....");
}
@GetMapping("/testUser")
public User testUser(){
return new User("noob");
}
@GetMapping("/testStr")
public ResponseEntity<String> test1(){
return ResponseEntity.ok("success");
}
@GetMapping("/testRspDTO")
public RspDTO testRspDTO(){
return new RspDTO(1,"响应成功");
}
依次访问测试接口:
http://localhost:8080/user/testExc
客户端请求中的Accept字段不正确或不匹配服务器的响应类型
http://localhost:8080/user/testUser
{"id":0,"username":"noob","password":null,"email":null,"phone":null,"createTime":null}
http://localhost:8080/user/testStr
success
http://localhost:8080/user/testRspDTO
客户端请求中的Accept字段不正确或不匹配服务器的响应类型
结合上述结果分析,响应都是符合预期的,但是从testRspDTO接口请求来看,直接返回RspDTO就触发了异常,说明Springboot在对RspDTO处理的时候出现异常,此时控制变量进行对比(因为正常情况下返回RspDTO和返回User都是返回对象,因此可以进一步确认这两个实体定义有什么不同)
RspDTO和User作为网络传输对象,需要通过序列化完成Java对象和二进制字节流的转化
最终定位问题出现在@Data注解,RspDTO并没有提供@Data注解,因此实体对象没有没有正常被处理(根本原因在于没有给RspDTO提供getter构造器,导致在渲染处理的时候没有办法将RspDTO正常转化),进而导致服务器将其归类为没有适配的响应类型,抛出相应的异常,最终解决方案是为RspDTO添加@Data注解,或者为其指定构造器
总结:@Data在某些层面上简化了开发,但某些情况下如果不注意使用,理所当然的忽略掉基本的内容,就会导致程序出现意想不到的问题,而问题的排查根据需要一步步定位,例如此处是先排查是否因为前后端配置问题导致的交互不匹配、是否是全局异常处理器配置问题、其次排查接口是否可以正常渲染自定义实体(转为JSON响应)、然后找到正常转化的实体,确认代码定义的差异,最终一步步定位到对应的实体定义的构造器问题(这个问题甚至没有从异常的堆栈信息中有明显的提示,只能一步步排查)
排查了上述问题,为RspDTO构建正常的@Data配置,再次访问测试,异常被正常拦截处理
Springboot导入Jackson依赖启动报错
springboot版本 2.7.6 ,导入如下版本jackson依赖,导致启动报错
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
跟踪最下面的错误提示,发现适合jackson相关的:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.filter.OrderedFormContentFilter]: Factory method 'formContentFilter' threw exception; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fasterxml.jackson.datatype.jsr310.JavaTimeModule]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/std/ToStringSerializerBase
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.24.jar:5.3.24]
... 56 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/std/ToStringSerializerBase
at com.fasterxml.jackson.datatype.jsr310.JavaTimeModule.<init>(JavaTimeModule.java:158) ~[jackson-datatype-jsr310-2.13.4.jar:2.13.4]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_412]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_412]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_412]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_412]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211) ~[spring-beans-5.3.24.jar:5.3.24]
... 71 common frames omitted
解决方案1:如项目中无引用,则取消掉Jackson依赖,重新刷新Maven再次启动确认
解决方案2:引入关联的依赖 jackson-datatype-jsr310
根据上述日志提示可以看到这个错误表明Spring框架在尝试自动配置Jackson数据类型的Java 8日期和时间模块(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
)时失败了。这通常是因为缺少相关的依赖或者依赖没有正确配置。因此需要关联引入Jackson的jackson-datatype-jsr310
模块的依赖,参考如下所示(jackson版本要一致)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.9</version>
</dependency>