关于spring:根据RequestMapping原理实现自定义注解

依据@RequestMapping原理实现自定义注解

基于最近的一个需要:客户端与服务端通过socket连贯,在socket通道里传输信息,在信息里定义一个type辨别客户端的申请类型,依据这个类型找到对应的业务解决逻辑。这不就是依据url解析对应门路找到Controller吗!于是间接开始撸@RequestMapping源码。

@RequestMapping源码剖析

间接看到 AbstractHandlerMethodMapping中afterPropertiesSet()

/**
 * 在初始化时检测处理程序办法
 * @see #initHandlerMethods
 */
@Override
public void afterPropertiesSet() {
   initHandlerMethods();
}

/**
 * 在ApplicationContext中扫描bean,检测并注册处理程序办法
 * SCOPED_TARGET_NAME_PREFIX示意域代理bean的前缀
 * @see #getCandidateBeanNames()
 * @see #processCandidateBean
 * @see #handlerMethodsInitialized
 */
protected void initHandlerMethods() {
   for (String beanName : getCandidateBeanNames()) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

在properties属性加载好之后就会初始化程序处理办法initHandlerMethods(),getCandidateBeanNames() 获取上下文中beanName,processCandidateBean(beanName)只解决不是域代理bean的bean,而后咱们看看processCandidateBean()

protected void processCandidateBean(String beanName) {
   Class<?> beanType = null;
   try {
      beanType = obtainApplicationContext().getType(beanName);
   }
   catch (Throwable ex) {
      // An unresolvable bean type, probably from a lazy bean - let's ignore it.
      if (logger.isTraceEnabled()) {
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
      }
   }
   // beanType != null 示意确定具备给定名称bean的类型
   // isHandler(beanType)示意是否被@Controller和@RequestMapping标注
   if (beanType != null && isHandler(beanType)) {
      detectHandlerMethods(beanName);
   }
}

/**
     * org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler
     * 提供实现
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

而后再看看detectHandlerMethods() 就是在指定的bean外面查找以后对应的method

/**
 * 在指定的处理程序bean中查找处理程序办法
 * @param handler either a bean name or an actual handler instance
 * @see #getMappingForMethod
 */
protected void detectHandlerMethods(Object handler) {
   //找出以后的类型,为了兼容能够传入beanName再在ApplicationContext里查找初对应的类型
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if (handlerType != null) {
      //返回给定类的用户定义类:通常只是给定的类,但对于CGLIB生成的子类,则返回原始类
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      //依据相关联的元数据查找抉择给定指标类型上的办法
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> {
               try {
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  throw new IllegalStateException("Invalid mapping on handler class [" +
                        userType.getName() + "]: " + method, ex);
               }
            });
      if (logger.isTraceEnabled()) {
         logger.trace(formatMappings(userType, methods));
      }
      //将以后失去信息便当寄存到Map中,key是RequestMappingInfo,value包装成HandlerMethod
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}
/**
 * 应用办法和类型级别的@ {@ link RequestMapping}注解 RequestMappingInfo
 * @return the created RequestMappingInfo, or {@code null} if the method
 * does not have a {@code @RequestMapping} annotation.
 * @see #getCustomMethodCondition(Method)
 * @see #getCustomTypeCondition(Class)
 */
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   //依据method创立RequestMappingInfo->蕴含门路信息,参数信息等
   RequestMappingInfo info = createRequestMappingInfo(method);
   if (info != null) {
      //依据类型创立依据method创立RequestMappingInfo
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
         //将类和method的RequestMappingInfo整合
         info = typeInfo.combine(info);
      }
      //解析类的门路信息
      String prefix = getPathPrefix(handlerType);
      if (prefix != null) {
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
      }
   }
   return info;
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
   RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
   RequestCondition<?> condition = (element instanceof Class ?
         getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
   return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestMappingInfo createRequestMappingInfo(
            RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(this.config).build();
    }

再回到initHandlerMethods()中的handlerMethodsInitialized(),这个办法就是获取Map中的HandlerMethod数量并打印进去。到这里咱们再来缕一缕流程:

  1. 获取ApplicationContext中所有被@Controller或@RequestMapping标识的bean
  2. 遍历bean中的method,获取bean和method的RequestMappingInfo,将两个RequestMappingInfo整合,
  3. 将失去RequestMappingInfo和对应的HandlerMethod存入Map中
  4. 失去了对应的可执行method,那么什么时候调用呢,接下来看看上面的org.springframework.web.servlet.DispatcherServlet#doDispatch 当申请过去了做的散发
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         //重点看这个办法,层层递进找怎么获取到handler的,就会看到上面的办法
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         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;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   //以后这个this.mappingRegistry就是咱们之前把信息存进去的Map
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   if (directPathMatches != null) {
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      // No choice but to go through all mappings...
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }

   if (!matches.isEmpty()) {
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
         matches.sort(comparator);
         bestMatch = matches.get(0);
         if (logger.isTraceEnabled()) {
            logger.trace(matches.size() + " matching mappings: " + matches);
         }
         if (CorsUtils.isPreFlightRequest(request)) {
            return PREFLIGHT_AMBIGUOUS_MATCH;
         }
         Match secondBestMatch = matches.get(1);
         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            String uri = request.getRequestURI();
            throw new IllegalStateException(
                  "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
   }
   else {
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
   }
}

到这里源码就全副浏览完了

尝试实现@SocketMapping

间接上代码

package com.cmge.handler;

import com.alibaba.fastjson.JSONObject;
import com.cmge.annotation.SocketMapping;
import com.cmge.controller.BaseController;
import com.cmge.info.SocketMappingInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author jasongaoj
 * @version 1.0.0
 * @Description 申请业务解决类
 * @createTime 2020年11月26日 17:49:00
 */
@Slf4j
@Component
public class RequestMappingHandler {

    private final Map<String, SocketMappingInfo> mappingLookup = new LinkedHashMap<>();

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 解析音讯,散发申请
     * @param msg
     * @return
     */
    public String doDispatchMessage(String msg) throws InvocationTargetException, IllegalAccessException {
        JSONObject msgJSON=JSONObject.parseObject(msg);
        String mapping=msgJSON.getString("type");
        SocketMappingInfo socketMappingInfo=mappingLookup.get(mapping);
        if(socketMappingInfo!=null){
            return (String) socketMappingInfo.getMethod().invoke(applicationContext.getBean(socketMappingInfo.getBeanName()),msg);
        }
        return null;
    }

    public boolean parseParam(JSONObject jsonObject){
        String mapping=jsonObject.getString("type");
        if(mapping.equals("SYS")){
            log.trace("过滤网关怀跳检测。。。");
        }
        return true;
    }

    /**
     * 注册所有的mapping
     */
    public void registerSocketHandlerMapping(){
        String[] beanNames=applicationContext.getBeanNamesForType(BaseController.class);
        for (String beanName:beanNames) {
            processCandidateBean(beanName);
        }
    }

    /**
     * 确定指定候选bean的类型并调用
     * @param beanName
     */
    private void processCandidateBean(String beanName){
        Class<?> beanType = null;
        try {
            beanType = applicationContext.getType(beanName);
        } catch (Throwable ex) {
            // An unresolvable bean type, probably from a lazy bean - let's ignore it.
            log.trace("Could not resolve type for bean '" + beanName + "'", ex);
        }
        //如果以后bean存在实例,则检测以后可执行的method
        if (beanType != null ) {
            detectHandlerMethods(beanType,beanName);
        }
    }

    /**
     * 获取以后mapping对应的method,将解析的method注册到mappingLookup
     * (key,value)->(@SocketMapping.value(),invokeMethod())
     * @param beanType
     * @param beanName
     */
    private void detectHandlerMethods(Class<?> beanType,String beanName){
        Class<?> userType = ClassUtils.getUserClass(beanType);
        Map<Method, SocketMappingInfo> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<SocketMappingInfo>) method -> {
                    try {
                        return createRequestMappingInfo(method);
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                userType.getName() + "]: " + method, ex);
                    }
                });
        methods.forEach(((method, socketMappingInfo) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            socketMappingInfo.setMethod(invocableMethod);
            socketMappingInfo.setBeanName(beanName);
            mappingLookup.put(socketMappingInfo.getName(),socketMappingInfo);
        }));
    }

    /**
     * 创立mapping信息
     * @param element
     * @return
     */
    @Nullable
    private SocketMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        SocketMapping socketMapping = AnnotatedElementUtils.findMergedAnnotation(element, SocketMapping.class);
        SocketMappingInfo socketMappingInfo=SocketMappingInfo.builder()
                .name(socketMapping.value())
                .build();
        return socketMappingInfo;
    }
}



package com.cmge.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * @author jasongaoj
 * @version 1.0.0
 * @Description socket音讯中的type实现申请散发
 * @createTime 2020年11月27日 10:03:00
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SocketMapping {
     String value() default "";
}


package com.cmge.info;

import lombok.*;

import java.lang.reflect.Method;

/**
 * @author jasongaoj
 * @version 1.0.0
 * @Description SocketMapping信息
 * @createTime 2020年11月27日 11:20:00
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SocketMappingInfo {
    private String name;
    private Method method;
    private String beanName;
}

我这里简略实现了依据type查找对应method,@SocketMapping很多中央有待优化。后续可能持续扩大将注解增加到类上、门路解析等性能,心愿对大家有所帮忙。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理