欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解SpringMVC組件之HandlerMapping(二)

 更新時間:2023年08月31日 11:02:10   作者:姠惢荇者  
這篇文章主要介紹了詳解SpringMVC組件之HandlerMapping(二),HandlerMapping組件是Spring?MVC核心組件,用來根據請求的request查找對應的Handler,在Spring?MVC中,有各式各樣的Web請求,每個請求都需要一個對應的Handler來處理,需要的朋友可以參考下

1、前言

在前面《詳解SpringMVC組件之HandlerMapping(一)》中,我們分析了HandlerMapping組件的整體邏輯及其AbstractUrlHandlerMapping系列的實現方式。

這一節(jié),我們將分析AbstractHandlerMethodMapping系列的實現方式。

2、AbstractHandlerMethodMapping體系

在AbstractHandlerMethodMapping體系中,只有三個類,分別是

  1. AbstractHandlerMethodMapping
  2. RequestMappingInfoHandlerMapping
  3. RequestMappingHandlerMapping

這三個類依次繼承于前面的類,而最前面的AbstractHandlerMethodMapping抽象類則繼承于AbstractHandlerMapping抽象類,并實現了InitializingBean接口,實現了InitializingBean接口后,就會在Bean實例化后調用afterPropertiesSet()方法。

在AbstractHandlerMethodMapping體系中,類的層級結構比較簡單明確,但是Spring為了保證AbstractHandlerMethodMapping體系的靈活性,邏輯還是比較復雜的。

在分析AbstractHandlerMethodMapping類之前,我們先認識一下其中的幾個核心的基礎類。

  • HandlerMethod
    • 一個基于方法的處理器,包括了該處理器對應的方法和實例Bean,并提供了一些訪問方法參數、方法返回值、方法注解等方法。
  • RequestMappingInfo
    • 表示請求信息,并封裝了映射關系的匹配條件。使用@RequestMapping注解時,配置的信息最后都設置到了RequestMappingInfo中,@RequestMapping注解的不同屬性,會映射到對應的XXXRequestCondition上。
  • AbstractHandlerMethodMapping-內部類Match
    • 封裝了HandlerMethod和泛型類T。泛型類T實際上表示了RequestMappingInfo。
  • 內部類MappingRegistration
    • 記錄映射關系注冊時的信息。封裝了HandlerMethod、泛型類T、mappingName、directUrls屬性(保存url和RequestMappingInfo對應關系,多個url可能對應著同一個mappingInfo)
  • 內部類MappingRegistry
    • 主要維護幾個Map,用來存儲映射的信息。下面詳細介紹該內部類及其定義的映射關系

3、AbstractHandlerMethodMapping抽象類

在AbstractHandlerMethodMapping抽象類中定義了很多個內部類,前面提到的Match、MappingRegistration、MappingRegistry均是在該類中,其中,又以MappingRegistry最重要,下面我們首先分析這個內部類,因為其中涉及到的幾個Map屬性,是維護request與處理器Handler間關系的核心所在。

3.1、MappingRegistry內部類

MappingRegistry內部類主要維護了T(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等對象間的映射關系,同時提供了一個讀寫鎖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(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等對象間的映射關系。

其中,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 {
		//創(chuàng)建一個HandlerMethod對象(構造函數創(chuàng)建對象)
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		//驗證handlerMethod和mapping的對應關系,如果已經存在一個HandlerMethod對象且與handlerMethod不一樣,就會拋出異?!癆mbiguous mapping. Cannot map”
		validateMethodMapping(handlerMethod, mapping);
		//維護mappingLookup對象,建立mapping和handlerMethod的映射關系
		this.mappingLookup.put(mapping, handlerMethod);
		//獲取匹配條件mapping,對應的URL(那些不含通配符的URL),且URL是由子類實現getMappingPathPatterns()方法提供的,實際上還是由PatternsRequestCondition提供的,那個該directUrls 是什么時候初始化的呢?我們后續(xù)在分析。
		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構造函數中進行了設置),命名規(guī)則:類名中全部大小字符+“#”+方法名
			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、初始化

在前面我們提到了AbstractHandlerMethodMapping實現了InitializingBean接口,所以就會在Bean實例化后調用afterPropertiesSet()方法。

其實,AbstractHandlerMethodMapping類的初始化工作也就是從這個地方開始的。

代碼如下:

@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}
protected void initHandlerMethods() {
	//獲取候選的bean實例
	for (String beanName : getCandidateBeanNames()) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			//遍歷后續(xù)的bean實例,并分別進行處理
			processCandidateBean(beanName);
		}
	}
	//只是打印了日志,沒有實際的處理邏輯
	handlerMethodsInitialized(getHandlerMethods());
}

