引言
在java web开发领域,springmvc作为一款主流的web框架,凭借其强大的功能和便捷的开发体验深受开发者喜爱。然而,在实际使用过程中,开发者常常会遇到各种各样的“坑”。本文将针对springmvc开发中常见的十大问题,结合实际案例和代码,深入剖析问题产生的原因,并提供详细的解决方案,帮助大家在开发过程中少走弯路。
一、自定义异常总看不懂?是设计逻辑出了问题吗?
在springmvc项目中,当业务逻辑变得复杂时,使用自定义异常可以更清晰地处理不同类型的错误情况。但有时开发者会发现自定义异常难以理解,这往往是因为异常设计逻辑不够清晰。
问题场景
假设我们正在开发一个电商系统,在用户下单时需要检查库存是否充足。当库存不足时,希望抛出一个自定义的insufficientstockexception
异常。但在实际调试过程中,发现异常信息混乱,难以定位问题根源。
原因分析
自定义异常设计不规范,没有合理继承已有的异常体系,或者异常信息没有包含足够的上下文信息,导致在捕获和处理异常时无法准确判断异常情况。
解决方案
- 定义自定义异常类,合理继承
runtimeexception
或exception
。例如:
// 继承runtimeexception,定义库存不足异常 public class insufficientstockexception extends runtimeexception { public insufficientstockexception(string message) { super(message); } }
- 在业务逻辑中使用自定义异常。以库存检查为例:
@service public class orderservice { private int stock = 10; // 模拟库存数量 public void placeorder(int quantity) { if (quantity > stock) { // 库存不足时抛出自定义异常 throw new insufficientstockexception("库存不足,无法下单"); } // 正常下单逻辑 } }
- 使用全局异常处理器统一处理异常,让异常信息更清晰易读。
@restcontrolleradvice public class globalexceptionhandler { @exceptionhandler(insufficientstockexception.class) public string handleinsufficientstockexception(insufficientstockexception e) { return "错误信息:" + e.getmessage(); } }
二、自定义异常不生效?为何还在报500错误?
开发者定义好自定义异常并配置了异常处理器后,有时会发现自定义异常并没有按照预期处理,页面仍然显示500错误。
问题场景
在上述电商系统中,配置好insufficientstockexception
及其处理器后,下单时库存不足依然显示500错误页面。
原因分析
- 异常处理器配置错误,没有被spring容器正确扫描到。
- 异常没有被正确抛出,在抛出异常之前可能被其他代码捕获处理。
- 全局异常处理器的优先级问题,其他优先级更高的异常处理机制先拦截了异常。
解决方案
- 确保异常处理器所在的类被
@restcontrolleradvice
或@controlleradvice
注解标注,并且所在的包被spring容器扫描。例如,在spring boot项目的启动类上添加@componentscan
注解,扫描包含异常处理器的包:
@springbootapplication @componentscan(basepackages = {"com.example.demo"}) public class demoapplication { public static void main(string[] args) { springapplication.run(demoapplication.class, args); } }
- 检查业务代码中异常抛出的逻辑,确保异常能够顺利抛出到全局异常处理器。
- 如果存在多个异常处理器,调整其优先级。可以通过实现
ordered
接口,重写getorder
方法来设置优先级,数值越小优先级越高:
@restcontrolleradvice public class globalexceptionhandler implements ordered { @exceptionhandler(insufficientstockexception.class) public string handleinsufficientstockexception(insufficientstockexception e) { return "错误信息:" + e.getmessage(); } @override public int getorder() { return 1; // 设置优先级 } }
三、时间格式转换失败?post请求的“陷阱”注意到了吗?
在处理包含日期时间类型参数的post请求时,经常会遇到时间格式转换失败的问题。
问题场景
前端通过post请求发送一个包含日期时间字段的数据到后端,后端使用@requestbody
接收数据并绑定到实体类中,但在转换过程中出现failed to convert property value of type 'java.lang.string' to required type 'java.util.date'
错误。
原因分析
- 前端发送的日期时间格式与后端期望的格式不一致。
- springmvc默认的日期时间格式转换配置不符合需求。
- 在post请求中,
@requestbody
解析数据时,对于日期时间类型的转换规则与get请求不同,需要额外配置。
解决方案
- 在实体类的日期时间字段上使用
@datetimeformat
注解指定日期时间格式。例如:
public class order { private long id; // 指定日期时间格式为"yyyy-mm-dd hh:mm:ss" @datetimeformat(pattern = "yyyy-mm-dd hh:mm:ss") private date ordertime; // 省略getter和setter方法 }
- 配置springmvc的日期时间格式化。在spring boot项目中,可以在
application.properties
文件中添加以下配置:
spring.jackson.date-format=yyyy-mm-dd hh:mm:ss spring.jackson.time-zone=gmt+8
- 如果上述方法无效,可以自定义一个
converter
来处理日期时间格式转换。例如:
@component public class customdateconverter implements converter<string, date> { private static final simpledateformat sdf = new simpledateformat("yyyy-mm-dd hh:mm:ss"); @override public date convert(string source) { try { return sdf.parse(source); } catch (parseexception e) { throw new illegalargumentexception("日期格式转换失败", e); } } }
然后在配置类中注册这个转换器:
@configuration public class webconfig implements webmvcconfigurer { @autowired private customdateconverter customdateconverter; @override public void addformatters(formatterregistry registry) { registry.addconverter(customdateconverter); } }
四、调试断点失效?是不是被多个filter“拦截”了?
在调试springmvc项目时,有时会发现设置的断点无法进入,导致调试工作无法正常进行。
问题场景
在控制器方法中设置了断点,启动调试模式后,请求到达该方法时断点没有生效,直接跳过执行后续代码。
原因分析
- 项目中存在多个filter,请求在到达控制器之前被其他filter拦截处理,导致无法进入断点所在的控制器方法。
- filter的配置顺序不合理,某些filter在处理请求时消耗了请求资源,使得后续请求无法正常处理。
- 断点设置的位置存在问题,例如在静态方法或没有被spring容器管理的类中设置断点。
解决方案
- 检查项目中的filter配置,确保没有不必要的filter拦截请求。可以通过在filter的
dofilter
方法中添加日志输出,查看请求是否经过该filter:
@component public class customfilter implements filter { @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { system.out.println("请求进入customfilter"); filterchain.dofilter(servletrequest, servletresponse); system.out.println("请求离开customfilter"); } @override public void destroy() { } }
- 调整filter的顺序,确保关键的filter在合适的位置执行。可以通过实现
ordered
接口,重写getorder
方法来设置filter的执行顺序:
@component public class customfilter implements filter, ordered { @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { filterchain.dofilter(servletrequest, servletresponse); } @override public void destroy() { } @override public int getorder() { return 1; // 设置filter执行顺序 } }
- 确保断点设置在被spring容器管理的类和方法中,并且方法不是静态方法。
五、request输入流读取后消失?响应体处理遗漏了?
在处理请求和响应时,可能会遇到request输入流读取一次后无法再次读取,或者响应体处理不当导致数据丢失的问题。
问题场景
在一个需要多次读取request输入流的场景中,第一次读取后,后续读取操作获取到的输入流为空。在处理响应时,发现响应数据没有按照预期输出。
原因分析
httpservletrequest
的输入流默认只能读取一次,读取后流会被关闭或重置,导致后续无法再次读取。- 在响应体处理过程中,没有正确设置响应头信息,或者没有将数据正确写入响应体。
- 存在其他代码在处理请求或响应过程中,意外关闭了输入流或响应流。
解决方案
- 自定义一个可以重复读取的
httpservletrequest
包装类。例如:
public class cachedbodyhttpservletrequest extends httpservletrequestwrapper { private final byte[] body; public cachedbodyhttpservletrequest(httpservletrequest request) throws ioexception { super(request); body = ioutils.tobytearray(request.getinputstream()); } @override public servletinputstream getinputstream() throws ioexception { final bytearrayinputstream bais = new bytearrayinputstream(body); return new servletinputstream() { @override public boolean isfinished() { return bais.available() == 0; } @override public boolean isready() { return true; } @override public void setreadlistener(readlistener readlistener) { } @override public int read() throws ioexception { return bais.read(); } }; } @override public bufferedreader getreader() throws ioexception { return new bufferedreader(new inputstreamreader(getinputstream())); } }
然后在filter中使用这个包装类来替换原始的httpservletrequest
:
@component public class requestbodycachefilter implements filter { @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { httpservletrequest httpservletrequest = (httpservletrequest) servletrequest; cachedbodyhttpservletrequest cachedbodyhttpservletrequest = new cachedbodyhttpservletrequest(httpservletrequest); filterchain.dofilter(cachedbodyhttpservletrequest, servletresponse); } @override public void destroy() { } }
- 正确处理响应体,设置响应头信息并将数据写入响应体。例如:
@restcontroller public class hellocontroller { @getmapping("/hello") public void hello(httpservletresponse response) throws ioexception { response.setcontenttype("application/json;charset=utf-8"); printwriter writer = response.getwriter(); writer.write("{\"message\":\"hello, world!\"}"); writer.flush(); writer.close(); } }
- 检查项目中所有涉及请求和响应处理的代码,确保没有意外关闭输入流或响应流的操作。
六、参数绑定总出错?是类型转换规则没吃透吗?
在springmvc中进行参数绑定时,经常会出现参数类型转换错误的问题,导致请求无法正确处理。
问题场景
前端传递一个字符串类型的参数,后端控制器方法期望接收一个整数类型的参数,但在绑定过程中出现failed to convert value of type 'java.lang.string' to required type 'java.lang.integer'
错误。
原因分析
- 前端传递的参数类型与后端控制器方法参数类型不匹配,并且springmvc无法自动进行正确的类型转换。
- 自定义的类型转换规则没有生效,或者类型转换规则定义错误。
- 参数名称不一致,导致springmvc无法正确匹配参数。
解决方案
- 确保前端传递的参数类型与后端控制器方法参数类型兼容,并且springmvc支持自动类型转换。如果不支持自动转换,可以使用
@requestparam
注解的required
属性设置为false
,避免参数不存在时抛出异常:
@getmapping("/user") public string getuser(@requestparam(value = "age", required = false) integer age) { if (age == null) { return "年龄参数未传递"; } return "用户年龄为:" + age; }
- 对于复杂的类型转换,可以自定义类型转换器。例如,将字符串转换为自定义的
user
对象:
public class user { private string name; private int age; // 省略getter和setter方法 } @component public class userconverter implements converter<string, user> { @override public user convert(string source) { string[] parts = source.split(","); user user = new user(); user.setname(parts[0]); user.setage(integer.parseint(parts[1])); return user; } }
然后在配置类中注册这个转换器:
@configuration public class webconfig implements webmvcconfigurer { @autowired private userconverter userconverter; @override public void addformatters(formatterregistry registry) { registry.addconverter(userconverter); } }
- 检查参数名称是否一致,确保前端传递的参数名与后端控制器方法中
@requestparam
或@requestbody
注解指定的参数名相同。
七、表单提交乱码?编码配置环节是否疏忽了?
在处理表单提交时,有时会出现提交的数据在后端显示为乱码的情况。
问题场景
用户在前端表单中输入中文内容并提交,后端接收到的中文内容显示为乱码。
原因分析
- 前端表单的
accept-charset
属性没有正确设置,或者设置的编码格式与后端不一致。 - springmvc的编码过滤器配置错误,没有对请求进行正确的编码处理。
- 服务器的默认编码设置与项目要求的编码不一致。
解决方案
- 在前端表单中设置
accept-charset
属性为utf-8
:
<form action="/submit" method="post" accept-charset="utf-8"> <input type="text" name="username" /> <input type="submit" value="提交" /> </form>
- 在spring boot项目中,配置
characterencodingfilter
来处理请求编码。在application.properties
文件中添加以下配置:
spring.http.encoding.charset=utf-8 spring.http.encoding.enabled=true spring.http.encoding.force=true
- 如果上述配置无效,可以自定义一个
filter
来处理编码问题:
@component public class encodingfilter implements filter { private static final string encoding = "utf-8"; @override public void init(filterconfig filterconfig) throws servletexception { } @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { servletrequest.setcharacterencoding(encoding); servletresponse.setcharacterencoding(encoding); filterchain.dofilter(servletrequest, servletresponse); } @override public void destroy() { } }
- 检查服务器的默认编码设置,确保与项目要求的编码一致。例如,在tomcat服务器中,可以在
conf/server.xml
文件中设置uriencoding="utf-8"
:
<connector port="8080" protocol="http/1.1" connectiontimeout="20000" redirectport="8443" uriencoding="utf-8"/>
八、拦截器拦截范围不对?匹配规则真的设置正确了?
在使用拦截器对请求进行拦截处理时,可能会出现拦截范围不符合预期的问题。
问题场景
配置了一个拦截器用于拦截所有的用户请求进行权限验证,但某些请求却没有被拦截到;或者不应该被拦截的请求反而被拦截了。
原因分析
- 拦截器的
addpathpatterns
和excludepathpatterns
方法设置的匹配规则不正确,没有准确覆盖需要拦截或排除的请求路径。 - 拦截器的注册顺序问题,导致部分请求在拦截器生效之前就已经被处理。
- 路径匹配规则中使用的通配符(如
*
、**
)理解错误,导致匹配范围不准确。
解决方案
1. 定义拦截器类,实现 handlerinterceptor 接口,在 prehandle 方法中进行拦截逻辑处理:
import org.springframework.web.servlet.handlerinterceptor; import org.springframework.web.servlet.modelandview; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; public class permissioninterceptor implements handlerinterceptor { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception { // 简单示例:判断请求中是否包含特定参数作为权限验证 string authtoken = request.getparameter("authtoken"); if (authtoken == null || !"valid_token".equals(authtoken)) { response.senderror(httpservletresponse.sc_unauthorized, "权限不足"); return false; } return true; } @override public void posthandle(httpservletrequest request, httpservletresponse response, object handler, modelandview modelandview) throws exception { } @override public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) throws exception { } }
2. 在配置类中注册拦截器,并设置拦截和排除路径:
import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.interceptorregistry; import org.springframework.web.servlet.config.annotation.webmvcconfigurer; @configuration public class webmvcconfig implements webmvcconfigurer { @override public void addinterceptors(interceptorregistry registry) { registry.addinterceptor(new permissioninterceptor()) .addpathpatterns("/user/**") // 拦截所有以 /user/ 开头的请求 .excludepathpatterns("/user/login", "/user/register"); // 排除登录和注册请求 } }
3. 若存在多个拦截器,通过实现 ordered
接口控制执行顺序:
import org.springframework.core.ordered; import org.springframework.web.servlet.handlerinterceptor; import org.springframework.web.servlet.modelandview; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; public class anotherinterceptor implements handlerinterceptor, ordered { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception { // 拦截逻辑 return true; } @override public void posthandle(httpservletrequest request, httpservletresponse response, object handler, modelandview modelandview) throws exception { } @override public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) throws exception { } @override public int getorder() { return 2; // 数值越小优先级越高,假设 permissioninterceptor 优先级为 1 } }
并在配置类中注册该拦截器,这样就能按顺序执行拦截逻辑。
九、视图解析失败?模板引擎配置出问题了吗?
在使用模板引擎(如 thymeleaf、freemarker)时,常常会遇到视图解析失败的情况,页面无法正确渲染。
问题场景
在 springmvc 项目中集成了 thymeleaf 模板引擎,控制器方法返回视图名称后,页面显示 whitelabel error page
,提示找不到对应的视图。
原因分析
- 模板引擎的依赖没有正确引入,或者版本不兼容。
- 模板引擎的配置不正确,如视图前缀、后缀设置错误,导致无法找到对应的模板文件。
- 模板文件的存放位置不符合配置要求,或者文件名拼写错误。
解决方案
以 thymeleaf 为例:
1. 确保在 pom.xml
文件中正确引入 thymeleaf 依赖:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-thymeleaf</artifactid> </dependency>
2. 在 application.properties
文件中配置 thymeleaf 的视图前缀和后缀:
spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=html spring.thymeleaf.encoding=utf-8 spring.thymeleaf.content-type=text/html
上述配置表示 thymeleaf 会在 classpath:/templates/
目录下寻找模板文件,并且模板文件的后缀为 .html
。
3. 确保模板文件存放在正确的目录下,并且文件名与控制器返回的视图名称一致。例如,控制器方法:
import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.getmapping; @controller public class indexcontroller { @getmapping("/") public string index() { return "index"; // 返回视图名称为 index,对应 templates 目录下的 index.html 文件 } }
同时,检查模板文件中是否存在语法错误,如标签闭合不正确、表达式错误等,这些也可能导致视图解析失败。
十、跨域请求被拒?cors 配置是否完整?
在前后端分离项目中,经常会遇到跨域请求被拒绝的问题,影响前后端数据交互。
问题场景
前端发起请求到后端接口,浏览器控制台提示 access to xmlhttprequest at 'xxx' from origin 'xxx' has been blocked by cors policy
错误,请求无法成功发送。
原因分析
- 后端没有配置 cors(cross-origin resource sharing,跨域资源共享)相关规则,浏览器出于安全策略限制了跨域请求。
- cors 配置不完整,如只允许了部分请求方法、没有设置允许携带凭证等,导致请求不符合跨域规则。
解决方案
方式一:使用 @crossorigin
注解
在控制器类或方法上添加 @crossorigin
注解,简单快速地解决跨域问题。例如:
import org.springframework.web.bind.annotation.crossorigin; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; @restcontroller @crossorigin(origins = "http://localhost:3000", allowedheaders = "*", methods = {java.net.httpurlconnection.http_get, java.net.httpurlconnection.http_post}) public class apicontroller { @getmapping("/data") public string getdata() { return "hello, cross-origin data"; } }
上述代码中,@crossorigin
注解允许来自 http://localhost:3000
的请求,允许所有请求头,支持 get 和 post 请求方法。
方式二:全局 cors 配置
通过配置类实现 webmvcconfigurer
接口,重写 addcorsmappings
方法进行全局 cors 配置:
import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.corsregistry; import org.springframework.web.servlet.config.annotation.webmvcconfigurer; @configuration public class corsconfig implements webmvcconfigurer { @override public void addcorsmappings(corsregistry registry) { registry.addmapping("/**") .allowedorigins("http://localhost:3000") .allowedmethods("get", "post", "put", "delete") .allowedheaders("*") .allowcredentials(true); } }
这里配置了对所有请求路径(/**
)的跨域支持,允许来自 http://localhost:3000
的请求,支持 get、post、put、delete 等请求方法,允许所有请求头,并且允许携带凭证(如 cookie)。
通过以上对 springmvc 开发中十大常见问题的详细解析和解决方案介绍,希望能帮助你在实际开发中顺利避开这些“坑”。
以上就是springmvc开发中十大常见问题深度解析与解决方案的详细内容,更多关于springmvc开发常见问题的资料请关注代码网其它相关文章!
发表评论