SpringMVC自定义兼容性HandlerMapping
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 */@Componentclass 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 */@Controllerpublic 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,点击按钮,前台提交表单,可以得到对应的响应。