Implement custom annotations based on @requestMapping

Based on a recent requirement: the client connects with the server through socket, transmits information in socket channel, defines a type in the information to distinguish the client request type, and finds the corresponding business processing logic according to this type. Find the Controller by url resolution. So start directly @requestMapping source code.

RequestMapping source code analysis

See directly AbstractHandlerMethodMapping afterPropertiesSet ()

/** * Checks handler methods * at initialization@see #initHandlerMethods
 */
@Override
public void afterPropertiesSet(a) {
   initHandlerMethods();
}

/** * Scans the bean in the ApplicationContext, detects and registers the handler method * SCOPED_TARGET_NAME_PREFIX which represents the prefix of the domain proxy bean *@see #getCandidateBeanNames()
 * @see #processCandidateBean
 * @see #handlerMethodsInitialized
 */
protected void initHandlerMethods(a) {
   for (String beanName : getCandidateBeanNames()) {
      if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); }Copy the code

After the properties property is loaded, the program handler initHandlerMethods() is initialized, getCandidateBeanNames() gets the context beanName, ProcessCandidateBean (beanName) deals only with beans that are not domain proxy beans, then let’s look at 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 determines the type of the bean with the given name
   // isHandler(beanType) specifies whether it is flagged by @Controller and @requestMapping
   if(beanType ! =null&& isHandler(beanType)) { detectHandlerMethods(beanName); }}/ * * *. Org springframework. Web. Servlet. MVC) method. The annotation. RequestMappingHandlerMapping# isHandler * * / provide implementation
	@Override
	protected boolean isHandler(Class
        beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}
Copy the code

DetectHandlerMethods () looks for the current method in the specified bean

/** * finds the handler method * in the specified handler bean@param handler either a bean name or an actual handler instance
 * @see #getMappingForMethod
 */
protected void detectHandlerMethods(Object handler) {
   // Find the current type. For compatibility, pass in beanName and look up the original type in ApplicationContextClass<? > handlerType = (handlerinstanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if(handlerType ! =null) {
      // Return a user-defined class for a given class: Usually just a given class, but for subclasses generated by CGLIB, the original class is returnedClass<? > userType = ClassUtils.getUserClass(handlerType);// select a method on a given target type based on the associated metadata
      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));
      }
      // Put the current information into the Map conveniently. The key is RequestMappingInfo and the value is wrapped as HandlerMethodmethods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code
/** * Use the @{@link RequestMapping} annotation RequestMappingInfo * at the method and type level@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) {
   RequestMappingInfo-> create RequestMappingInfo-> create RequestMappingInfo-> create RequestMappingInfo-
   RequestMappingInfo info = createRequestMappingInfo(method);
   if(info ! =null) {
      Create RequestMappingInfo based on method
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if(typeInfo ! =null) {
         // Integrate the class with the Method's RequestMappingInfo
         info = typeInfo.combine(info);
      }
      // Parse the path information for the class
      String prefix = getPathPrefix(handlerType);
      if(prefix ! =null) {
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); }}return info;
}
Copy the code
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<? > condition = (elementinstanceofClass ? getCustomTypeCondition((Class<? >) element) : getCustomMethodCondition((Method) element));return(requestMapping ! =null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, @NullableRequestCondition<? > 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();
	}
Copy the code

Back to initHandlerMethods handlerMethodsInitialized in () (), the method is to obtain the number of HandlerMethod in Map and printed. Here we have a glimpse of the process:

  1. Gets all beans identified by @Controller or @RequestMapping in the ApplicationContext

  2. The method of the bean is iterated, and the RequestMappingInfo of the bean and method is obtained. The two RequestMappingInfo are integrated.

  3. The resulting RequestMappingInfo and the corresponding HandlerMethod are stored in the Map

  4. Method obtained the corresponding executable, so what time do you call, then look at the following org. Springframework. Web. Servlet. DispatcherServlet# doDispatch distribution when the request came over to do

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.
         // Get the handler from the 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); }}}}Copy the code

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

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   // The current this.mappingRegistry is the Map where we stored the information
   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); }}Copy the code

Here the source code is all read

Try implementing @socketMapping

Go straight to code

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
 * @DescriptionRequest business processing class *@createTime26 November 2020 17:49:00 */
@Slf4j
@Component
public class RequestMappingHandler {

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

    @Autowired
    private ApplicationContext applicationContext;

    /** * parse the message and distribute the request *@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("Filter care jump detection...");
        }
        return true;
    }

    /** * Register all mapping */
    public void registerSocketHandlerMapping(a){
        String[] beanNames=applicationContext.getBeanNamesForType(BaseController.class);
        for(String beanName:beanNames) { processCandidateBean(beanName); }}/** * Determine the type of the specified candidate bean and call *@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);
        }
        // If the current bean has an instance, the current executable method is detected
        if(beanType ! =null) { detectHandlerMethods(beanType,beanName); }}MappingLookup * (key,value) -> (@SocketMappingThe value (), the 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); })); }/** * Create mapping information *@param element
     * @return* /
    @Nullable
    private SocketMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        SocketMapping socketMapping = AnnotatedElementUtils.findMergedAnnotation(element, SocketMapping.class);
        SocketMappingInfo socketMappingInfo=SocketMappingInfo.builder()
                .name(socketMapping.value())
                .build();
        returnsocketMappingInfo; }}package com.cmge.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/ * * *@author jasongaoj
 * @version 1.0.0
 * @DescriptionThe type in the socket message implements request distribution *@createTime27 November 2020 10:03:00 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SocketMapping {
     String value(a) default "";
}


package com.cmge.info;

import lombok.*;

import java.lang.reflect.Method;

/ * * *@author jasongaoj
 * @version 1.0.0
 * @DescriptionSocketMapping information *@createTime27 November 2020 11:20:00 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SocketMappingInfo {
    private String name;
    private Method method;
    private String beanName;
}

Copy the code

I have simply implemented a method search based on Type, and @socketMapping needs to be optimized in many places. Further extensions to add annotations to classes, path resolution, etc., will be possible.