自定义注解实现RequestMapping功能

RequestMapping注解原理

RequestMapping注解的核心处理逻辑都在RequestMappingHandlerMapping中。
先上类图
在这里插入图片描述

RequestMappingHandlerMapping 介绍

RequestMappingHandlerMapping是SpringMVC中的一个重要组件,作用是扫描@Controller、@RequestMapping注解修饰的类,然后生成请求与方法的对应关系,当有一个 HTTP 请求进入 SpringMVC 时,就会通过请求找到对应的方法进行执行。
可以简单的想象一下,在RequestMappingHandlerMapping会维护一个Map<String,Handle>,key 存放的是URI,value 存放的是对应处理的handle,例如:
map.put(“GET /user”,UserController#get)
map.put(“POST /user”,UserController#create)

加载流程

  • 流程图
    在这里插入图片描述
    在项目启动时,会触发上述加载流程。具体如下:
  1. 方法入口:RequestMappingHandlerMapping实现了InitializingBean接口,在应用启动时会触发afterPropertiesSet方法。
    – RequestMappingHandlerMapping.java
    在这里插入图片描述
  2. 接着调用父类AbstractHandlerMethodMapping,在initHandlerMethods方法中,会遍历所有候选的 Bean,并通过processCandidateBean方法进行处理。
    – AbstractHandlerMethodMapping.java
protected void initHandlerMethods() {
        String[] var1 = this.getCandidateBeanNames();
        int var2 = var1.length;
        //遍历所有候选的bean name
        for(int var3 = 0; var3 < var2; ++var3) {
            String beanName = var1[var3];
            if (!beanName.startsWith("scopedTarget.")) {
                //处理bean
                this.processCandidateBean(beanName);
            }
        }

        this.handlerMethodsInitialized(this.getHandlerMethods());
    }
  1. 在processCandidateBean方法中,会调用RequestMappingHandlerMapping中实现的isHandler判断Bean是否为@Controller、@RequestMapping注解修饰的类,是的话调用detectHandlerMethods来检查类中的Handler method

– AbstractHandlerMethodMapping.java

protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;

        try {
            beanType = this.obtainApplicationContext().getType(beanName);
        } catch (Throwable var4) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
            }
        }
		// 调用实现类的 isHandler方法
        if (beanType != null && this.isHandler(beanType)) {
            // 处理符合条件的bean
            this.detectHandlerMethods(beanName);
        }

    }

– RequestMappingHandlerMapping.java

// 判断Bean是否为@Controller、@RequestMapping注解修饰的类
protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
    }
  1. detectHandlerMethods中会遍历类中所有方法,通过getMappingForMethod方法筛选出@RequestMapping注解修饰的方法,然后解析成method->mapping的 Map 结构存起来,再遍历使用registerHandlerMethod方法注册到 SpringMVC 中。
    – AbstractHandlerMethodMapping.java
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 获取该类中的所有方法
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
                try {
                    // 匿名内部类,最终会调用RequestMappingHandlerMapping中具体实现方法,筛选出被@RequestMapping修饰的方法
                    return this.getMappingForMethod(method, userType);
                } catch (Throwable var4) {
                    throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
                }
            });
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(this.formatMappings(userType, methods));
            }
            // 把符合条件的方法进行注册
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                //注册逻辑
                this.registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }

    }
  1. 通过registerHandlerMethod将对应的关系存放到mappingRegistry对象中,里面有很多的 Map 用于存储映射关系。
    – AbstractHandlerMethodMapping.java
public void register(T mapping, Object handler, Method method) {
            if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length > 0 && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
                    throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
                }
            }

            this.readWriteLock.writeLock().lock();

            try {
                // 封装HandlerMethod,实际上就是bean name+method,在拦截器中就是暴露的这个对象
                HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
                this.validateMethodMapping(handlerMethod, mapping);
                // 将mapping对象和handlerMethod关系存放至mappingLookup
                this.mappingLookup.put(mapping, handlerMethod);
                List<String> directUrls = this.getDirectUrls(mapping);
                Iterator var6 = directUrls.iterator();

                while(var6.hasNext()) {
                    String url = (String)var6.next();
                    // 将非通配符形式的路径与mapping对象关系存放至urlLookup
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
                    name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
                    this.addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                this.registry.put(mapping, new MappingRegistration(mapping, handlerMethod, directUrls, name));
            } finally {
                this.readWriteLock.writeLock().unlock();
            }

        }

通过源码可以得知,目前有这两个mappingLookup和urlLookup对象存放了请求映射关系,在请求到来的时候就会通过这两个Map去寻找要执行的方法。

请求流程

