spring 拦截器请求类型异常.servlet.resource.ResourceHttpRequestHandler cannot be cast to .method.HandlerMethod

12 篇文章 0 订阅
2 篇文章 0 订阅

目录

 

自定义SpringMVC拦截器中HandlerMethod类型转换问题调研

摘要

问题

分析

方案

方案一:修改springMVC拦截器配置

方案二:检查内置tomcat配置

二次分析:先搞清楚问题究竟在哪儿

第一个断点

第二个断点

context-path

方案三:指定context-path

补充

总结


自定义SpringMVC拦截器中HandlerMethod类型转换问题调研

摘要

在将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.processRequest(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 returned as appropriate, avoiding unnecessary overhead for resources that are already cached by the client.

HandlerMethod
org.springframework.web.method.HandlerMethod 
Encapsulates information about a handler method consisting of a method and a bean. Provides convenient access to method parameters, the method return value, method annotations, etc. 
The class may be created with a bean instance or with a bean name (e.g. lazy-init bean, prototype bean). Use createWithResolvedBean() to obtain a HandlerMethod instance with a bean instance resolved through the associated BeanFactory.

简单的说,ResourceHttpRequestHandler是用来处理静态资源的;而HandlerMethod则是springMVC中用@Controller声明的一个bean及对应的处理方法。以http://localhost:8080/a/rest/users/admin这个接口为例,它对应的HandlerMethod应该指向这个类的这个方法:

@Controller@RequestMapping("/rest/users")
public class UserRESTController extends AbstractController
{
    @PreAuthorize("hasRole('USER_DETAIL')")
    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    @ResponseBody
    public User getUserByID(@PathVariable String id) throws InvalidDataException {
        // 省略具体代码
    }
    // 省略其它方法
}

所以这个问题的核心是:为什么springMVC把一个非静态资源识别成了静态资源,并了调用静态资源处理器?

方案

这里尝试了好几种方案。实际上只有最后的方案是可行的。不过前面几种方案也记录了一下。

方案一:修改springMVC拦截器配置

那个接口怎么着也不是一个静态资源啊。所以我一开始认为是拦截器的配置有问题。于是我看了一下它的配置,发现确实与别的拦截器不一样:

<mvc:interceptors>
<!-- 一种配置是这样的:拦截所有请求,但过滤掉静态资源 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/js/**" />
        <mvc:exclude-mapping path="/html/**" />
        <mvc:exclude-mapping path="/resources/**" />
        <bean class="cn.xxx.thread.common.interceptor.LoginUserInterceptor" />
    </mvc:interceptor>
    <!-- 一种配置是这样的:只拦截REST请求。 -->
    <mvc:interceptor>
        <mvc:mapping path="/rest/**" />
        <bean class="cn.xxx.thread.common.web.speedcontrol.SpeedControlInterceptor" />
    </mvc:interceptor>
    <!-- 出问题的拦截器是这样的:拦截所有请求,并且不过滤静态资源 -->
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="cn.xxx.thread.common.web.speedctrlforuser.SpeedctrlForUserInterceptor" />
    </mvc:interceptor>
    <!-- 省略其它拦截器配置,与第一、第二种大同小异 -->
</mvc:interceptors>

于是我先后做了两次调整:把SpeedctrlForUserInterceptor拦截器的<mvc:mapping />配置改成<mvc:mapping path="/rest/**" />;把SpeedctrlForUserInterceptor拦截器的顺序调整为第一位。 
都没起作用。当然都不起作用。这段配置一直在线上正常运行;用war包发布到tomcat上也不报错。说明问题并不在这里。修改这段配置当然不会起作用。

方案二:检查内置tomcat配置

既然问题只在使用embeded tomcat发布时出现,那么多半是它的配置上的问题了。 
于是我又查了一下,发现tomcat有一个defaultServlet,用于处理一些静态资源。并且我在外置tomcat的web.xml中也确实发现了这个配置:

<!-- The default servlet for all web applications, that serves static -->
<!-- resources.  It processes all requests that are not mapped to other   -->
<!-- servlets with servlet mappings (defined either here or in your own   -->
<!-- web.xml file).  This servlet supports the following initialization   -->
<!-- parameters (default values are in square brackets):                  -->
<!-- 省略后面的注释 -->  
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

难道是内置tomcat没有显式开启这个servlet导致的?我尝试着在spring-servlet-common.xml中增加了一个配置:

<!-- 这是增加的配置 -->
<mvc:default-servlet-handler/>
<!-- 官方提供的注释如下:Element : default-servlet-handlerConfigures a handler for serving static resources by
forwarding to the Servlet container's default Servlet. Use of this handler allows using a "/" mapping with the
DispatcherServlet while still utilizing the Servlet container to serve static resources. This handler will forward all
requests to the default Servlet. Therefore it is important that it remains last in the order of all other URL
HandlerMappings. That will be the case if you use the "annotation-driven" element or alternatively if you are setting up
your customized HandlerMapping instance be sure to set its "order" property to a value lower than that of the
DefaultServletHttpRequestHandler, which is Integer.MAX_VALUE. -->

加上配置之后,还是不起作用。当然不起作用。从注释上看,它的作用是增加一个handler,在识别出静态资源之后将请求转发给容器提供的default servlet。然而我遇到的问题是,springMVC在识别静态资源上出现了误判。加这个配置当然不会起作用。
顺带一提,我后来debug时发现,内置tomcat同样会注册default servlet。在这一点上,内置、外置没有区别。

二次分析:先搞清楚问题究竟在哪儿

上面两个方案,其实都是建立在“推测问题原因”上的。换句话说就是“我猜我猜我猜猜”。初步分析时可以使用这种方法;但由于它对问题原因的分析很不到位,所以再怎么调整、修改也改不到点子上。 
所以在拿出方案三之前,我打算祭出最后的法宝,先把病因搞清楚再开方子拿药。 
这个法宝就是:开debug模式,逐行执行代码。而且在这个问题中,由于外置tomcat能够正常执行,因此,还可以用正常情况下的运行时数据来与出错情况做对比。

第一个断点

第一个断点打在哪儿?分析异常信息可以发现,异常抛出位置是DispatcherServlet.doDispatch(DispatcherServlet.java:962)。这个方法的代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 这里是第940行

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 这里是第962行
                return;
            }
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

第962行执行了mappedHandler.applyPreHandle(processedRequest, response),而其中的mappedHandler来自第940的mappedHandler = getHandler(processedRequest);。这个getHandler方法的代码如下:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request); // 这里是第1160行
        if (handler != null) {
            return handler
        }
    }
    return null;
}

可以很清楚地看出:这段代码就是SpringMVC决定使用哪个Handler来处理当前Request的地方。因此,我把第一个断点打在了第1160行(getHandler方法中HandlerExecutionChain handler = hm.getHandler(request);这一句上)。一来检查内置/外置tomcat下,SpringMVC生成的handlerMappings是否有不同;二来检查两种情况下,SpringMVC分别由哪个HandlerMapping来处理request并生成HandlerExecutionChain。 
执行结果的图我就不贴了。结论是这样的:两种tomcat下,handlerMappings中都有9个HandlerMapping的示例,并且两种情况下列表中的类、顺序都是一样的。但是,外置tomcat下,是下标为1的实例(RequestMappingHandlerMapping)处理了请求、并返回了一个HandlerMethod实例;而内置tomcat中,是下标为5的实例(SimpleUrlHandlerMapping)来处理请求,并返回了一个ResourceHttpRequestHandler实例!而正是这个ResourceHttpRequestHandler,在代码中强转HandlerMthod时抛出了异常。 
因此,我们可以将问题聚焦为:内置tomcat情况下,为什么下标为1的实例(RequestMappingHandlerMapping)没能正确处理这个请求?

第二个断点

但是,虽然我们可以确定问题出现在RequestMappingHandlerMapping这个类中,但通过分析代码可以发现,getHandler方法的流程并没有进入这个类中,而是由它的父类(AbstractHandlerMethodMapping/AbstractHandlerMapping)定义的方法处理了。

sequenceDiagram
    DispatcherServlet->>AbstractHandlerMapping: getHandler(request)
    AbstractHandlerMapping->> AbstractHandlerMethodMapping: getHandlerInternal(request)
    AbstractHandlerMethodMapping->>AbstractHandlerMethodMapping: lookupHandlerMehtod(lookupPath,request)
    AbstractHandlerMethodMapping->>AbstractHandlerMapping: return HandlerMethod
    AbstractHandlerMapping->>DispatcherServlet: return HandleExecutionChain

最关键的方法是AbstractHandlerMethodMapping.lookupHandlerMethod( String lookupPath, HttpServletRequest request),其代码如下:

/**
 * Look up the best-matching handler method for the current request.
 * If multiple matches are found, the best match is selected.
 * @param lookupPath mapping lookup path within the current servlet mapping
 * @param request the current request
 * @return the best-matching handler method, or {@code null} if no match
 * @see #handleMatch(Object, String, HttpServletRequest)
 * @see #handleNoMatch(Set, String, HttpServletRequest)
 */
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        Collections.sort(matches, comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                    lookupPath + "] : " + matches);
        }
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
            }
        }
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

