Bug复现
今天在写SpringBoot应用时,写一个后端生成图形验证码的业务时,需要用到session保存后端生成的图形验证码的Base64编码,然后前端通过session来显示图形验证码。
代码里用到了servlet里的HttpSession类,于是idea自动导入了javax.servlet包里的HttpSession。
业务代码如下:
package cn.edu.wit.controller;
import cn.edu.wit.entity.Result;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 注意这里导入的是javax.servlet包
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import javax.imageio.ImageIO;
@Slf4j
@RestController
public class CaptchaController {
private DefaultKaptcha captchaProducer;
public CaptchaController() {
// 配置Kaptcha
captchaProducer = new DefaultKaptcha();
captchaProducer.setConfig(new Config(new java.util.Properties()));
}
@GetMapping("/captcha")
public Result getCaptcha(HttpSession session) throws Exception {
// 生成验证码文本
String captchaText = captchaProducer.createText();
log.info("验证码: " + captchaText);
// 将验证码文本存储在Session中
session.setAttribute("captcha", captchaText);
// 生成验证码图片
BufferedImage image = captchaProducer.createImage(captchaText);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
// 转换为Base64
String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray());
// 返回给前端Base64编码的图形验证码
return Result.success("data:image/jpeg;base64," + base64Image);
}
}
Postman 测试如下:
GET http://localhost:8080/captcha
启动SpringBoot应用后,测试Postman接口,idea控制台报错:
java.lang.IllegalStateException: No primary or single unique constructor found for interface javax.servlet.http.HttpSession
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267)
at org.springframework.validation.DataBinder.createObject(DataBinder.java:924)
at org.springframework.validation.DataBinder.construct(DataBinder.java:903)
at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:116)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:156)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:148)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:225)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:917)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:829)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:842)
Bug原因
从stackoverflow社区找到了错误原因和解决方案
错误原因原文如下:
Spring Boot 3 (and Spring Framework 6) require a baseline of Jakarte EE 10. You cannot use it with Java EE or Jakarte EE versions below that.
You have to remove the explicit dependency on
jakarta.servlet-api
from yourpom.xml
. Java Servlet 4 is below the baseline and in particular still uses the package names starting withjavax.servlet
.中文机翻:
Spring Boot 3(和 Spring Framework 6)需要 Jakarte EE 10 的基线。您不能将其与 Java EE 或低于该版本的 Jakarte EE 版本一起使用。
您必须
jakarta.servlet-api
从您的pom.xml
. Java Servlet 4 低于基线,特别是仍然使用以javax.servlet
.
也就是说:SpringBoot3已经不支持javax.servlet包里的HttpSession了,如果需要使用该类的话,需要导入jakarta.servlet包里的HttpSession。
Bug修复
原作者给出的解决方案如下:
If you remove the explicit dependency, Spring will pull in transitively the correct one. You then need to replace all imports starting with
javax.servlet
withjavax
replaced byjakarta
, e.g.import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse;
bug修复方法很简单,就是将所有的javax.servlet包替换成jakarta.servlet包。
例如我的代码里就应该这样修改:
// import javax.servlet.http.HttpSession;
// 将javax替换成jakarta
import jakarta.servlet.http.HttpSession;
修改后,重新运行SpringBoot应用即可正常生成图形验证码,并保存到session里返回给前端。
参考资料
Fix “No primary or single unique constructor found for interface javax.servlet.http.HttpServletResponse” in Spring Boot application
版权声明:本文为博主作者:DarkComxEating原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/DarkComxEating/article/details/136200635