springMVC 流程图
在这里插入图片描述
请求的入口由DispatcherServlet统一接管,核心处理方法为 doDispatch
– DispatcherServlet.java

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

        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 获取拦截器链HandlerExecutionChain,其中包含了处理请求的controller方法、拦截器方法等。
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
					// 1、执行preHandle()方法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    // 2、执行controller中的方法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    // 3、执行postHandle()方法
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // 4、调用afterCompletion()方法
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                // 4、内部调用了afterCompletion()方法,就算报异常也会调用afterCompletion()方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                // 4、内部调用了afterCompletion()方法,就算报异常也会调用afterCompletion()方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

在getHandler方法中就是对应的逻辑了,代码如下:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        //遍历handlerMappings,只要能根据请求匹配到一个handler就返回
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

这里值得一提的是handlerMappings是一组HandlerMapping接口的实现,SpringMVC默认提供的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,如果有需要我们也可以自定义一个HandlerMapping实现来处理请求。

接着一路跟踪源码,直到AbstractHandlerMethodMapping#lookupHandlerMethod(String lookupPath, HttpServletRequest request)方法,就可以看到具体的实现了。
– AbstractHandlerMethodMapping.java

//先直接使用URI进行匹配,适用于没使用通配符修饰的接口路径,对应urlLookup
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
    //路径匹配到之后,还要根据method、header、consume、produce等等条件继续进行匹配
    addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
    //如果没匹配到,再通过通配符的方式去匹配,对应mappingLookup
    addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

自定义注解

实现自定义注解实现RequestMapping的功能。

定义注解

// 用于标识对外暴露的接口类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {
    @AliasFor("prefix")
    String[] value() default {};

    @AliasFor("value")
    String[] prefix() default {};
    
    // 默认POST方法
    RequestMethod[] method() default {RequestMethod.POST};
}
//DisableOpenApi:用于标记接口类中不对外暴露的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisableOpenApi {
}

核心处理逻辑

/**
 * OpenApiHandlerMapping
 */
public class OpenApiHandlerMapping extends RequestMappingHandlerMapping {
    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

    /**
     * 判断是否是OpenApi注解的类
     *
     * @param beanType
     * @return
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, OpenApi.class);
    }

    /**
     * 构造每个方法的请求信息
     *
     * @param method
     * @param handlerType
     * @return
     */
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        //根据方法构造,如果方法上没有请求path,直接返回null
        RequestMappingInfo info = createRequestMappingInfoByMethod(method);
        if (info != null) {
            //获取类上的prefix,和方法上的path拼接在一起
            RequestMappingInfo typeInfo = createRequestMappingInfoByClass(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
        }
        return info;
    }

    private RequestMappingInfo createRequestMappingInfoByClass(AnnotatedElement element) {
        OpenApi OpenApi = AnnotatedElementUtils.findMergedAnnotation(element, OpenApi.class);
        return bizOpenApi != null ? RequestMappingInfo.paths(bizOpenApi.value()).build() : null;
    }

    private RequestMappingInfo createRequestMappingInfoByMethod(AnnotatedElement element) {
        // DisableOpenApi标记的方法不对外暴露
        if (AnnotatedElementUtils.findMergedAnnotation(element, DisableOpenApi.class) != null) {
            return null;
        }
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        if (requestMapping == null) {
            return null;
        }
        RequestMappingInfo.Builder builder = RequestMappingInfo.paths(super.resolveEmbeddedValuesInPatterns(requestMapping.path()))
            .methods(requestMapping.method())
            .params(requestMapping.params())
            .headers(requestMapping.headers())
            .consumes(requestMapping.consumes())
            .produces(requestMapping.produces())
            .mappingName(requestMapping.name());
        return builder.options(this.config).build();
    }

    @Override
    public void afterPropertiesSet() {
        //设置优先级
        this.setOrder(100);
        super.afterPropertiesSet();
    }
}

配置类

关键:配置类继承DelegatingWebMvcConfiguration的目的是为了把spring mvc的拦截器添加进来,使得我们配置的拦截器对自定义注解依然生效。(关于spring拦截器的原理将在下一章解析)

@Configuration
public class OpenApiConfig extends DelegatingWebMvcConfiguration {
    @Bean
    public BizOpenApiHandlerMapping bizOpenApiHandlerMapping(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
        OpenApiHandlerMapping mapping = new OpenApiHandlerMapping();
        mapping.setInterceptors(this.getInterceptors(conversionService, resourceUrlProvider));
        mapping.setContentNegotiationManager(contentNegotiationManager);
        mapping.setCorsConfigurations(this.getCorsConfigurations());
        return mapping;
    }
}

效果

localhost:8080/test/hello 可以正常访问
localhost:8080/test/hello2 无法访问到

@RestController
@OpenApi("/test")
public class ApiController {
    @PostMapping("/hello")
    public void hello() {
         System.out.println("hello");
    }
    @PostMapping("/hello2")
    @DisableOpenApi
    public void hello() {
         System.out.println("hello");
    }

参考文章
自定义SpringMVC中的RequestMappingHandlerMapping
RequestMapping原理分析
自定义注解实现@RequestMapping路由功能

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值