SpringMVC用这个方法来将请求路径(入参lookupPath)匹配到已注册的handler上。于是我在这个方法的入口处加了个断点,在内置/外置tomcat下逐步执行后,发现了玄机:
外置tomcat下,directPathMatches不为空;而内置tomcat下,directPathMatches是一个EmptyList,这又进一步导致了matches是一个EmptyList,并使得最终的返回值是null。

可以不用打第三个断点了。细致一点就能发现:内置tomcat下,lookupPath的值是"/a/rest/users",而外置tomcat下则是"/rest/users"。而无论使用内置/外置tomcat,MappingRegistry中保存的urlPath,都是"/rest/xxxx"格式的。用toString()方法打印出来的话,基本是这样的:"/rest/dirtyUpload/clean=[{[/rest/dirtyUpload/clean],methods=[GET]}], /{path}=[{[/{path}],methods=[GET]}], /=[{[/],methods=[GET]}], /rest/server/time=[{[/rest/server/time]}], ……"(这些mapping是c模块下的;a模块下类似,只是具体路径不同)。

context-path

为什么使用外置tomcat启动时,工程名a不会被识别为URI呢?因为当我们使用eclipse将a发布到tomcat中时,eclipse会自动向tomcat的server.xml中写入一行配置:

<Context docBase="a" path="/a" reloadable="true" source="org.eclipse.jst.jee.server:a"/></Host>