在afterPropertiesSet()方法中,調用了initHandlerMethods()方法,實際初始化邏輯就是在該方法中實現的。首先通過getCandidateBeanNames()方法獲取所有候選的Bean實例的name,代碼如下:

protected String[] getCandidateBeanNames() {
	//detectHandlerMethodsInAncestorContexts 表示是否從祖先容器中查找bean實例
	return (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
			obtainApplicationContext().getBeanNamesForType(Object.class));
}

然后,遍歷通過getCandidateBeanNames()方法獲取的Bean實例,調用processCandidateBean()方法進行處理,代碼如下:

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);
	}
}

在processCandidateBean()方法中,判斷beanName對應的Bean實例是否是處理器類型(isHandler方法判斷),如果是的話,則調用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、getHandlerInternal()方法

在前面分析了AbstractHandlerMethodMapping類的初始化方法,現在我們開始分析該類如何實現父類的getHandlerInternal()方法,即如何通過request獲取對應的處理器HandlerMethod對象。

@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()方法,創(chuàng)建對應的Bean實例
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

在getHandlerInternal()方法中,我們知道lookupHandlerMethod()方法是真正實現查找處理器的方法,代碼如下:

@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、RequestMappingInfoHandlerMapping抽象類

RequestMappingInfoHandlerMapping抽象類繼承自AbstractHandlerMethodMapping類,并明確了其中的泛型類為RequestMappingInfo。

在RequestMappingInfoHandlerMapping抽象類中做了以下幾件事:

1、定義了Options的默認處置方法,其中涉及到了HTTP_OPTIONS_HANDLE_METHOD常量、內部類HttpOptionsHandler和靜態(tài)代碼塊初始化常量。

2、構造函數,在構造函數中設置了映射的命名策略,即RequestMappingInfoHandlerMethodMappingNamingStrategy實現的方式。

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、重新父類的handleMatch()方法,在該方法中主要添加了處理變量(模板變量和矩陣變量(MatrixVariable))和媒體類型的邏輯。后續(xù)再詳細分析參數處理的過程。

5、重寫父類的handleNoMatch()方法,該方法中使用了內部類PartialMatchHelper來判斷不匹配的原因,然后拋出指定的異常。不在貼出代碼。

5、RequestMappingHandlerMapping類

RequestMappingHandlerMapping類除了繼承了RequestMappingInfoHandlerMapping之外,還實現了MatchableHandlerMapping和EmbeddedValueResolverAware兩個接口。

其中,實現MatchableHandlerMapping接口的方法,暫時未使用;而實現了EmbeddedValueResolverAware接口,說明要支持解析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()方法是實現初始化的方法。在AbstractHandlerMethodMapping抽象類中,實現了handler類和方法的檢測和注冊。在RequestMappingHandlerMapping類中,又增加了RequestMappingInfo構建配置的初始化,代碼如下:

@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()方法

在AbstractHandlerMethodMapping類中,進行初始化的時候,在processCandidateBean()方法中使用了isHandler判斷當前bean實例是否是處理器。實際判斷邏輯在這里實現的。

@Override
protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

getMappingForMethod方法

