摘要
在将a模块迁移到spring boot项目下、使用embeded tomcat启动项目后,在调用RESTfule接口时,模块中声明的一个SpringMVC拦截器"cn.xxx.thread.common.web.speedctrlforuser.SpeedctrlForUserInterceptor"中抛出了ClassCastException。但是使用外置Tomcat启动就没有这个问题。在逐行debug后发现是spring boot缺失一项配置导致了这个问题。
问题
在 TECHSTUDY-91 - THREAD模块接入服务注册/订阅服务 进行中 任务中,我为a模块定义了一个启动类(注解了@SpringBootApplication),并配置了对应的application.properties。由于目前只需要注册到eureka上,配置文件中只有如下两行配置:
applictaion.properties
spring.application.name=a
eureka.client.serviceUrl.defaultZone=http://10.255.33.207:8080/eureka,http://10.255.33.208:8080/eureka,http://10.255.33.209:8080/eureka
在其它配置(如maven依赖关系、xml配置文件引入等)都整理好之后,用eclipse将a模块发布到tomcat上(即打成war包后发布),调用auth模块接口(如http://localhost:8080/a/rest/users/admin),一切正常。
但是,在使用启动类将模块发布到内置tomcat上(相当于打成jar包后发布),再调用上述auth模块的接口,会出现以下异常:
17:52:31,864 ERROR [org.apache.juli.logging.DirectJDKLog.log] (http-nio-8080-exec-2) Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod] with root cause^|TraceId.-http-nio-8080-exec-2
java.lang.ClassCastException: org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod
at cn.xxx.thread.common.web.speedctrlforuser.SpeedctrlForUserInterceptor.preHandle(SpeedctrlForUserInterceptor.java:66) ~[classes/:?]
at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:133) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:962) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.proce***equest(FrameworkServlet.java:970)
分析
从上文的异常信息可知,问题出现在SpeedctrlForUserInterceptor的第66行。这里的代码是这样的:
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws TooManyRequestsException {
User user = SecurityUtils.getUserFromPrincipal(SecurityContextHolder
.getContext().getAuthentication());
if (user == null) {
return true;
}
HandlerMethod method = (HandlerMethod) handler; // 这里是第66行
// 省略后续代码
}
在第66行,代码中做了一个强制类型转换。根据异常信息,在这里得到的handler是一个ResourceHttpRequestHandler,而不是HandlerMethod。所以会报错。
这里的ResourceHttpRequestHandler和HandlerMethod分别是什么呢?我们可以简单的看一下二者的Javadoc。
org.springframework.web.servlet.resource.ResourceHttpRequestHandler
HttpRequestHandler that serves static resources in an optimized way according to the guidelines of Page Speed, YSlow, etc.
The "locations" property takes a list of Spring Resource locations from which static resources are allowed to be served by this handler. Resources could be served from a classpath location, e.g. "classpath:/META-INF/public-web-resources/", allowing convenient packaging and serving of resources such as .js, .css, and others in jar files.
This request handler may also be configured with a resourcesResolver and resourceTransformer chains to support arbitrary resolution and transformation of resources being served. By default a PathResourceResolver simply finds resources based on the configured "locations". An application can configure additional resolvers and transformers such as the VersionResourceResolver which can resolve and prepare URLs for resources with a version in the URL.
This handler also properly evaluates the Last-Modified header (if present) so that a 304 status code will be