其中的path属性,就指定了这个项目的context-path是/a。因而,在将URL(protocol://host:port/context-path/URI?queryString)解析为URI时,SpringMVC能够得到正确的结果。 
即使不手动处理server.xml(tomcat官方也并不推荐手动处理server.xml),用war包/文件夹方式发布web项目时,tomcat也会自动将路径名解析为context-path。
但是使用内置tomcat启动时,由于项目的application.properties中没有相关配置,因而context-path默认被指定为“/”。进而,在解析URL时,"protocal://host:port/"后、"?queryString"前的全部字符串都被当做了URI。 
前文提出的两个问题(为什么springMVC把一个非静态资源识别成了静态资源,并了调用静态资源处理器?内置tomcat情况下,为什么下标为1的实例(RequestMappingHandlerMapping)没能正确处理这个请求?)都是这个原因导致的。

方案三:指定context-path

知道了真正的原因之后,方案就非常简单了:在application.properties中指定context-path即可:

server.contextPath=/a
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

迎刃而解。

补充

如果是存在静态资源访问;可以

拦截器那里排除静态资源的请求路径

registry.addInterceptor(new MyInterceptor()).addPathPatterns("/xx/**")
                .excludePathPatterns("/xx/**");

 

总结

原因就在于,spring boot 2.0对静态资源也进行了拦截,当拦截器拦截到请求之后,但controller里并没有对应的请求时,该请求会被当成是对静态资源的请求。此时的handler就是 ResourceHttpRequestHandler,就会抛出上述错误。

所以此时也是检查该异常的一个思路。

 

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 这个错误通常是因为代码中尝试将一个类型为org.springframework.web.servlet.resource.ResourceHttpRequestHandler的对象强制转换为org.springframework.web.method.HandlerMethod类型而引起的。 要解决这个问题,你需要检查代码中的类型转换部分,确保你正在尝试将正确的类型强制转换为HandlerMethod类型。如果你确定类型转换正确无误,那么可能需要检查代码中的依赖项,以确保所有的依赖项都已正确地加载和配置。 如果你无法解决这个问题,你可能需要考虑寻求更多帮助,例如在相关的开发社区或论坛上寻求帮助,或者咨询相关的技术专家。 ### 回答2: 这是因为在Spring MVC框架中存在两种不同的Handler,一种是ResourceHttpRequestHandler,另一种是HandlerMethod,它们有着不同的特点和用途。 ResourceHttpRequestHandler是用于处理静态资源的Handler,例如图片、CSS文件、JavaScript文件等等。这种Handler不需要进行数据转换或者业务逻辑处理,因此它的返回类型是void,就是说它不需要返回结果。 HandlerMethod则是处理普通请求的Handler,它可以进行数据转换、业务逻辑处理,并最终返回结果。例如,在处理用户提交的表单时,需要将表单数据转换为Java对象,然后进行业务逻辑处理,最终将处理结果返回给用户。 在代码中出现“org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod”的原因是由于代码中将ResouceHttpRequestHandler强制转换成了HandlerMethod,而这两种Handler是完全不同的两种类型,不能进行强制类型转换。 解决这个问题的方法是:在代码中使用正确的Handler类型,并根据实际需求进行处理。如果需要处理静态资源,就使用ResourceHttpRequestHandler;如果需要处理普通请求,就使用HandlerMethod。 总之,在使用Spring MVC框架时,要注意不同Handler的区别和使用方法,以避免出现错误。 ### 回答3: 问题的出现可能是由于在Spring框架中不同的Handler处理程序之间类型转换引起的。在某些情况下,可能要求将某些处理程序的对象转换为另一种类型的对象。如果这种转换不正确,就可能导致类型转换异常。 在这种情况下,出现了org.springframework.web.servlet.resource.ResourceHttpRequestHandler无法转换为org.springframework.web.method.HandlerMethod的问题。这是因为org.springframework.web.servlet.resource.ResourceHttpRequestHandler是用于处理静态资源的处理程序,而org.springframework.web.method.HandlerMethod是用于处理控制中定义的方法的处理程序。 因此,可能是在配置文件或代码中错误地将org.springframework.web.servlet.resource.ResourceHttpRequestHandler指定为处理程序,而实际上需要处理控制方法的org.springframework.web.method.HandlerMethod。 要解决这个问题,应检查配置文件和代码,查看是否有不正确的类型转换。如果有,应将其更正为正确的处理程序类型。还可以考虑使用适当的Spring MVC注解来指示Spring框架使用正确的Handler处理程序。例如,在控制中使用@RequestMapping注解指定要处理的URL映射。同时,还应注意遵循Spring框架的最佳实践,在代码和配置中避免类型转换错误,以确保应用程序的正确运行和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值