Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和鑒權(quán)全過(guò)程
一、Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證
1、springsecurity是通過(guò)在web一系列原生filter攔截器中增加自己的過(guò)濾器鏈來(lái)攔截web請(qǐng)求,然后請(qǐng)求會(huì)在經(jīng)過(guò)過(guò)濾器鏈的過(guò)程中會(huì)完成認(rèn)證與授權(quán),如果中間發(fā)現(xiàn)這條請(qǐng)求未認(rèn)證或者未授權(quán),會(huì)根據(jù)被保護(hù)API的權(quán)限去拋出異常,然后由異常處理器去處理這些異常。
2、SpringSecurity通過(guò)FilterChainProxy管理眾多SecurityFilterChain, 而FilterChainProxy則被DelegatingFilterProxy管理并被DelegatingFilterProxy放入web原生的過(guò)濾器鏈中;
每個(gè)SecurityFilterChain下則是具體的擁有攔截規(guī)則的filter,這些filter由SpringSecurity進(jìn)行代理操作,可以理解為他是"Security Filter",而不是原生的"Web Filter";
總結(jié)就是:
【DelegatingFilterProxy】——管理——>【FilterChainProxy】——管理——>【SecurityFilterChain】——管理——>【Security Filter】
3、springboot整合springsecurity,springboot會(huì)通過(guò)一系列xxxAutoConfiguration進(jìn)行自動(dòng)配置默認(rèn)的Spring Security的一系列底層組件,如WebSecurityConfigurerAdapter和一些默認(rèn)組件,有些"Security Filter"會(huì)自動(dòng)開(kāi)啟,有些則不會(huì);
整個(gè)認(rèn)證的過(guò)程其實(shí)一直在圍繞圖中過(guò)濾鏈的綠色部分,而動(dòng)態(tài)鑒權(quán)主要是圍繞其橙色部分;
Spring Security配置中有兩個(gè)叫formLogin和httpBasic的配置項(xiàng),這兩個(gè)配置項(xiàng)就分別對(duì)應(yīng)著圖中分的兩個(gè)過(guò)濾器
- formLogin對(duì)應(yīng)著你form表單認(rèn)證方式,即UsernamePasswordAuthenticationFilter。
- httpBasic對(duì)應(yīng)著B(niǎo)asic認(rèn)證方式,即BasicAuthenticationFilter。
4、我使用的就是UsernamePasswordAuthenticationFilter這個(gè)過(guò)濾器,springboot整合springsecurity時(shí)會(huì)自動(dòng)加載這個(gè)過(guò)濾器;
Spring Security 在自動(dòng)裝配后,會(huì)有默認(rèn)的攔截策略,未登陸的請(qǐng)求都會(huì)被攔截并跳轉(zhuǎn)到login登錄頁(yè),此時(shí)輸入賬號(hào)密碼登錄就會(huì)被這個(gè)UsernamePasswordAuthenticationFilter攔截,并驗(yàn)證賬號(hào)是否存在,密碼是否正確
進(jìn)入formlogin,發(fā)現(xiàn)有個(gè)**FormLoginConfigurer()**方法
進(jìn)入FormLoginConfigurer()方法,在這里用戶(hù)輸入賬號(hào)密碼就會(huì)被這個(gè)UsernamePasswordAuthenticationFilter攔截,并驗(yàn)證進(jìn)行認(rèn)證
發(fā)送登陸請(qǐng)求后,UsernamePasswordAuthenticationFilter會(huì)調(diào)用attemptAuthentication() 方法進(jìn)行認(rèn)證,失敗則拋出異常,成功則返回帶有用戶(hù)信息的Authentication對(duì)象
"Security Filter"中,認(rèn)證過(guò)程是由 " 主角 " AuthenticationManager(接口)去管理AuthenticationProvider(接口)去實(shí)現(xiàn)的,AuthenticationManager可以有多個(gè),他們?nèi)绻J(rèn)證失敗就會(huì)調(diào)用父親也就是全局的AuthenticationManager再去認(rèn)證看看,一般只用一個(gè)全局的.
一個(gè)AuthenticationProvider代表一種認(rèn)證方法,只要其中一個(gè)AuthenticationProvider認(rèn)證通過(guò)就算登陸成功,記住兩個(gè)主角的實(shí)現(xiàn)類(lèi)ProviderManager和DaoAuthenticationProvider
回到attemptAuthentication()方法,調(diào)用拿到全局的AuthenticationMananger去執(zhí)行*authenticate()*方法,拿到ProviderManager中所有的AuthenticationProvider,交給他們?nèi)フJ(rèn)證
在遍歷provider這個(gè)過(guò)程中,調(diào)用了provider(DaoAuthenticationProvider)的authenticate方法,由provider去認(rèn)證,AuthenticationProvider的實(shí)現(xiàn)類(lèi)DaoAuthenticationProvider繼承了AbstractUserDetailsAuthenticationProvider,所以自然也有父類(lèi)方法的*authenticate()方法,因?yàn)闆](méi)有重寫(xiě)他,所以在源碼debug階段會(huì)進(jìn)入了他的父類(lèi)的authenticate()方法,他的父類(lèi)AbstractUserDetailsAuthenticationProvider實(shí)現(xiàn)了AuthenticationProvider
在provider(DaoAuthenticationProvider)的authenticate()方法中,先調(diào)用retrieveUser()通過(guò)用戶(hù)名來(lái)獲取我們存儲(chǔ)中是否有該用戶(hù),如果有就封裝到UserDetail中,后面再拿請(qǐng)求中的密碼跟UserDetail用戶(hù)信息中的密碼進(jìn)行比較,如果沒(méi)有,密碼都不用比較了,因?yàn)橛脩?hù)根本不存在.provider中有個(gè)叫UserDetailService的接口,通過(guò)用戶(hù)名可以獲取我們的用戶(hù)數(shù)據(jù)(他功能相當(dāng)于一個(gè)service層去調(diào)用dao層最終返回用戶(hù)數(shù)據(jù)),在自動(dòng)裝配中,默認(rèn)配了個(gè)基于內(nèi)存存儲(chǔ)的InMemoryUserDetailsManager,他是UserDetailService的實(shí)現(xiàn)類(lèi);
所以在使用springsecurity進(jìn)行登錄認(rèn)證的時(shí)候,除了要?jiǎng)?chuàng)建配置類(lèi)進(jìn)行相關(guān)內(nèi)容的配置,還要?jiǎng)?chuàng)建UserDetailService的實(shí)現(xiàn)類(lèi)用于到數(shù)據(jù)庫(kù)中查詢(xún)登錄認(rèn)證所需要的信息;
并且還要?jiǎng)?chuàng)建UserDetail的實(shí)現(xiàn)類(lèi)用于封裝查詢(xún)出來(lái)的數(shù)據(jù),并把數(shù)據(jù)交給springsecurity框架拿去用于認(rèn)證
最后通過(guò)additionalAuthenticationChecks()方法進(jìn)行密碼比較
認(rèn)證失敗拋異常,認(rèn)證成功則將用戶(hù)詳細(xì)信息封裝進(jìn)Authentication返回
二、Springboot整合SpringSecurity實(shí)現(xiàn)鑒權(quán)
1、整個(gè)認(rèn)證的過(guò)程其實(shí)一直在圍繞圖中過(guò)濾鏈的綠色部分,而現(xiàn)在要說(shuō)的動(dòng)態(tài)鑒權(quán)主要是圍繞其橙色部分,也就是圖上標(biāo)的:FilterSecurityInterceptor
2、想知道怎么動(dòng)態(tài)鑒權(quán)首先我們要搞明白SpringSecurity的鑒權(quán)邏輯,從上圖中我們也可以看出:一個(gè)請(qǐng)求完成了認(rèn)證,且沒(méi)有拋出異常之后就會(huì)到達(dá)FilterSecurityInterceptor所負(fù)責(zé)的鑒權(quán)部分,也就是說(shuō)鑒權(quán)的入口就在FilterSecurityInterceptor。
先來(lái)看看FilterSecurityInterceptor的定義和主要方法:
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } }
上文代碼可以看出FilterSecurityInterceptor是抽象類(lèi)AbstractSecurityInterceptor的一個(gè)實(shí)現(xiàn)類(lèi),這個(gè)AbstractSecurityInterceptor中預(yù)先寫(xiě)好了一段很重要的代碼(后面會(huì)說(shuō)到)。
FilterSecurityInterceptor的主要方法是doFilter方法,請(qǐng)求過(guò)來(lái)之后會(huì)執(zhí)行這個(gè)doFilter方法,F(xiàn)ilterSecurityInterceptor的doFilter方法出奇的簡(jiǎn)單,總共只有兩行:
- 第一行是創(chuàng)建了一個(gè)FilterInvocation對(duì)象,這個(gè)FilterInvocation對(duì)象你可以當(dāng)作它封裝了request,它的主要工作就是拿請(qǐng)求里面的信息,比如請(qǐng)求的URI和method
- 第二行就調(diào)用了自身的invoke方法,并將FilterInvocation對(duì)象傳入
所以我們主要邏輯肯定是在這個(gè)invoke方法里面了,我們來(lái)打開(kāi)看看:
public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } // 進(jìn)入鑒權(quán) InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } }
invoke方法中只有一個(gè)if-else,一般都是不滿足if中的那三個(gè)條件的,然后執(zhí)行邏輯會(huì)來(lái)到else。
else的代碼也可以概括為兩部分:
- 調(diào)用了super.beforeInvocation(fi)。
- 調(diào)用完之后過(guò)濾器繼續(xù)往下走。
第二步可以不看,每個(gè)過(guò)濾器都有這么一步,所以我們主要看super.beforeInvocation(fi),前文我已經(jīng)說(shuō)過(guò), FilterSecurityInterceptor實(shí)現(xiàn)了抽象類(lèi)AbstractSecurityInterceptor, 所以這個(gè)里super其實(shí)指的就是AbstractSecurityInterceptor, 那這段代碼其實(shí)調(diào)用了AbstractSecurityInterceptor.beforeInvocation(fi), 前文我說(shuō)過(guò)AbstractSecurityInterceptor中有一段很重要的代碼就是這一段, 那我們繼續(xù)來(lái)看這個(gè)beforeInvocation(fi)方法的源碼:
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); final boolean debug = logger.isDebugEnabled(); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException( "Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); Authentication authenticated = authenticateIfRequired(); try { // 鑒權(quán)需要調(diào)用的接口 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } }
源碼較長(zhǎng),這段代碼大致可以分為三步:
拿到了一個(gè)Collection對(duì)象,這個(gè)對(duì)象是一個(gè)List,其實(shí)里面是通過(guò)我們?cè)谂渲梦募信渲玫倪^(guò)濾規(guī)則獲取到請(qǐng)求需要的角色權(quán)限。
public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; }
拿到了Authentication,這里是調(diào)用authenticateIfRequired方法拿到了,其實(shí)里面是通過(guò)SecurityContextHolder拿到的
Authentication authenticated = authenticateIfRequired();
調(diào)用了accessDecisionManager.decide(authenticated, object, attributes),前兩步都是對(duì)decide方法做參數(shù)的準(zhǔn)備,第三步才是正式去到鑒權(quán)的邏輯,既然這里面才是真正鑒權(quán)的邏輯,那也就是說(shuō)鑒權(quán)其實(shí)是accessDecisionManager在做。
// 鑒權(quán)需要調(diào)用的接口 this.accessDecisionManager.decide(authenticated, object, attributes);
AccessDecisionManager是一個(gè)接口,它聲明了三個(gè)方法,除了第一個(gè)decide()鑒權(quán)方法以外,還有兩個(gè)是輔助性的方法,其作用都是甄別 decide方法中參數(shù)的有效性。
那既然是一個(gè)接口,上文中所調(diào)用的肯定是他的實(shí)現(xiàn)類(lèi)了
它主要有三個(gè)實(shí)現(xiàn)類(lèi),分別代表了三種不同的鑒權(quán)邏輯:
- AffirmativeBased:一票通過(guò),只要有一票通過(guò)就算通過(guò),默認(rèn)是它。
- UnanimousBased:一票反對(duì),只要有一票反對(duì)就不能通過(guò)。
- ConsensusBased:少數(shù)票服從多數(shù)票。
這里的表述為什么要用票呢?因?yàn)樵趯?shí)現(xiàn)類(lèi)里面采用了委托的形式,將請(qǐng)求委托給投票器,每個(gè)投票器拿著這個(gè)請(qǐng)求根據(jù)自身的邏輯來(lái)計(jì)算出能不能通過(guò)然后進(jìn)行投票,所以會(huì)有上面的表述。
也就是說(shuō)這三個(gè)實(shí)現(xiàn)類(lèi),其實(shí)還不是真正判斷請(qǐng)求能不能通過(guò)的類(lèi),真正判斷請(qǐng)求是否通過(guò)的是投票器,然后實(shí)現(xiàn)類(lèi)把投票器的結(jié)果綜合起來(lái)來(lái)決定到底能不能通過(guò)。
剛剛已經(jīng)說(shuō)過(guò),實(shí)現(xiàn)類(lèi)把投票器的結(jié)果綜合起來(lái)進(jìn)行決定,也就是說(shuō)投票器可以放入多個(gè),每個(gè)實(shí)現(xiàn)類(lèi)里的投票器數(shù)量取決于構(gòu)造的時(shí)候放入了多少投票器,我們可以看看默認(rèn)的AffirmativeBased的源碼。
public class AffirmativeBased extends AbstractAccessDecisionManager { public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) { super(decisionVoters); } // 拿到所有的投票器,循環(huán)遍歷進(jìn)行投票 public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } }
AffirmativeBased的構(gòu)造是傳入投票器List,其主要鑒權(quán)邏輯交給投票器去判斷,投票器返回不同的數(shù)字代表不同的結(jié)果,然后AffirmativeBased根據(jù)自身一票通過(guò)的策略決定放行還是拋出異常。
AffirmativeBased默認(rèn)傳入的構(gòu)造器只有一個(gè)->WebExpressionVoter,這個(gè)構(gòu)造器會(huì)根據(jù)你在配置文件中的配置進(jìn)行邏輯處理得出投票結(jié)果。
所以SpringSecurity默認(rèn)的鑒權(quán)邏輯就是根據(jù)配置文件中的配置進(jìn)行鑒權(quán),這是符合我們現(xiàn)有認(rèn)知的
3、總結(jié)一下就是:
FilterSecurityInterceptor執(zhí)行doFilter 方法創(chuàng)建FilterInvocation(req,resp,chain)對(duì)象;然后調(diào)用自身invoke方法,傳入對(duì)象
invoke方法中,在 chain().doFilter 前有 super.beforeInvocation(fi),調(diào)用 AbstractSecurityInterceptor 的beforeInvocation方法
beforeInvocation方法中
- 通過(guò)調(diào)用請(qǐng)求過(guò)濾接口obtainSecurityMetadataSource() 的getAttributes()方法獲取一個(gè)Collection對(duì)象,這個(gè)對(duì)象是一個(gè)list,里面封裝了請(qǐng)求所需要的角色權(quán)限
- 調(diào)用authenticateIfRequired方法拿到Authentication對(duì)象
- 調(diào)用了accessDecisionManager.decide(authenticated, object, attributes)正式進(jìn)行鑒權(quán)
4、在使用springsecurity進(jìn)行鑒權(quán)操作的時(shí)候,根據(jù)具體業(yè)務(wù)需求去自定義請(qǐng)求過(guò)濾器obtainSecurityMetadataSource()和投票器accessDecisionManager()
- 自定義請(qǐng)求過(guò)濾器,重寫(xiě)getAttributes()方法
@Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // 修改接口角色關(guān)系后重新加載 if (CollectionUtils.isEmpty(resourceRoleList)) { this.loadDataSource(); } //Spring Security 通過(guò)FilterInvocation對(duì)object進(jìn)行封裝,可以安全的拿到其HttpServletRequest 和 HttpServletResponse對(duì)象 FilterInvocation fi = (FilterInvocation) object; // 獲取用戶(hù)請(qǐng)求方式 String method = fi.getRequest().getMethod(); // 獲取用戶(hù)請(qǐng)求Url String url = fi.getRequest().getRequestURI(); //new一個(gè)工具類(lèi)AntPathMatcher的實(shí)例化對(duì)象,把路徑匹配委托給AntPathMatcher實(shí)現(xiàn) AntPathMatcher antPathMatcher = new AntPathMatcher(); // 獲取接口角色信息,若為匿名接口則放行,若無(wú)對(duì)應(yīng)角色則禁止 for (ResourceRoleDTO resourceRoleDTO : resourceRoleList) { //判斷resourceRoleList中是否有和參數(shù)對(duì)象的URL和method完全相同的對(duì)象 if (antPathMatcher.match(resourceRoleDTO.getUrl(), url) && resourceRoleDTO.getRequestMethod().equals(method)) { //如果有對(duì)象匹配成功,則獲取該對(duì)象的角色列表RoleList List<String> roleList = resourceRoleDTO.getRoleList(); if (CollectionUtils.isEmpty(roleList)) { return SecurityConfig.createList("disable"); } return SecurityConfig.createList(roleList.toArray(new String[]{})); //rolelist集合轉(zhuǎn)換成String數(shù)組,通過(guò)SecurityConfig.createList(str)對(duì)結(jié)果進(jìn)行封裝,然后return } } return null; }
自定義投票器
@Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { // 獲取用戶(hù)權(quán)限列表 List<String> permissionList = authentication.getAuthorities() .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); for (ConfigAttribute item : collection) { //item.getAttribute()獲取當(dāng)前用戶(hù)訪問(wèn)資源所需要的權(quán)限 //如果用戶(hù)權(quán)限列表中包含該權(quán)限,則return,否則最后會(huì)提示沒(méi)有操作權(quán)限 if (permissionList.contains(item.getAttribute())) { return; } } throw new AccessDeniedException("沒(méi)有操作權(quán)限"); }
Config文件中,調(diào)用 postProcess 方法將自定義的請(qǐng)求過(guò)濾器和投票器注冊(cè)到 Spring 容器中去
http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O fsi) { fsi.setSecurityMetadataSource(securityMetadataSource()); //設(shè)置請(qǐng)求攔截規(guī)則 fsi.setAccessDecisionManager(accessDecisionManager()); //設(shè)置訪問(wèn)決策管理器,真正的鑒權(quán)操作在這里完成 return fsi; } })
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot整合SpringSecurityOauth2實(shí)現(xiàn)鑒權(quán)動(dòng)態(tài)權(quán)限問(wèn)題
- SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)的實(shí)現(xiàn)
- SpringSecurity動(dòng)態(tài)加載用戶(hù)角色權(quán)限實(shí)現(xiàn)登錄及鑒權(quán)功能
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問(wèn)題
- SpringSecurity實(shí)現(xiàn)權(quán)限認(rèn)證與授權(quán)的使用示例
- SpringSecurity進(jìn)行認(rèn)證與授權(quán)的示例代碼
- springSecurity用戶(hù)認(rèn)證和授權(quán)的實(shí)現(xiàn)
- 深入淺析springsecurity入門(mén)登錄授權(quán)
- mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)
- SpringSecurity頁(yè)面授權(quán)與登錄驗(yàn)證實(shí)現(xiàn)(內(nèi)存取值與數(shù)據(jù)庫(kù)取值)
- SpringSecurity 鑒權(quán)與授權(quán)的具體使用
相關(guān)文章
使用Spring?Cloud?Stream處理事件的示例詳解
Spring?Cloud?Stream?是基于?Spring?Boot?的用于構(gòu)建消息驅(qū)動(dòng)微服務(wù)的框架,本文主要介紹了如何使用?Spring?Cloud?Stream?來(lái)處理事件,需要的可以參考一下2023-06-06Spring Cloud OpenFeign實(shí)現(xiàn)動(dòng)態(tài)服務(wù)名調(diào)用的示例代碼
在微服務(wù)架構(gòu)中,我們經(jīng)常需要根據(jù)動(dòng)態(tài)傳入的服務(wù)名來(lái)遠(yuǎn)程調(diào)用其他服務(wù),例如,你的業(yè)務(wù)中可能有多個(gè)子服務(wù):service-1、service-2……需要?jiǎng)討B(tài)決定調(diào)用哪個(gè),所以本文給大家介紹了Spring Cloud OpenFeign 實(shí)現(xiàn)動(dòng)態(tài)服務(wù)名調(diào)用指南,需要的朋友可以參考下2025-06-06如何用Java將數(shù)據(jù)庫(kù)的數(shù)據(jù)生成pdf返回給前端用戶(hù)下載
本文詳細(xì)介紹了使用SpringBoot、iText庫(kù)、MyBatis等技術(shù)從數(shù)據(jù)庫(kù)中選取數(shù)據(jù)并生成PDF文件的后端處理流程,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09java算法題解Leetcode15三數(shù)之和實(shí)例
這篇文章主要為大家介紹了java算法題解Leetcode15三數(shù)之和實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01mybatis sum(參數(shù)) 列名作為參數(shù)的問(wèn)題
這篇文章主要介紹了mybatis sum(參數(shù)) 列名作為參數(shù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Java中ArrayList類(lèi)的用法與源碼完全解析
這篇文章主要介紹了Java中ArrayList類(lèi)的用法與源碼完全解析,ArrayList類(lèi)通過(guò)List接口實(shí)現(xiàn),是Java中引申出的一種數(shù)據(jù)結(jié)構(gòu),需要的朋友可以參考下2016-05-05SpringBoot3應(yīng)用中集成和使用Spring Retry的實(shí)踐記錄
SpringRetry為SpringBoot3提供重試機(jī)制,支持注解和編程式兩種方式,可配置重試策略與監(jiān)聽(tīng)器,適用于臨時(shí)性故障場(chǎng)景,需合理設(shè)置次數(shù)、退避策略并做好監(jiān)控,本文給大家詳細(xì)介紹如何在 SpringBoot 3 應(yīng)用中集成和使用 Spring Retry,感興趣的朋友一起看看吧2025-06-06Spring mvc JSON數(shù)據(jù)交換格式原理解析
這篇文章主要介紹了Spring mvc JSON數(shù)據(jù)交換格式原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03