自定义Spring-MVC HandlerMapping Demo
- 这里就不介绍Spring-MVC 的流程了,不懂的可以百度;
- 你都知道什么是HandlerMapping了,应该是知道什么Spring-MVC的工作流程
- 接下来就是就是demo示例了。
我们先分析一下Spring-MVC的自带RequestMappingHandlerMapping映射器
这是RequestMappingHandlerMapping 的继承树
顾名思义这个就是解析我们@RequestMapping()的映射器,我后面就模仿一个@ZkqMapping()
Spring Boot 再启动的时会自动调用该方法,把注入的Spring bean进行验证当前bean是否支持该映射器
这里会循环说有HandlerMapping 映射器,只要不为空就表示匹配成功!这个映射器就能解析我们的请求(如果出现多个可以解析的HandlerMapping ,那么谁在前面就用谁,源码中可以看出。不为空就直接返回,不在继续循环)
getHandler 有调用的当前getHandlerInternal 这个是确定当HandlerMapping 有没有能处理该请求的方法
getHandlerInternal 内部查找有没有能处理该请求的方法内有就返回null ,然后就出栈了,上面就会循环下一个HandlerMapping 直到找到为止(所以我们常用的RequestMappingHandlerMapping一般都在第一个提高效率)
到此我们大概了解的我们常用的RequestMappingHandlerMapping 的工作流程,接下老我们模仿一个。。。
2. 自定义HandlerMapping代码实现
2.1 编写说明
这里我把Spring的 RequestMappingHandlerMapping和RequestMappingInfoHandlerMapping和二为一。
当然也可以直接继承RequestMappingInfoHandlerMapping 示例:
这是我的继承树
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
第二次循环才匹配,因为第一个是我们自定义的
响应没问题
3.2 自定义请求
只定义请求地址: http://localhost:8080/yue
第一次就匹配成功了
响应也正常
到此结束,通过此案例我们大概了解了RequestMappingHandlerMapping的映射流程,以及实现了一个简单的demo。有感兴趣的可以了解其他的HandlerMapping。(欢迎交流技术,大神勿喷!)