自定义Spring-MVC HandlerMapping Demo

  1. 这里就不介绍Spring-MVC 的流程了,不懂的可以百度;
  2. 你都知道什么是HandlerMapping了,应该是知道什么Spring-MVC的工作流程
  3. 接下来就是就是demo示例了。

我们先分析一下Spring-MVC的自带RequestMappingHandlerMapping映射器

这是RequestMappingHandlerMapping 的继承树

harmonyos显示自定义loading 自定义handlermapping_mvc

顾名思义这个就是解析我们@RequestMapping()的映射器,我后面就模仿一个@ZkqMapping()

Spring Boot 再启动的时会自动调用该方法,把注入的Spring bean进行验证当前bean是否支持该映射器

harmonyos显示自定义loading 自定义handlermapping_ide_02

这里会循环说有HandlerMapping 映射器,只要不为空就表示匹配成功!这个映射器就能解析我们的请求(如果出现多个可以解析的HandlerMapping ,那么谁在前面就用谁,源码中可以看出。不为空就直接返回,不在继续循环)

harmonyos显示自定义loading 自定义handlermapping_java_03


getHandler 有调用的当前getHandlerInternal 这个是确定当HandlerMapping 有没有能处理该请求的方法

harmonyos显示自定义loading 自定义handlermapping_java_04


getHandlerInternal 内部查找有没有能处理该请求的方法内有就返回null ,然后就出栈了,上面就会循环下一个HandlerMapping 直到找到为止(所以我们常用的RequestMappingHandlerMapping一般都在第一个提高效率)

harmonyos显示自定义loading 自定义handlermapping_java_05


到此我们大概了解的我们常用的RequestMappingHandlerMapping 的工作流程,接下老我们模仿一个。。。

2. 自定义HandlerMapping代码实现

2.1 编写说明

这里我把Spring的 RequestMappingHandlerMapping和RequestMappingInfoHandlerMapping和二为一。

当然也可以直接继承RequestMappingInfoHandlerMapping 示例:

这是我的继承树

harmonyos显示自定义loading 自定义handlermapping_ide_06

2.2 代码编写

我们先定义个类似SpringMVC中的@RequestMapping 注解 名字为 @ZkqMapping

import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.*;

/**
 * @Description Description
 * @Author 张凯强
 * @Date Created in 2021/9/24
 * @E-mail 862166318@qq.com
 */

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ZkqMapping {

    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};

}

我们编写映射器 ,有很多都是直接复制RequestMappingInfoHandlerMapping 的代码,我要只要是定义isHandler()、getMappingForMethod(),这连个方法即可

import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringValueResolver;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Set;

/**
 * @Description Description
 * @Author 张凯强
 * @Date Created in 2021/9/17
 * @E-mail 862166318@qq.com
 */

// 把当前类注入spring IOC 容器,不然Spring 无法添加当前映射器
@Component
public class ZkqHandlerMapping  extends AbstractHandlerMethodMapping<RequestMappingInfo> implements MatchableHandlerMapping, EmbeddedValueResolverAware {

    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

    private StringValueResolver embeddedValueResolver;

    // 这个是提升我们的HandlerMapping等级(重写了父类的方法还有其他方式这里不在演示) int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    // 判断这个类的公共方法上是否有@ZkqMapping 注解,有表示该映射器支持当前类的映射关系,否则不支持
    @Override
    protected boolean isHandler(Class<?> aClass) {
        return (AnnotatedElementUtils.hasAnnotation(aClass, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(aClass, ZkqMapping.class));
    }

    // 返回映射的方法
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> aClass) {
        ZkqMapping zkqMapping = method.getAnnotation(ZkqMapping.class);
        return zkqMapping==null?null:RequestMappingInfo
                .paths(zkqMapping.value())
                .methods(zkqMapping.method())
                .params(zkqMapping.params())
                .headers(zkqMapping.headers())
                .consumes(zkqMapping.consumes())
                .produces(zkqMapping.produces())
                .mappingName(zkqMapping.name())
                .options(config)
                .build();
    }

    // 返回所提供映射中包含的URL路径。
    @Override
    protected Set<String> getMappingPathPatterns(RequestMappingInfo requestMappingInfo) {
        return requestMappingInfo.getPatternsCondition().getPatterns();
    }

    // 检查一个映射是否与当前请求匹配,并返回一个与当前请求相关的条件的(可能是新的)映射。
    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo requestMappingInfo, HttpServletRequest httpServletRequest) {
        return requestMappingInfo.getMatchingCondition(httpServletRequest);
    }

    // 返回一个比较器,用于对匹配的映射进行排序。 返回的比较器应该将“更好”的匹配排序得更高。
    @Override
    protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest httpServletRequest) {
        return (info1, info2) -> info1.compareTo(info2, httpServletRequest);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    // 这里匹配成功,可以给request里面放值,在想要的环节可以取出
    @Override
    protected void handleMatch(RequestMappingInfo mapping, String lookupPath, HttpServletRequest request) {
        System.out.println(lookupPath);
//        super.handleMatch(mapping, lookupPath, request);
    }

    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        try {
            return super.getHandlerInternal(request);
        }
        finally {
            ProducesRequestCondition.clearMediaTypesAttribute(request);
        }
    }

    @Override
    public RequestMatchResult match(HttpServletRequest request, String pattern) {
        RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
        RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
        if (matchingInfo == null) {
            return null;
        }
        Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request, LOOKUP_PATH);
        return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
    }

启动类和Controller的编写

import com.zkq.springdemo.config.ZkqMapping;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class SpringDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringDemoApplication.class, args);

    }

    // 内部类 (不想在创建包,毕竟只是demo)
    @RestController
    public class TestController{

        // SpringMVC的原生映射
        @GetMapping("test")
        public String test(){
            return "zkq";
        }

        // 我们自己的映射
        @ZkqMapping("zkq")
        public String zkq(){
            return "自定义";
        }

        @ZkqMapping("yue")
        public String yue(){
            return "月光之上";
        }
    }

}

3. 测试代码

3.1 原生请求

我们先访问原生的映射 http://localhost:8080/test

第二次循环才匹配,因为第一个是我们自定义的

harmonyos显示自定义loading 自定义handlermapping_ide_07


响应没问题

harmonyos显示自定义loading 自定义handlermapping_mvc_08

3.2 自定义请求

只定义请求地址: http://localhost:8080/yue

第一次就匹配成功了

harmonyos显示自定义loading 自定义handlermapping_MVC_09


响应也正常

harmonyos显示自定义loading 自定义handlermapping_mvc_10


到此结束,通过此案例我们大概了解了RequestMappingHandlerMapping的映射流程,以及实现了一个简单的demo。有感兴趣的可以了解其他的HandlerMapping。(欢迎交流技术,大神勿喷!)