在AbstractHandlerMethodMapping類中,進行初始化的時候,在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();
}

其他方法 在registerMapping()、registerHandlerMethod()方法這兩個方法,除了調用父類的方法之外, 主要是設置了ConsumesRequestCondition的判斷條件。

后續(xù)還有配置跨域相關參數,這里不在詳細分析了。

6、總結

在這篇文章中,我們只是分析了AbstractHandlerMethodMapping體系實現的HandlerMapping中這三個類的基本實現,其中涉及到的RequestCondition及RequestMappingInfo(也是RequestCondition的子類)還有HandlerMethod等類的具體介紹,我們在后續(xù)過程中在逐漸的學習記錄。

到此這篇關于詳解SpringMVC組件之HandlerMapping(二)的文章就介紹到這了,更多相關SpringMVC的HandlerMapping內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java面向對象設計原則之單一職責與依賴倒置原則詳解

    java面向對象設計原則之單一職責與依賴倒置原則詳解

    這篇文章主要介紹了java面向對象設計原則之單一職責與依賴倒置原則的分析詳解,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進步早日升職加薪
    2021-10-10
  • java application maven項目打自定義zip包實例(推薦)

    java application maven項目打自定義zip包實例(推薦)

    下面小編就為大家?guī)硪黄猨ava application maven項目打自定義zip包實例(推薦)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • Spring Bean的定義概念和使用

    Spring Bean的定義概念和使用

    這篇文章主要介紹了Spring Bean的定義概念和使用,Spring bean對象是構成應用程序的支柱,也是由Spring IoC容器管理的。bean是一個被實例化,組裝,并通過Spring IoC容器所管理的對象。這些bean是由用容器提供的配置元數據創(chuàng)建的
    2023-04-04
  • IDEA添加Java類注釋模版的方法

    IDEA添加Java類注釋模版的方法

    本篇文章主要介紹了IDEA添加Java類注釋模版的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • Mybatis之如何攔截慢SQL日志記錄

    Mybatis之如何攔截慢SQL日志記錄

    這篇文章主要介紹了Mybatis之如何攔截慢SQL日志記錄問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • SSH框架網上商城項目第17戰(zhàn)之購物車基本功能

    SSH框架網上商城項目第17戰(zhàn)之購物車基本功能

    這篇文章主要為大家詳細介紹了SSH框架網上商城項目第17戰(zhàn)之購物車基本功能的實現過程,感興趣的小伙伴們可以參考一下
    2016-06-06
  • 詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟

    詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟

    本文就在項目中來集成 UidGenerator這一工程來作為項目的全局唯一 ID生成器。接下來通過實例代碼給大家詳解詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟,感興趣的朋友一起看看吧
    2018-10-10
  • Java實現基于UDP協議的網絡通信UDP編程

    Java實現基于UDP協議的網絡通信UDP編程

    在Java中使用UDP編程,仍然需要使用Socket,因為應用程序在使用UDP時必須指定網絡接口(IP地址)和端口號。注意:UDP端口和TCP端口雖然都使用0~65535,但他們是兩套獨立的端口,即一個應用程序用TCP占用了端口1234,不影響另一個應用程序用UDP占用端口1234
    2023-04-04
  • Java數據結構之紅黑樹的實現方法和原理詳解

    Java數據結構之紅黑樹的實現方法和原理詳解

    這篇文章主要介紹了Java數據結構之紅黑樹的實現方法和原理,紅黑樹是一種特殊的二叉查找樹,每個結點都要儲存位表示結點的顏色,或紅或黑,本文將通過示例為大家詳細講講紅黑樹的原理及實現,感興趣的朋友可以了解一下
    2024-02-02
  • Spring Boot應用事件監(jiān)聽示例詳解

    Spring Boot應用事件監(jiān)聽示例詳解

    這篇文章主要給大家介紹了關于Spring Boot應用事件監(jiān)聽的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-12-12

最新評論