Spring MVC Interpretation - @RequestMapping (1)

Spring MVC Interpretation - @RequestMapping

    In order to reduce the length of the article and make the article more targeted and concise, we will not exemplify the usage of various @RequestMapping.

    The article mainly addresses the following issues:

    1. How Spring handles @RequestMapping (how to map request paths to controller classes or methods)

    2. How Spring dispatches requests to the correct controller class or method

    3. How Spring implements flexible controller methods

    In versions prior to Spring MVC 3.1, Spring used DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter by default to handle @RequestMapping annotations and request method calls, and since 3.1, a new set of APIs have been provided to complete these tasks. In contrast, the new API is more reasonable and complete, open, easy to expand, and object-oriented. This article is based on the analysis of the new API of 3.1.

1. Concept Analysis

    Before we start, let's understand the new interface or class introduced in the new API, which will help the understanding of the subsequent processing. It has to be said that the new API provides more beautiful abstractions, and you can feel the charm of object-oriented.

  1. The RequestMappingInfo  class is an abstraction of request mapping, which includes request path, request method, request header and other information. In fact, it can be regarded as a corresponding class of @RequestMapping.

  2. HandlerMethod  This class encapsulates the handler instance (Controller Bean), the processing method instance (Method) and the method parameter array (MethodParameter[])

  3. The MethodParameter    class has existed since 2.0. It encapsulates the relevant information and behavior of a parameter of the method, such as the index of the parameter, the method instance or constructor instance to which the parameter belongs, and the type of the parameter.

  4. HandlerMapping  The implementation class of this interface is used to define the mapping relationship between the request and the handler, and only one method getHandler is defined.

  5. AbstractHandlerMethodMapping  This is a basic implementation class of HandlerMapping, which defines the mapping relationship between requests and HandlerMethod instances.

  6. RequestMappingInfoHandlerMapping  This is the implementation class of AbstractHandlerMethodMapping, which maintains a Map property of RequestMappingInfo and HandlerMethod.

  7. RequestMappingHandlerMapping  This is a subclass of RequestMappingInfoHandlerMapping, which converts the @RequestMapping annotation into a RequestMappingInfo instance and uses it for the parent class. That is the end point where we process @RequestMapping.

  8. The InitializingBean  interface defines that its implementation Bean can perform custom initialization operations after the container completes property settings. Our AbstractHandlerMethodMapping implements this interface and defines a set of custom operations, which are used to detect and process our @RequestMapping annotations.

    Too many concepts are always a bad thing. But understanding the above concepts is basically half the battle, and the implementation is much simpler than that of @Autowired.

 

二、InitialiZingBean.afterPropertySet()

    Let's start from scratch and see exactly how Spring detects and handles our @RequestMapping annotations. I don't know if you still remember this code:

Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }

 

   This is a piece of code that BeanFactory needs to execute in the process of creating a bean. The populateBean method is the processing process of the @Autowired annotation, and the automatic injection of the executed properties. Because the initializeBean method had nothing to do with the topic at the time, it was the focus of our attention at this time. ( Previous @Autowired detailed explanation )

    In the above concept, we talked about the InitializingBean interface. Its implementation Bean will perform a custom operation after the container completes the property injection. This does not satisfy the execution wake-up of the initializeBean method. Let's look at its implementation:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {//这里检测当前Bean是否实现一些列Aware接口,并调用相关方法,我们不关心。
            invokeAwareMethods(beanName, bean);
        }
        
        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            invokeInitMethods(beanName, wrappedBean, mbd);//这是我们需要关心的,下面看下它的实现
        }

        if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

 

   Let's take a look at the implementation of the invokeInitMethods method:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {
        //是否是InitializingBean的实例
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && 
                (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        public Object run() throws Exception {//利用系统安全管理器调用
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
            }                
            else {//调用InitializingBean的afterPropertiesSet方法。
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
        //调用自定义初始化方法。。。省略,不关心
    }

 

   In the last article about <mvc:annotation-driven/> , we said that when this tag is added to the configuration file, Spring (after 3.1) will default to our registration RequestMappingHandlerMappingand other bean definitions. andRequestMappingHandlerMapping 实现了InitializingBean接口,因此,在初始化并装配该Bean实例时,执行到上述代码是,便会执行他的afterPropertySet方法。我们接下来看看他的afterPropertySet方法:

public void afterPropertiesSet() {
        initHandlerMethods();
    }

    //Scan beans in the ApplicationContext, detect and register handler methods.
    protected void initHandlerMethods() {
        //扫描所有注册的Bean
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
           BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), 
                Object.class) : getApplicationContext().getBeanNamesForType(Object.class));
        //遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod
        for (String beanName : beanNames) {
            if (isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        //这个方法是个空实现,不管他
        handlerMethodsInitialized(getHandlerMethods());
    }

 

   It directly calls the initHandlerMethods() method, and the method is described as: scan the beans in the ApplicationContext, detect and register the handler methods. we are close.

3. Detection @RequestMapping    

    Let's see how it determines whether it is a processor, and how to detect Handler Methods:

@Override
    protected boolean isHandler(Class<?> beanType) {
        return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
                (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
    }

 

   Aha, very simple, just to see if it is marked with @Controller or @RequestMapping annotations

protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String) ?
                getApplicationContext().getType((String) handler) : handler.getClass();
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){
            public boolean matches(Method method) {//只选择被@RequestMapping标记的方法
                return getMappingForMethod(method, userType) != null;
            }
        });

        for (Method method : methods) {
            //根据方法上的@RequestMapping来创建RequestMappingInfo实例。
            T mapping = getMappingForMethod(method, userType);
            //注册请求映射
            registerHandlerMethod(handler, method, mapping);
        }
    }

 

   The whole detection process is generally clear: 1) Traverse all the methods in the Handler to find the methods marked by the @RequestMapping annotation. 2) Then traverse these methods to generate RequestMappingInfo instances. 3) Register the RequestMappingInfo instance and the handler method in the cache.

    Let's look at the details:

@Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType{
        RequestMappingInfo info = null;
        //获取方法method上的@RequestMapping实例。
        RequestMapping methodAnnotation = 
                                AnnotationUtils.findAnnotation(method, RequestMapping.class);
        if (methodAnnotation != null) {//方法被注解了
            RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始终返回null
            info = createRequestMappingInfo(methodAnnotation, methodCondition);//创建MappingInfo
            //检查方法所属的类有没有@RequestMapping注解
            RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, 
                                                                        RequestMapping.class);
            if (typeAnnotation != null) {//有类层次的@RequestMapping注解
                RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null
                //将类层次的RequestMapping和方法级别的RequestMapping结合
                info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
            }
        }
        return info;
    }

 

   It's very clear, first get the @RequestMapping information on the method, then get the @RequestMapping information on the class level, and then combine the two, here we need to understand how to create a RequestMappingInfo object (including its internal structure), and How to combine class-level request mapping information with method-level information?

private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation
                                                        RequestCondition<?> customCondition) {
    return new RequestMappingInfo(
         new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),
                 this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
         new RequestMethodsRequestCondition(annotation.method()),
         new ParamsRequestCondition(annotation.params()),
         new HeadersRequestCondition(annotation.headers()),
         new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
         new ProducesRequestCondition(annotation.produces(), annotation.headers(), 
                    getContentNegotiationManager()), 
        customCondition
    );
}

 

   There are several classes involved, and we have a general understanding of the meaning:

  • PatternRequestCondition  is actually an encapsulation of URL patterns, which contains a Set collection of URL patterns. In fact, the value in the @RequestMapping annotation is worth encapsulating.

  • RequestMethodRequestCondition  It is the encapsulation of the method attribute in the @RequestMapping annotation

  • ParamsRequestCondition  it is the encapsulation of the params attribute in the @RequestMapping annotation

    And so on, and so on. So RequestMappingInfo is actually the encapsulation of @RquestMapping.

    Let's take a look at how to combine:

public RequestMappingInfo combine(RequestMappingInfo other) {
    PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
    RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
    ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
    HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
    ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
    ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
    RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

    return new RequestMappingInfo(patterns, methods, params, headers, consumes, 
                    produces, custom.getCondition());
}

 

   It is very clear that the combine operation is performed on each element. Here we only look at how the PatternRequestCondition is combined, that is, how to combine the urls. Nothing else is necessary.

public PatternsRequestCondition combine(PatternsRequestCondition other) {
        Set<String> result = new LinkedHashSet<String>();
        if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
            for (String pattern1 : this.patterns) {
                for (String pattern2 : other.patterns) {
                    result.add(this.pathMatcher.combine(pattern1, pattern2));
                }
            }
        }
        else if (!this.patterns.isEmpty()) {
            result.addAll(this.patterns);
        }
        else if (!other.patterns.isEmpty()) {
            result.addAll(other.patterns);
        }
        else {
            result.add("");
        }
        return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, 
                   this.useSuffixPatternMatch,this.useTrailingSlashMatch, this.fileExtensions);
    }

 

    1) Both patterns exist, call the combine method of PathMatcher to merge the two patterns.

    2) Only one sometimes, use this.

    3) When there are none, it is empty "".

    Now the real url concatenation is done by PathMatcher. Let's not look at his code. It is a combination of if else. The point is to consider various situations. Let's take a look at the comments of the method:

    Be clear and comprehensive. If you are interested, you can take a look at the code. I won't talk about it here.

4. Registration request mapping

    We have talked about the detection and processing of @RequestMapping above, and generated a RequestMappingInfo instance based on @RequestMapping, so Spring must save this information to process our request.

    In the third section, we mentioned that a method has not been analyzed, that is, the registerHandlerMethod method:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        HandlerMethod handlerMethod;
        if (handler instanceof String) {
            String beanName = (String) handler;
            handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
        }
        else {
            handlerMethod = new HandlerMethod(handler, method);
        }
        //上面几行是根据新的处理器实例,方法实例,RequestMappingInfo来生成新的HandlerMethod实例
        //下面是从缓存中查看是否有存在的HandlerMethod实例,如果有并且不相等则抛出异常
        HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
        if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
            throw new IllegalStateException();
        }
        //handlerMethods 是一个Map键是RequestMappingInfo对象,值是HandlerMethod实例
        //因此一个HandlerMethod实例可能处理多个mapping,而一个mapping实例只能由一个method处理
        this.handlerMethods.put(mapping, handlerMethod);
        //这里获取mapping实例中的所有url。
        Set<String> patterns = getMappingPathPatterns(mapping);
        for (String pattern : patterns) {
            if (!getPathMatcher().isPattern(pattern)) {
                //urlMap也是Map,键是url 模式,值是RequestMappingInfo实例
                //因此一个mapping实例可能对应多个pattern,但是一个pattern只能对应一个mapping实例
                this.urlMap.add(pattern, mapping);
            }
        }
    }

 

   This may be a little confusing, but the reason is very simple. When the request arrives, you need to find the matching url in urlMap, get the corresponding mapping instance, and then go to handlerMethods to get the matching HandlerMethod instance.

5. Linking the previous and the next

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326480871&siteId=291194637