• Spring源码从入门到放弃-Controller注册


    Spring源码从入门到放弃-Controller注册

    {toc}

    @contact:zhangxin@benmu-health.com
    @update:2017-03-23 02:18:31
    @spirng.version:4.3.7.RELEASE

    本文主要介绍SpringMVC中如何注册Controller

    SpringMVC中Controller由@Controller和@RequestMapping注解定义,@Controller定义对象为一个Controller,@RequestMapping定义了请求url路径,SpringMVC内部将Controller的方法抽象为多个org.springframework.web.method.HandlerMethod,将Method的@RequestMapping注解抽象成org.springframework.web.servlet.mvc.method.RequestMappingInfo,一个到来的http请求,经过DispatcherServlet转发,通过RequestMappingInfo匹配路径,找到对应的HandlerMethod处理请求。这里的HandlerMethod可以理解为Controller的一个方法。

    1.RequestMappingHandlerMapping

    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping类的初始化过程完成了对@Controller和@RequestMapping两个注解的解析该类由spring容器初始化过程解析。解析<mvc:annotation-driven />标签时会自动向spring容器注册该类。并在DispatcherServlet初始化的时候,在initHandlerMappings()方法中会从Spring容器中将该HandlerMapping作为DispatcherServlet的成员,用以处理http请求。

    继承关系:

    该类实现了HandlerMapping和InitializingBean两个接口,初始化方法afterPropertiesSet()完成了对@Controller和@RequestMapping的解析和注册。

    2.afterPropertiesSet

    Controller注册是在初始化方法afterPropertiesSet中,首先拿到Spring容器中所有的Bean,对每一个Bean判断是否为Controller

    	@Override
    	public void afterPropertiesSet() {
    		initHandlerMethods();
    	}
    
    	/**
    	 * Scan beans in the ApplicationContext, detect and register handler methods.
    	 * @see #isHandler(Class)
    	 * @see #getMappingForMethod(Method, Class)
    	 * @see #handlerMethodsInitialized(Map)
    	 */
    	protected void initHandlerMethods() {
    	//拿到所有bean的名字
    		String[] beanNames = getApplicationContext().getBeanNamesForType(Object.class));
    
    		for (String beanName : beanNames) {
    				Class<?> beanType = null;
    				//拿到Class
    				beanType = getApplicationContext().getType(beanName);
    				
    				//如果改bean带有@Controller和@RequestMapping注解
    				if (beanType != null && isHandler(beanType)) {
    					//注册hanler mapping即Controller
    					detectHandlerMethods(beanName);
    				}
    		}
    	
    	}
    
    

    3.解析RequestMappingInfo

    detectHandlerMethods完成对一个Controller的解析,将@RequestMapping方法解析成映射和可执行的HandlerMethod,映射抽象为RequestMappingInfo(即url pattern),将可执行的HandlerMethod和RequestMappingInfo一起注册到MappingRegistry中,DispatcherServlet收到一个请求的时候会从MappingRegistry中取出与url匹配的handler method来执行。

    protected void detectHandlerMethods(final Object handler) {
            Class<?> handlerType = (handler instanceof String ?
                    getApplicationContext().getType((String) handler) : handler.getClass());
            //拿到用户实际注册的类,防止CGLIB代理
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
    
            //选出该类打@RequestMapping的方法,并转成Map<Method,RequestMappingInfo>
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    new MethodIntrospector.MetadataLookup<T>() {
                        @Override
                        public T inspect(Method method) {
                            //对每一个method,转成RequestMappingInfo,如果不带@RequestMapping注解则返回null
                            return getMappingForMethod(method, userType);
                        }
                    });
    
            for (Map.Entry<Method, T> entry : methods.entrySet()) {
                //包装成一个可执行方法
                Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                //实际为RequestMappingInfo
                T mapping = entry.getValue();
                //将RequestMappingInfo和handler注册到MappingRegistry
                //DispatcherServlet收到一个请求的时候会从MappingRegistry中取出与url匹配的handler来执行
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
        }
    
    

    getMappingForMethod()方法中完成了将带有@RequestMapping注解的方法转为RequestMappingInfo。
    分别将Class和Method上的@RequestMapping拿到,用属性生成RequestMappingInfo。然后将两个RequestMappingInfo合并成一个。e.g. Class上的注解为path=/test,Method上的注解为path=/hello,method=POST,合并之后就是path=/test/hello,method=POST,并且为每一个RequestMappingInfo生成一个PatternsRequestCondition,用来完成DispatchServlet分发请求时url匹配。

    @Override
    	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    		//解析method的@RequestMapping
    		RequestMappingInfo info = createRequestMappingInfo(method);
    		if (info != null) {
    			//解析Class的@RequestMapping
    			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
    			if (typeInfo != null) {
    				//将两个@RequestMapping合并
    				info = typeInfo.combine(info);
    			}
    		}
    		return info;
    	}
    	
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    		//拿到注解
    		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    		return (requestMapping != null ? createRequestMappingInfo(requestMapping, null) : null);
    	}
    
    protected RequestMappingInfo createRequestMappingInfo(
    			RequestMapping requestMapping, RequestCondition<?> customCondition) {
    
    		//用@RequestMapping的属性生成RequestMappingInfo
    		return RequestMappingInfo
    				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))//e.g. /test
    				.methods(requestMapping.method())//e.g. POST
    				.params(requestMapping.params())
    				.headers(requestMapping.headers())
    				.consumes(requestMapping.consumes())
    				.produces(requestMapping.produces())
    				.mappingName(requestMapping.name())
    				.customCondition(customCondition)
    				.options(this.config)
    				.build();
    	}
    
    @Override
    		public RequestMappingInfo build() {
    			ContentNegotiationManager manager = this.options.getContentNegotiationManager();
    
    			//生成路径匹配类,DispatcherServlet中分发url请求时调用
    			PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
    					this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
    					this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
    					this.options.getFileExtensions());
    
    			return new RequestMappingInfo(this.mappingName, patternsCondition,
    					new RequestMethodsRequestCondition(methods),
    					new ParamsRequestCondition(this.params),
    					new HeadersRequestCondition(this.headers),
    					new ConsumesRequestCondition(this.consumes, this.headers),
    					new ProducesRequestCondition(this.produces, this.headers, manager),
    					this.customCondition);
    		}
    

    4.RequestMappingInfo与handler注册

    handler最终会被封装成HandlerMethod
    RequestMappingInfo与HandlerMethod都注册到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry中,MappingRegistry有两个属性,Map<RequestMappingInfo, HandlerMethod>Map<url, HandlerMethod>,维护了路径和HandlerMethod的关系。注册@Controller即生成这两个Map。

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    		this.mappingRegistry.register(mapping, handler, method);
    	}
    
    public void register(T mapping, Object handler, Method method) {
                this.readWriteLock.writeLock().lock();
                try {
                    //通过Bean和Method,抽象成可执行的HandlerMethod,即Controller的带有@RequestMapping注解的Method
                    HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    
                    //注册Map<RequestMappingInfo, HandlerMethod>
                    this.mappingLookup.put(mapping, handlerMethod);
    
                    List<String> directUrls = getDirectUrls(mapping);
                    for (String url : directUrls) {
                        //注册Map<url, HandlerMethod>
                        this.urlLookup.add(url, mapping);
                    }
    
                    //http跨域配置
                    CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                    if (corsConfig != null) {
                        this.corsLookup.put(handlerMethod, corsConfig);
                    }
    
                    //注册Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>>
                    this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
                } finally {
                    this.readWriteLock.writeLock().unlock();
                }
            }
    
    

    5.DispatcherServlet与MappingRegistry

    这里顺带提一下DispatcherServlet如何找到处理当前Http Request的HandlerMethod,最终http请求由匹配到的HandlerMethod来处理。

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
            List<Match> matches = new ArrayList<Match>();
            //从Map<url, HandlerMethod>中找
            List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
            if (directPathMatches != null) {
                addMatchingMappings(directPathMatches, matches, request);
            }
            if (matches.isEmpty()) {
                //从所有的RequestMappingInfo中找
                addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
            }
    
            if (!matches.isEmpty()) {
                //本质为Comparator<RequestMappingInfo>
                Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
                Collections.sort(matches, comparator);
                //选出最匹配当前Request的RequestMappingInfo
                Match bestMatch = matches.get(0);
                if (matches.size() > 1) {
                    Match secondBestMatch = matches.get(1);
                    if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                        //校验
                        Method m1 = bestMatch.handlerMethod.getMethod();
                        Method m2 = secondBestMatch.handlerMethod.getMethod();
                        throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                                request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                    }
                }
                //处理url上的template variables, matrix variables
                handleMatch(bestMatch.mapping, lookupPath, request);
                
                //拿到handlerMethod
                return bestMatch.handlerMethod;
            } else {
                return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
            }
        }
    
  • 相关阅读:
    SQL Server DB 基于多核CPU的设置
    如何在64位的Windows 2008的系统配置导入Excel功能
    如果在IIS中没有将虚拟目录配置为应用程序,则可能导致此错误
    .NET 数据绑定中空格符的问题
    .net 读取客户端文件的方法
    spring4+quartz
    web端 图片上传
    Maven配置setting.xml详细说明
    Timer
    实现quartz定时器及quartz定时器原理介绍
  • 原文地址:https://www.cnblogs.com/kindevil-zx/p/6603154.html
Copyright © 2020-2023  润新知