詳解SpringMVC組件之HandlerMapping(二)
1、前言
在前面《詳解SpringMVC組件之HandlerMapping(一)》中,我們分析了HandlerMapping組件的整體邏輯及其AbstractUrlHandlerMapping系列的實(shí)現(xiàn)方式。
這一節(jié),我們將分析AbstractHandlerMethodMapping系列的實(shí)現(xiàn)方式。
2、AbstractHandlerMethodMapping體系
在AbstractHandlerMethodMapping體系中,只有三個(gè)類,分別是
- AbstractHandlerMethodMapping
- RequestMappingInfoHandlerMapping
- RequestMappingHandlerMapping
這三個(gè)類依次繼承于前面的類,而最前面的AbstractHandlerMethodMapping抽象類則繼承于AbstractHandlerMapping抽象類,并實(shí)現(xiàn)了InitializingBean接口,實(shí)現(xiàn)了InitializingBean接口后,就會(huì)在Bean實(shí)例化后調(diào)用afterPropertiesSet()方法。
在AbstractHandlerMethodMapping體系中,類的層級(jí)結(jié)構(gòu)比較簡單明確,但是Spring為了保證AbstractHandlerMethodMapping體系的靈活性,邏輯還是比較復(fù)雜的。
在分析AbstractHandlerMethodMapping類之前,我們先認(rèn)識(shí)一下其中的幾個(gè)核心的基礎(chǔ)類。
- HandlerMethod
- 一個(gè)基于方法的處理器,包括了該處理器對(duì)應(yīng)的方法和實(shí)例Bean,并提供了一些訪問方法參數(shù)、方法返回值、方法注解等方法。
- RequestMappingInfo
- 表示請(qǐng)求信息,并封裝了映射關(guān)系的匹配條件。使用@RequestMapping注解時(shí),配置的信息最后都設(shè)置到了RequestMappingInfo中,@RequestMapping注解的不同屬性,會(huì)映射到對(duì)應(yīng)的XXXRequestCondition上。
- AbstractHandlerMethodMapping-內(nèi)部類Match
- 封裝了HandlerMethod和泛型類T。泛型類T實(shí)際上表示了RequestMappingInfo。
- 內(nèi)部類MappingRegistration
- 記錄映射關(guān)系注冊(cè)時(shí)的信息。封裝了HandlerMethod、泛型類T、mappingName、directUrls屬性(保存url和RequestMappingInfo對(duì)應(yīng)關(guān)系,多個(gè)url可能對(duì)應(yīng)著同一個(gè)mappingInfo)
- 內(nèi)部類MappingRegistry
- 主要維護(hù)幾個(gè)Map,用來存儲(chǔ)映射的信息。下面詳細(xì)介紹該內(nèi)部類及其定義的映射關(guān)系
3、AbstractHandlerMethodMapping抽象類
在AbstractHandlerMethodMapping抽象類中定義了很多個(gè)內(nèi)部類,前面提到的Match、MappingRegistration、MappingRegistry均是在該類中,其中,又以MappingRegistry最重要,下面我們首先分析這個(gè)內(nèi)部類,因?yàn)槠渲猩婕暗降膸讉€(gè)Map屬性,是維護(hù)request與處理器Handler間關(guān)系的核心所在。
3.1、MappingRegistry內(nèi)部類
MappingRegistry內(nèi)部類主要維護(hù)了T(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等對(duì)象間的映射關(guān)系,同時(shí)提供了一個(gè)讀寫鎖readWriteLock對(duì)象。
1、屬性
//維護(hù)mapping與MappingRegistration注冊(cè)信息的關(guān)系 private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); //維護(hù)mapping與HandlerMethod的關(guān)系 private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); //保存著URL與匹配條件(mapping)的對(duì)應(yīng)關(guān)系,key是那些不含通配符的URL,value對(duì)應(yīng)的是一個(gè)list類型的值。 private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); //保存著name和HandlerMethod的對(duì)應(yīng)關(guān)系(一個(gè)name可以有多個(gè)HandlerMethod) private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); //保存HandlerMethod與跨域配置的關(guān)系 private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); //讀寫鎖 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
2、register()方法、unregister()方法 在register()方法、unregister()方法中主要是用來注冊(cè)T(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等對(duì)象間的映射關(guān)系。
其中,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)建一個(gè)HandlerMethod對(duì)象(構(gòu)造函數(shù)創(chuàng)建對(duì)象) HandlerMethod handlerMethod = createHandlerMethod(handler, method); //驗(yàn)證handlerMethod和mapping的對(duì)應(yīng)關(guān)系,如果已經(jīng)存在一個(gè)HandlerMethod對(duì)象且與handlerMethod不一樣,就會(huì)拋出異常“Ambiguous mapping. Cannot map” validateMethodMapping(handlerMethod, mapping); //維護(hù)mappingLookup對(duì)象,建立mapping和handlerMethod的映射關(guān)系 this.mappingLookup.put(mapping, handlerMethod); //獲取匹配條件mapping,對(duì)應(yīng)的URL(那些不含通配符的URL),且URL是由子類實(shí)現(xiàn)getMappingPathPatterns()方法提供的,實(shí)際上還是由PatternsRequestCondition提供的,那個(gè)該directUrls 是什么時(shí)候初始化的呢?我們后續(xù)在分析。 List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } String name = null; //獲取mappingName,并維護(hù)與handlerMethod的關(guān)系 if (getNamingStrategy() != null) { //namingStrategy屬性對(duì)應(yīng)的是RequestMappingInfoHandlerMethodMappingNamingStrategy對(duì)象(是在子類RequestMappingInfoHandlerMapping構(gòu)造函數(shù)中進(jìn)行了設(shè)置),命名規(guī)則:類名中全部大小字符+“#”+方法名 name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } //獲取跨域配置CorsConfiguration對(duì)象,維護(hù)與handlerMethod的關(guān)系。initCorsConfiguration()定了空方法,在RequestMappingHandlerMapping類中進(jìn)行了重寫。 CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } //維護(hù)mapping與MappingRegistration的關(guān)系 this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
而unregister()方法主要是移除mapping匹配條件與其他對(duì)象的映射關(guān)系,這里不在貼出代碼了。
3.2、初始化
在前面我們提到了AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口,所以就會(huì)在Bean實(shí)例化后調(diào)用afterPropertiesSet()方法。
其實(shí),AbstractHandlerMethodMapping類的初始化工作也就是從這個(gè)地方開始的。
代碼如下:
@Override public void afterPropertiesSet() { initHandlerMethods(); } protected void initHandlerMethods() { //獲取候選的bean實(shí)例 for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { //遍歷后續(xù)的bean實(shí)例,并分別進(jìn)行處理 processCandidateBean(beanName); } } //只是打印了日志,沒有實(shí)際的處理邏輯 handlerMethodsInitialized(getHandlerMethods()); }
在afterPropertiesSet()方法中,調(diào)用了initHandlerMethods()方法,實(shí)際初始化邏輯就是在該方法中實(shí)現(xiàn)的。首先通過getCandidateBeanNames()方法獲取所有候選的Bean實(shí)例的name,代碼如下:
protected String[] getCandidateBeanNames() { //detectHandlerMethodsInAncestorContexts 表示是否從祖先容器中查找bean實(shí)例 return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); }
然后,遍歷通過getCandidateBeanNames()方法獲取的Bean實(shí)例,調(diào)用processCandidateBean()方法進(jìn)行處理,代碼如下:
protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { //獲取對(duì)應(yīng)的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類中實(shí)現(xiàn),根據(jù)是否有Controller或RequestMapping注解進(jìn)行判斷 if (beanType != null && isHandler(beanType)) { //獲取指定Bean實(shí)例中對(duì)應(yīng)的處理方法,并通過mappingRegistry注冊(cè)到對(duì)應(yīng)的Map關(guān)系映射中 detectHandlerMethods(beanName); } }
在processCandidateBean()方法中,判斷beanName對(duì)應(yīng)的Bean實(shí)例是否是處理器類型(isHandler方法判斷),如果是的話,則調(diào)用detectHandlerMethods()方法,獲取該實(shí)例中對(duì)應(yīng)的處理方法,并通過mappingRegistry注冊(cè)到對(duì)應(yīng)的Map關(guān)系映射中,代碼如下:
protected void detectHandlerMethods(Object handler) { //獲取處理器對(duì)應(yīng)的Class Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { //獲取用戶定義的本來的類型,大部分情況下就是類型本身,主要針對(duì)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()注冊(cè)到映射關(guān)系中 methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
自此,初始化工作就完成了,即注冊(cè)了mapping匹配條件與HandlerMethod的映射關(guān)系。
3.3、getHandlerInternal()方法
在前面分析了AbstractHandlerMethodMapping類的初始化方法,現(xiàn)在我們開始分析該類如何實(shí)現(xiàn)父類的getHandlerInternal()方法,即如何通過request獲取對(duì)應(yīng)的處理器HandlerMethod對(duì)象。
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //獲取lookupPath String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //設(shè)置“org.springframework.web.servlet。HandlerMapping.lookupPath”參數(shù) request.setAttribute(LOOKUP_PATH, lookupPath); //獲取讀鎖 this.mappingRegistry.acquireReadLock(); try { //獲取request對(duì)應(yīng)的處理器 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //如果處理器對(duì)應(yīng)的是bean name,則調(diào)用createWithResolvedBean()方法,創(chuàng)建對(duì)應(yīng)的Bean實(shí)例 return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }
在getHandlerInternal()方法中,我們知道lookupHandlerMethod()方法是真正實(shí)現(xiàn)查找處理器的方法,代碼如下:
@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); //從MappingRegistry.urlLookup屬性中,獲取lookupPath對(duì)應(yīng)的mapping集合 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { //獲取匹配的mapping,添加到matches變種 addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // 如果沒有匹配lookupPath的實(shí)例,則遍歷所有的mapping,查找符合條件的mapping addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) {//說明存在符合條件的mapping,可能是多個(gè) //獲取匹配條件的排序器,由抽象方法getMappingComparator()方法獲取,該方法由子類實(shí)現(xiàn) Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); //排序 matches.sort(comparator); Match bestMatch = matches.get(0); if (matches.size() > 1) {//不止一個(gè)匹配時(shí) if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) {//OPTIONS請(qǐng)求時(shí),直接處理 return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); //如果存在多個(gè)處理器,則直接拋出異常 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); //處理匹配的處理器,這里只是添加了一個(gè)".pathWithinHandlerMapping"屬性,具體實(shí)現(xiàn)在子類中進(jìn)行。 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else {//不存在匹配的bean時(shí),調(diào)用handleNoMatch()方法,空方法,有子類進(jìn)行實(shí)現(xiàn)。 return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
4、RequestMappingInfoHandlerMapping抽象類
RequestMappingInfoHandlerMapping抽象類繼承自AbstractHandlerMethodMapping類,并明確了其中的泛型類為RequestMappingInfo。
在RequestMappingInfoHandlerMapping抽象類中做了以下幾件事:
1、定義了Options的默認(rèn)處置方法,其中涉及到了HTTP_OPTIONS_HANDLE_METHOD常量、內(nèi)部類HttpOptionsHandler和靜態(tài)代碼塊初始化常量。
2、構(gòu)造函數(shù),在構(gòu)造函數(shù)中設(shè)置了映射的命名策略,即RequestMappingInfoHandlerMethodMappingNamingStrategy實(shí)現(xiàn)的方式。
3、實(shí)現(xiàn)了父類中的幾個(gè)方法,如下所示:
//獲取RequestMappingInfo 對(duì)應(yīng)的URL集合 @Override protected Set<String> getMappingPathPatterns(RequestMappingInfo info) { return info.getPatternsCondition().getPatterns(); } //判斷當(dāng)前的RequestMappingInfo與request是否匹配,實(shí)際上是由RequestMappingInfo的getMatchingCondition()方法實(shí)現(xiàn)判斷,并返回一個(gè)新建的RequestMappingInfo 實(shí)例 @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ù)再詳細(xì)分析參數(shù)處理的過程。
5、重寫父類的handleNoMatch()方法,該方法中使用了內(nèi)部類PartialMatchHelper來判斷不匹配的原因,然后拋出指定的異常。不在貼出代碼。
5、RequestMappingHandlerMapping類
RequestMappingHandlerMapping類除了繼承了RequestMappingInfoHandlerMapping之外,還實(shí)現(xiàn)了MatchableHandlerMapping和EmbeddedValueResolverAware兩個(gè)接口。
其中,實(shí)現(xiàn)MatchableHandlerMapping接口的方法,暫時(shí)未使用;而實(shí)現(xiàn)了EmbeddedValueResolverAware接口,說明要支持解析String字符串。
定義的屬性:
//是否啟用后綴匹配 private boolean useSuffixPatternMatch = true; //后綴模式匹配是否應(yīng)該只對(duì)顯式地在ContentNegotiationManager中注冊(cè)的路徑擴(kuò)展有效。 private boolean useRegisteredSuffixPatternMatch = false; //尾部斜杠匹配 private boolean useTrailingSlashMatch = true; //根據(jù)條件設(shè)置前綴path private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>(); //多媒體類型判斷 private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); //字符串解析器,處理spring表達(dá)式 @Nullable private StringValueResolver embeddedValueResolver; //RequestMappingInfo構(gòu)建配置 private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
afterPropertiesSet()方法
在前面,我們知道afterPropertiesSet()方法是實(shí)現(xiàn)初始化的方法。在AbstractHandlerMethodMapping抽象類中,實(shí)現(xiàn)了handler類和方法的檢測和注冊(cè)。在RequestMappingHandlerMapping類中,又增加了RequestMappingInfo構(gòu)建配置的初始化,代碼如下:
@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類中,進(jìn)行初始化的時(shí)候,在processCandidateBean()方法中使用了isHandler判斷當(dāng)前bean實(shí)例是否是處理器。實(shí)際判斷邏輯在這里實(shí)現(xiàn)的。
@Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
getMappingForMethod方法
在AbstractHandlerMethodMapping類中,進(jìn)行初始化的時(shí)候,在detectHandlerMethods()方法中,調(diào)用該方法實(shí)現(xiàn)處理器方法的判斷。
@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()方法這兩個(gè)方法,除了調(diào)用父類的方法之外, 主要是設(shè)置了ConsumesRequestCondition的判斷條件。
后續(xù)還有配置跨域相關(guān)參數(shù),這里不在詳細(xì)分析了。
6、總結(jié)
在這篇文章中,我們只是分析了AbstractHandlerMethodMapping體系實(shí)現(xiàn)的HandlerMapping中這三個(gè)類的基本實(shí)現(xiàn),其中涉及到的RequestCondition及RequestMappingInfo(也是RequestCondition的子類)還有HandlerMethod等類的具體介紹,我們?cè)诤罄m(xù)過程中在逐漸的學(xué)習(xí)記錄。
到此這篇關(guān)于詳解SpringMVC組件之HandlerMapping(二)的文章就介紹到這了,更多相關(guān)SpringMVC的HandlerMapping內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java面向?qū)ο笤O(shè)計(jì)原則之單一職責(zé)與依賴倒置原則詳解
這篇文章主要介紹了java面向?qū)ο笤O(shè)計(jì)原則之單一職責(zé)與依賴倒置原則的分析詳解,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進(jìn)步早日升職加薪2021-10-10java application maven項(xiàng)目打自定義zip包實(shí)例(推薦)
下面小編就為大家?guī)硪黄猨ava application maven項(xiàng)目打自定義zip包實(shí)例(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05SSH框架網(wǎng)上商城項(xiàng)目第17戰(zhàn)之購物車基本功能
這篇文章主要為大家詳細(xì)介紹了SSH框架網(wǎng)上商城項(xiàng)目第17戰(zhàn)之購物車基本功能的實(shí)現(xiàn)過程,感興趣的小伙伴們可以參考一下2016-06-06詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟
本文就在項(xiàng)目中來集成 UidGenerator這一工程來作為項(xiàng)目的全局唯一 ID生成器。接下來通過實(shí)例代碼給大家詳解詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟,感興趣的朋友一起看看吧2018-10-10Java實(shí)現(xiàn)基于UDP協(xié)議的網(wǎng)絡(luò)通信UDP編程
在Java中使用UDP編程,仍然需要使用Socket,因?yàn)閼?yīng)用程序在使用UDP時(shí)必須指定網(wǎng)絡(luò)接口(IP地址)和端口號(hào)。注意:UDP端口和TCP端口雖然都使用0~65535,但他們是兩套獨(dú)立的端口,即一個(gè)應(yīng)用程序用TCP占用了端口1234,不影響另一個(gè)應(yīng)用程序用UDP占用端口12342023-04-04Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹的實(shí)現(xiàn)方法和原理詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹的實(shí)現(xiàn)方法和原理,紅黑樹是一種特殊的二叉查找樹,每個(gè)結(jié)點(diǎn)都要儲(chǔ)存位表示結(jié)點(diǎn)的顏色,或紅或黑,本文將通過示例為大家詳細(xì)講講紅黑樹的原理及實(shí)現(xiàn),感興趣的朋友可以了解一下2024-02-02Spring Boot應(yīng)用事件監(jiān)聽示例詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot應(yīng)用事件監(jiān)聽的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12