Spring MVC学習ノートのSpring MVCコンポーネントHandlerMapping(二)


1、はじめに
前の「Spring MVCコンポーネントHandlerMapping(一)」では、HandlerMappingコンポーネントの全体論理とAbstractUrlhandlerMappingシリーズの実現方法を分析した.このセクションでは、Abstracthandler MethodMappingシリーズの実現方法を分析します.
2、Abstract Handler MethodMappingシステム
Abstract Handler MethodMappingシステムにおいて、Abstracthandleingは三つの種類しかありません.それぞれAbstract Handler MethMapping、Request MappingInfoler Mapping、Request MappingHandlerMappingという三つの種類が順次前の種類に継承されています.InitializingBeanインターフェースを実現すると、Beaビンの実装後にafterPropertiesset()メソッドを呼び出します.
Abstracthandler MethodMappingシステムでは、クラスの階層構造は比較的簡単で明確ですが、SpringはAbstracthandlerMethodMappingシステムの柔軟性を保証するために、論理は複雑です.Abstract Handler MethMapping類を分析する前に、私達はまずその中のいくつかの核心の基礎種類を認識します.
  • HandlerMethodは、プロセッサに対応する方法およびインスタンスBeanを含む方法に基づくプロセッサをカプセル化し、いくつかのアクセス方法パラメータ、方法リターン値、方法注釈などの方法を提供する.
  • Request MappingInfoは要求情報を表し、マッピング関係の整合条件をカプセル化した.@Request Mappingの注釈を使う時、配置の情報は最後にすべてRequest MappingInfoに設定しました.@Request Mappingの注釈の異なる属性は対応するXREquest Coditionにマッピングされます.
  • Abstracthandler MethodMapping-内部種類MatchはHandlerMethodと汎型Tをカプセル化しました.汎型Tは実際にRequest MappingInfoを表しています.
  • 内部クラスMappingRegistrationは、マッピング関係登録時の情報を記録する.HandlerMethod、汎型T、mappingName、directUrls属性(保存urlとRequest MappingInfoの対応関係、複数のurlは同一のmappingInfoに対応しているかもしれません)
  • を実装しました.
  • 内部クラスのMappingRegistryは主にいくつかのMapを維持し、マッピングされた情報を格納するために使用されます.この内部クラスとその定義のマッピング関係を詳細に説明する.
    3、Abstract Handler MethodMapping抽象類
    上記のMatch、MappingRegistration、MappingRegistryはこのクラスにあります.その中でMappingRegistryが最も重要です.まずこの内部種類を分析します.これらの中でいくつかのMappingRegistryが含まれています.
    3.1、MappingRegistry内部類
     MappingRegistry内部類は主にT(Request MappingInfo)、mappingName、HandlerMethod、CorsCorsCofigrationなどの対象間のマッピング関係を維持し、同時に読み書きロックreadWriteLockの対象を提供しました.
    1、属性
    //  mapping MappingRegistration       
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
    //  mapping HandlerMethod   
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
    //   URL     (mapping)     ,key         URL,value      list    。
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
    //   name HandlerMethod     (  name     HandlerMethod)
    private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
    //  HandlerMethod        
    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
    //   
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    2、register()方法、unregister()方法は主にregister()方法、unregister()方法でT(Request MappingInfo)、mappingName、HandlerMethod、CorsCorsCorsConsCofigrationなどのオブジェクト間のマッピング関係を登録します.ここで、register()の方法コードは以下の通りである.
    public void register(T mapping, Object handler, Method method) {
         
    	// Assert that the handler method is not a suspending one.
    	if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
         
    		throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
    	}
    	//    
    	this.readWriteLock.writeLock().lock();
    	try {
         
    		//    HandlerMethod  (        )
    		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    		//  handlerMethod mapping     ,        HandlerMethod    handlerMethod   ,      “Ambiguous mapping. Cannot map”
    		validateMethodMapping(handlerMethod, mapping);
    		//  mappingLookup  ,  mapping handlerMethod     
    		this.mappingLookup.put(mapping, handlerMethod);
    		//      mapping,   URL(        URL), URL      getMappingPathPatterns()     ,      PatternsRequestCondition   ,   directUrls           ?       。
    		List<String> directUrls = getDirectUrls(mapping);
    		for (String url : directUrls) {
         
    			this.urlLookup.add(url, mapping);
    		}
    
    		String name = null;
    		//  mappingName,    handlerMethod   
    		if (getNamingStrategy() != null) {
         
    			//namingStrategy      RequestMappingInfoHandlerMethodMappingNamingStrategy  (    RequestMappingInfoHandlerMapping          ),    :         +“#”+   
    			name = getNamingStrategy().getName(handlerMethod, mapping);
    			addMappingName(name, handlerMethod);
    		}
    		//      CorsConfiguration  ,   handlerMethod   。initCorsConfiguration()     , RequestMappingHandlerMapping       。
    		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
    		if (corsConfig != null) {
         
    			this.corsLookup.put(handlerMethod, corsConfig);
    		}
    		//  mapping MappingRegistration   
    		this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    	}
    	finally {
         
    		this.readWriteLock.writeLock().unlock();
    	}
    }
    
    一方、unregister()方法は主にmapping整合条件を除去し、他のオブジェクトとのマッピング関係を行います.ここではコードを貼り付けません.
    3.2、初期化
    前に述べたAbstract HandlerMethodMappingはInitializingBeanインターフェースを実現していますので、Beanの実装後にafterPropertiessetを呼び出すことができます.実は、Abstract Handler MethodMapping類の初期化作業はこの場所から始まっています.コードは以下の通りです
    @Override
    public void afterPropertiesSet() {
         
    	initHandlerMethods();
    }
    
    protected void initHandlerMethods() {
         
    	//     bean  
    	for (String beanName : getCandidateBeanNames()) {
         
    		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         
    			//     bean  ,       
    			processCandidateBean(beanName);
    		}
    	}
    	//       ,         
    	handlerMethodsInitialized(getHandlerMethods());
    }
    
    afterPropertiesset()方法では、inithandler Methods()メソッドを呼び出し、実際の初期化ロジックはこの方法で実現される.まずget CanddateBenNames()法により、全ての候補のBeanインスタンスのnameを取得する.コードは以下の通りである.
    protected String[] getCandidateBeanNames() {
         
    	//detectHandlerMethodsInAncestorContexts             bean  
    	return (this.detectHandlerMethodsInAncestorContexts ?
    			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
    			obtainApplicationContext().getBeanNamesForType(Object.class));
    }
    
    その後、get Canda teBeabin Names()方法で取得したBeanのインスタンスを巡回して、processCadidateBean()方法を呼び出して処理します.コードは以下の通りです.
    protected void processCandidateBean(String beanName) {
         
    	Class<?> beanType = null;
    	try {
         
    		//     Class  
    		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        ,  isHandler()    ,    RequestMappingHandlerMapping    ,     Controller RequestMapping      
    	if (beanType != null && isHandler(beanType)) {
         
    		//    Bean          ,   mappingRegistry      Map     
    		detectHandlerMethods(beanName);
    	}
    }
    
    _;  processCadidateBean()方法では、beanNameに対応するBeanインスタンスがプロセッサタイプかどうかを判断し、そうであればdetectHandlerMethods()メソッドを呼び出し、この例に対応する処理方法を取得し、mappingRegistryで対応するMap関係に登録します.
    protected void detectHandlerMethods(Object handler) {
         
    	//        Class
    	Class<?> handlerType = (handler instanceof String ?
    			obtainApplicationContext().getType((String) handler) : handler.getClass());
    
    	if (handlerType != null) {
         
    		//            ,            ,    cglib       ,  cglib     ;
    		Class<?> userType = ClassUtils.getUserClass(handlerType);
    		//      userType      ,       getMappingForMethod()     
    		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));
    		}
    		//        ,  registerHandlerMethod()        
    		methods.forEach((method, mapping) -> {
         
    			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    			registerHandlerMethod(handler, invocableMethod, mapping);
    		});
    	}
    }
    
    _; 以来、初期化作業は完了しました.つまり、Mappingマッチング条件とHandlerMethodのマッピング関係を登録しました.
    3.3、get Handler Internal()方法
    前にAbstract Handler MethodMapping類の初期化方法を分析しましたが、今からこの種類がどのように親類のgetHandler Internalを実現するかを分析します.
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
         
    	//  lookupPath
    	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    	//  “org.springframework.web.servlet。HandlerMapping.lookupPath”  
    	request.setAttribute(LOOKUP_PATH, lookupPath);
    	//    
    	this.mappingRegistry.acquireReadLock();
    	try {
         
    		//  request      
    		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    		//         bean name,   createWithResolvedBean()  ,     Bean  
    		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    	}
    	finally {
         
    		this.mappingRegistry.releaseReadLock();
    	}
    }
    
       get Handler Internal()の方法では、lookuhandler Method()の方法は本当にプロセッサを検索する方法であることを知っています.コードは以下の通りです.
    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
         
    	List<Match> matches = new ArrayList<>();
    	// MappingRegistry.urlLookup   ,  lookupPath   mapping  
    	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    	if (directPathMatches != null) {
         
    		//     mapping,   matches  
    		addMatchingMappings(directPathMatches, matches, request);
    	}
    	if (matches.isEmpty()) {
         
    		//       lookupPath   ,      mapping,       mapping
    		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    	}
    
    	if (!matches.isEmpty()) {
         //         mapping,     
    		//          ,     getMappingComparator()    ,        
    		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
    		//  
    		matches.sort(comparator);
    		Match bestMatch = matches.get(0);
    		if (matches.size() > 1) {
         //       
    			if (logger.isTraceEnabled()) {
         
    				logger.trace(matches.size() + " matching mappings: " + matches);
    			}
    			if (CorsUtils.isPreFlightRequest(request)) {
         //OPTIONS   ,    
    				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 + "}");
    			}
    		}
    		//  “.bestMatchingHandler”  
    		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
    		//        ,         ".pathWithinHandlerMapping"  ,          。
    		handleMatch(bestMatch.mapping, lookupPath, request);
    		return bestMatch.handlerMethod;
    	}
    	else {
         //      bean ,  handleNoMatch()  ,   ,       。
    		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    	}
    }
    
    4、Request MappingInfoHandlerMapping抽象類
    Request MappingInfolerMapping抽象類はAbstractHandlerMethodMapping類から継承され、その中の汎型がRequest MappingInfoであることを明らかにしました.
    _; はRequest MappingInfoHandlerMapping抽象類において、以下のいくつかのことをした.
    1、Optionsのデフォルトの処置方法を定義していますが、HTTP_に関するものがあります.OPTIONHANDLE_METHOD定数、内部クラスHttpOptionshandler、および静的コードブロック初期化定数.
    2、コンストラクタは、コンストラクタにマッピングのネーミングポリシー、すなわちRequest MappingInfoHandlerMethodMappingNamingStrategyを設定して実現する方式です.
    3、親類のいくつかの方法を実現しました.
    //  RequestMappingInfo    URL  
    @Override
    protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
         
    	return info.getPatternsCondition().getPatterns();
    }
    //     RequestMappingInfo request    ,     RequestMappingInfo getMatchingCondition()      ,        RequestMappingInfo   
    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
         
    	return info.getMatchingCondition(request);
    }
    //     
    @Override
    protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
         
    	return (info1, info2) -> info1.compareTo(info2, request);
    }
    
    4、父親類のハンドルマッチ()方法は、この方法には主に処理変数(テンプレート変数とマトリックス変数)とメディアタイプの論理が追加されています.次にパラメータ処理の過程を詳しく分析します.
    5、親類を書き換えたhandleNoMatch()方法では、内部クラスのPartar Match Helperを使用して不一致の原因を判断し、指定された異常を投げる.コードを貼り付けていません.
    5、Request MappingHandlerMapping類
    レイクマットMappling HandlerMapping類は、Request MappingInfo HandlerMappingを継承したほか、Match able HandlerMappingとEmbededValueResourAwareの2つのインターフェースを実現しました.この中で、Match atleHandlerMappingインターフェースを実現する方法は、まだ使われていません.EmbodValue ResourAwareインターフェースを実現しました.解析String文字列をサポートするということです.
    定義された属性:
    //        
    private boolean useSuffixPatternMatch = true;
    //                ContentNegotiationManager          。
    private boolean useRegisteredSuffixPatternMatch = false;
    //      
    private boolean useTrailingSlashMatch = true;
    //        path
    private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
    //       
    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
    //      ,  spring   
    @Nullable
    private StringValueResolver embeddedValueResolver;
    //RequestMappingInfo    
    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
    
    afterPropertiesset()方法
    前に、afterPropertiesset()方法が初期化を実現する方法であることを知っています.Abstracthandler MethodMappingの抽象的な種類の中で、hander種類と方法の検査と登録を実現しました.Request MappingHandlerMapping類では、Request MappingInfo構築構成構成の初期化が追加されました.コードは以下の通りです.
    @Override
    public void afterPropertiesSet() {
         
    	this.config = new RequestMappingInfo.BuilderConfiguration();
    	this.config.setUrlPathHelper(getUrlPathHelper());
    	this.config.setPathMatcher(getPathMatcher());
    	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    	this.config.setContentNegotiationManager(getContentNegotiationManager());
    
    	super.afterPropertiesSet();
    }
    
    ishandler()メソッド  はAbstracthandler MethodMapping類において初期化を行う場合、processCadidateBen()メソッドにishandlerを使用して現在のbeanインスタンスがプロセッサかどうかを判断します.実際の判断ロジックはここで実現されます.
    @Override
    protected boolean isHandler(Class<?> beanType) {
         
    	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
    			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
    
    get MappingForMethodメソッド  AbstractHandler MethodMappingクラスでは、初期化を行う際に、detectHandlerMethods()メソッドにおいて、この方法を呼び出してプロセッサ方法の判断を実現します.
    @Override
    @Nullable
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
         
    	RequestMappingInfo info = createRequestMappingInfo(method);
    	if (info != null) {
         
    		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
    		if (typeInfo != null) {
         
    			info = typeInfo.combine(info);
    		}
    		String prefix = getPathPrefix(handlerType);
    		if (prefix != null) {
         
    			info = RequestMappingInfo.paths(prefix).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();
    }
    
    その他の方法は、レギターMapping()、レギターHandlerMethod()の方法の2つの方法であり、父の種類のメソッドを呼び出す以外に、主にConsmesRequest Coditionの判断条件を設定している.今後はクロスドメイン関連のパラメータも配置されていますが、ここでは詳しく分析していません.
    6、まとめ
    この文章の中で、私達はAbstracthandlerMethMappingシステムが実現したHandlerMappingの中の3つの種類の基本的な実現を分析しただけです.その中にRequest CoditionとRequest MappingInfo(Request Coditionのサブクラスでもあります.)やHandleMethodなどの具体的な学習過程が含まれています.