• SpringMVC自定义兼容性HandlerMapping


    写在前面

    看到这篇博客时,默认你知道Spring MVC中HandlerMapping的作用,及前台请求到响应的的流转。
    感谢网上其他大佬博客给我的借鉴,博客地址这里忘记了。
    大家可以直接点击右上角进入我的SpringBoot项目查看源码,有用的话帮我点亮下呗。

    自定义Handler

    我有时候会考虑是否可以自定义HandlerMapping,可以参考RequestMappingHandlerMapping继承的父类,并且重写部分方法,以下为我的实现。

    首先,需要新建一个注解,这个注解的作用同@RequestMapping.

    package com.example.feng.annotation;
    
    
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author fengjirong
     * @date 2021/3/11 14:20
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FengRequestMapping {
    
        String value() default "";
    
        RequestMethod[] method() default {};
    }
    
    
    

    接下来是自定义handler的代码,需要实现多个方法,用于指定自定义注解修饰的方法使用当前handler,设置handler对象的url等参数,各方法的作用请查看对应方法注释。需要注意的是,需将自定义handler的优先级设置为order(0),否则会出现异常。

    package com.example.feng.handler;
    
    import com.example.feng.annotation.FengRequestMapping;
    import com.example.feng.utils.ApplicaitonFactory;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.EmbeddedValueResolverAware;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.lang.Nullable;
    import org.springframework.stereotype.Component;
    import org.springframework.util.Assert;
    import org.springframework.util.StringValueResolver;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.MatchableHandlerMapping;
    import org.springframework.web.servlet.handler.RequestMatchResult;
    import org.springframework.web.servlet.mvc.condition.RequestCondition;
    import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
    import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
    import org.springframework.web.util.UrlPathHelper;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    import java.util.Set;
    
    /**
     * @author fengjirong
     * @date 2021/3/11 15:51
     */
    @Component
    class FengRequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
            implements MatchableHandlerMapping, EmbeddedValueResolverAware {
        private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
    
        @Nullable
        private StringValueResolver embeddedValueResolver;
    
        /**
         * handler是否含有@FengRequestMapping注解
         *
         * @param beanType
         * @return boolean
         * @Author fengjirong
         * @Date 2021/3/11 14:35
         */
        @Override
        protected boolean isHandler(Class<?> beanType) {
            Method[] methods = beanType.getDeclaredMethods();
            for (Method method : methods) {
                if (AnnotationUtils.findAnnotation(method, FengRequestMapping.class) != null) {
                    return true;
                }
            }
            return false;
    
        }
    
        /**
         * description: 使用方法级别的@ {@FengRequestMapping}注释创建RequestMappingInfo。
         *
         * @param method  handlerType
         * @param handlerType  handlerType
         * @return RequestMappingInfo
         * @Author fengjirong
         * @Date   2021/3/12 11:24
         */
        @Override
        protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
            RequestMappingInfo info = null;
            FengRequestMapping mapping = method.getAnnotation(FengRequestMapping.class);
            if (mapping != null){
                RequestCondition<?> condition = getCustomMethodCondition(method);
                info = createRequestMappingInfo(mapping, condition);
            }
            return info;
        }
    
        /**
         * description: 匹配操作
         *
         * @param info
         * @return
         * @Author fengjirong
         * @Date   2021/3/12 11:26
         */
        @Override
        protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
            request.setAttribute("isMongo", true);
            request.setAttribute("handledTime", System.nanoTime());
        }
    
        /**
         * description: 不匹配url处理
         * 
         * @param infos 
         * @param lookupPath 
         * @param request 
         * @return HandlerMethod
         * @Author fengjirong
         * @Date   2021/3/12 11:37
         */
        @Override
        protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
            return null;
        }
    
        /**
         * description: 从注解中获得请求路径、请求类型等创建RequestMappingInfo对象方法
         *
         * @param requestMapping
         * @param customCondition
         * @return RequestMappingInfo
         * @Author fengjirong
         * @Date   2021/3/12 11:28
         */
        private RequestMappingInfo createRequestMappingInfo(
                FengRequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
            ConfigurableApplicationContext context = ApplicaitonFactory.getContext();
            RequestMappingInfo.Builder builder = RequestMappingInfo
                    .paths(resolveEmbeddedValuesInPatterns(new String[]{requestMapping.value()}))
                    .methods(requestMapping.method())
                    .params(new String[]{})
                    .headers(new String[]{})
                    .consumes(new String[]{})
                    .produces(new String[]{})
                    .mappingName("");
            if (customCondition != null) {
                builder.customCondition(customCondition);
            }
            return builder.options(this.config).build();
        }
    
        /**
         * 属性设置
         */
        @Override
        public void afterPropertiesSet() {
            // 提升当前 HandlerMapping 的在映射处理器列表中的顺序
            super.setOrder(0);
            super.afterPropertiesSet();
        }
    
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            this.embeddedValueResolver = resolver;
        }
    
        @Override
        public RequestMatchResult match(HttpServletRequest request, String pattern) {
            Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern");
            RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
            RequestMappingInfo match = info.getMatchingCondition(request);
            return (match != null && match.getPatternsCondition() != null ?
                    new RequestMatchResult(
                            match.getPatternsCondition().getPatterns().iterator().next(),
                            UrlPathHelper.getResolvedLookupPath(request),
                            getPathMatcher()) : null);
        }
    
        /**
         * Resolve placeholder values in the given array of patterns.
         * @return a new array with updated patterns
         */
        protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
            if (this.embeddedValueResolver == null) {
                return patterns;
            }
            else {
                String[] resolvedPatterns = new String[patterns.length];
                for (int i = 0; i < patterns.length; i++) {
                    resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]);
                }
                return resolvedPatterns;
            }
        }
    
        @Nullable
        protected RequestCondition<?> getCustomMethodCondition(Method method) {
            return null;
        }
    }
    
    

    接下来是测试controller,测试@FengRequestMapping与@RequestMapping的兼容性。

    package com.example.feng.student;
    
    import com.example.feng.annotation.FengRequestMapping;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author fengjirong
     * @date 2021/3/10 11:11
     */
    @Controller
    public class StudentController {
    
        @ResponseBody
        @FengRequestMapping(value = "/student", method = RequestMethod.GET)
        public String get(){
            return "get submit";
        }
    
        @ResponseBody
        @FengRequestMapping(value = "/student", method = RequestMethod.POST)
        public String post(){
            return "post submit";
        }
    
        @ResponseBody
        @FengRequestMapping(value = "/student", method = RequestMethod.PUT)
        public String put(){
            return "put submit";
        }
    
        @ResponseBody
        //@FengRequestMapping(value = "/student", method = RequestMethod.DELETE)
        @DeleteMapping(value = "/student")
        public String delete(){
            return "delete submit";
        }
    }
    
    

    前台页面使用rest风格表单提交的index.html。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>rest风格controller测试</title>
        <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
        <script type="text/javascript">
            function doButton() {
                $.ajax({
                    type: "DELETE",
                    url: "/student",
                    async:false,
                    success:function (data) {
                        alert(data)
                    }
                });
            }
        </script>
    </head>
    <body>
    <form action="/student" method="GET">
        <input type="submit" value="get">
    </form>
    <form action="/student" method="POST">
        <input type="submit" value="post">
    </form>
    <form action="/student" method="POST">
        <input name="_method" value="put" type="hidden">
        <input name="_m" value="put" type="hidden">
        <input type="submit" value="put">
    </form>
    <form action="/student" method="POST">
        <input name="_method" value="delete" type="hidden">
        <input name="_m" value="delete" type="hidden">
        <input type="submit" value="delete">
    </form>
    
    <button name="button1" onclick="doButton()">
        确认
    </button>
    
    
    </body>
    </html>
    

    SpringBoot开启Rest风格controller需要在配置文件中手动开启,添加以下配置项

    spring:
      mvc:
        hiddenmethod:
          filter:
          #启用rest风格请求
            enabled: true
    

    看效果

    查看自定义handler中handler注册详情。

    而使用@RequestMapping标注的handler注册在RequestMappingHandlerMapping组件中。

    由上两张图,我们可以看到即使多个注解在一个conroller中,也能够得到很好的映射,这样提高了自定义handler的兼容性。
    由于我测试的时候构建的是Spring Boot项目,访问http://localhost:8004/跳转到index.html,点击按钮,前台提交表单,可以得到对应的响应。

  • 相关阅读:
    如何在Centos官网下载所需版本的Centos
    Zabbix微信告警
    CentOS 7.4 源码编译安装 Redis
    zabbix源码安装后,设置为服务启动和关闭
    MySQL 快速入门教程
    mysql解决 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)的报错
    转:Centos7安装zabbix3.4超详细步骤解析
    centos7使用Gogs搭建Git服务器
    Centos7 使用firewall管理防火墙
    zabbix登录密码重置方法
  • 原文地址:https://www.cnblogs.com/fjrgg/p/14523883.html
Copyright © 2020-2023  润新知