SpringSecurity集成第三方登錄過程詳解(最新推薦)
SpringSecurity 集成第三方登錄
認(rèn)證及自定義流程
首先我們提供一個(gè)實(shí)現(xiàn)了AbstractAuthenticationProcessingFilter抽象類的過濾器,用來代替UsernamePasswordAuthenticationFilter邏輯,然后提供一個(gè)AuthenticationProvider實(shí)現(xiàn)類代替AbstractUserDetailsAuthenticationProvider或DaoAuthenticationProvider,最后再提供一個(gè)UserDetailsService實(shí)現(xiàn)類。
1.驗(yàn)證碼登錄
1.通用過濾器實(shí)現(xiàn)–ThirdAuthenticationFilter
這個(gè)ThirdAuthenticationFilter過濾器我們可以仿照UsernamePasswordAuthenticationFilter來實(shí)現(xiàn)(也實(shí)現(xiàn)了AbstractAuthenticationProcessingFilter抽象類),主要是重新定義了attemptAuthentication()方法,這里需要根據(jù)“authType”參數(shù)值的類別構(gòu)建不同的AbstractAuthenticationToken,具體實(shí)現(xiàn)如下:
//驗(yàn)證類型,比如Sms,uernamepassword等 private String authTypeParameter = "authType"; //對(duì)應(yīng)用戶名或手機(jī)號(hào)等 private String principalParameter = "principal"; //對(duì)應(yīng)密碼或驗(yàn)證碼等 private String credentialsParameter = "credentials"; private boolean postOnly = true; public ThirdAuthenticationFilter() { super(new AntPathRequestMatcher("/login/doLogin", "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String authType = request.getParameter(authTypeParameter); if(StringUtils.isEmpty(authType)){ authType = AuthTypeEnum.AUTH_TYPE_DEFAULT.getAuthType(); } String principal = request.getParameter(principalParameter); String credentials = request.getParameter(credentialsParameter); AbstractAuthenticationToken authRequest = null; switch (authType){ case "sms": authRequest = new SmsAuthenticationToken(principal, credentials); ((SmsAuthenticationToken)authRequest).setCode((String)request.getSession().getAttribute("code")); break; case "github": authRequest = new GithubAuthenticationToken(principal, credentials); break; case "default": authRequest = new UsernamePasswordAuthenticationToken(principal, credentials); } authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); return this.getAuthenticationManager().authenticate(authRequest); } }
定義了ThirdAuthenticationSecurityConfig 配置類,我們還需要在SpringSecurity配置類中應(yīng)用才能生效,具體實(shí)現(xiàn)如下:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/error","/login/**","/login/goLogin","/login/doLogin","/login/code","/login/authorization_code").anonymous() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login/goLogin") .loginProcessingUrl("/login/doLogin") .failureUrl("/login/error") .permitAll() .successHandler(new QriverAuthenticationSuccessHandler("/index/toIndex")); //這里我們省略了一些配置 …… //應(yīng)用前面定義的配置 http.apply(thirdAuthenticationSecurityConfig); }
至此,我們定義的通用第三方過濾器就完成了,并且也完成了在SpringSecurity中生效的配置。下面我們就開始分別實(shí)現(xiàn)不同類型登錄的具體過程。
在ThirdAuthenticationFilter 類的attemptAuthentication()方法中,我們通過authType類型,然后創(chuàng)建對(duì)應(yīng)的Authentication實(shí)現(xiàn)來實(shí)現(xiàn)不同方式的登錄,這里我們主要實(shí)現(xiàn)了如下三種方式,我們分別梳理一下。
3、默認(rèn)的登錄過程
默認(rèn)的登錄過程,即根據(jù)用戶名密碼進(jìn)行登錄,需要使用到UsernamePasswordAuthenticationToken,當(dāng)“authType”參數(shù)為"default"時(shí),這里就會(huì)創(chuàng)建UsernamePasswordAuthenticationToken對(duì)象,然后后續(xù)通過ProviderManager的authenticate()方法,最后就會(huì)調(diào)用AbstractUserDetailsAuthenticationProvider(DaoAuthenticationProvider)的 authenticate()方法,最終又會(huì)調(diào)用定義的UserDetailsService實(shí)現(xiàn)類。這是默認(rèn)的過程,這里就不再重復(fù)其中的邏輯,除了UserDetailsService實(shí)現(xiàn)類需要自己定義,其他都是SpringSecurity提供的實(shí)現(xiàn)類。
4、短信驗(yàn)證碼登錄實(shí)現(xiàn)
短信驗(yàn)證碼登錄,是最貼近用戶名密碼登錄的一種方式,所以我們完全可以仿照用戶名密碼這種方式實(shí)現(xiàn)。我們這里先梳理一下短信驗(yàn)證碼登錄的業(yè)務(wù)邏輯:首先,登錄界面輸入手機(jī)號(hào)碼,然后再點(diǎn)擊“獲取驗(yàn)證碼”按鈕獲取短信驗(yàn)證碼,然后輸入收到的短信驗(yàn)證碼,最后點(diǎn)擊“登錄”按鈕進(jìn)行登錄認(rèn)證。和用戶名密碼登錄相比,短信驗(yàn)證碼登錄多了一個(gè)獲取驗(yàn)證碼的過程,其他其實(shí)都是一樣的,我們下面逐步實(shí)現(xiàn)短信驗(yàn)證碼登錄:
@RestController @RequestMapping("/login") public class SmsValidateCodeController { //生成驗(yàn)證碼的實(shí)例對(duì)象 @Autowired private ValidateCodeGenerator smsCodeGenerator; //調(diào)用服務(wù)商接口,發(fā)送短信驗(yàn)證碼的實(shí)例對(duì)象 @Autowired private DefaultSmsCodeSender defaultSmsCodeSender; @RequestMapping("/code") public String createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException { ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request)); String mobile = (String)request.getParameter("principal"); request.getSession().setAttribute("code",smsCode.getCode()); defaultSmsCodeSender.send(mobile, smsCode.getCode()); System.out.println("驗(yàn)證碼:" + smsCode.getCode()); return "驗(yàn)證碼發(fā)送成功!"; } }
在上述方法中,我們注入了smsCodeGenerator和defaultSmsCodeSender兩個(gè)實(shí)例對(duì)象,分別用來生成驗(yàn)證碼和發(fā)送短信驗(yàn)證碼,這個(gè)可以根據(jù)項(xiàng)目的實(shí)際情況進(jìn)行定義和實(shí)現(xiàn),這里不再貼出其中的實(shí)現(xiàn)。同時(shí)在createSmsCode()方法中,還有一點(diǎn)需要注意的就是,我們發(fā)出去的短信驗(yàn)證碼需要進(jìn)行保存,方便后續(xù)登錄時(shí)進(jìn)行驗(yàn)證,這個(gè)也可以選擇很多方法,比如說會(huì)話、數(shù)據(jù)庫(kù)、緩存等,我這里為了簡(jiǎn)單,直接存到了session會(huì)話中了。
然后,我們前面定義ThirdAuthenticationFilter過濾器時(shí),根據(jù)登錄方式不同,需要對(duì)應(yīng)的Authentication對(duì)象,這里我們還需要?jiǎng)?chuàng)建短信驗(yàn)證登錄需要的Authentication類,這里我們可以仿照UsernamePasswordAuthenticationToken類進(jìn)行編寫,實(shí)現(xiàn)如下
public class SmsAuthenticationToken extends AbstractAuthenticationToken { //對(duì)應(yīng)手機(jī)號(hào)碼 private final Object principal; //對(duì)應(yīng)手機(jī)驗(yàn)證碼 private Object credentials; //后臺(tái)存儲(chǔ)的短信驗(yàn)證碼,用于驗(yàn)證前端傳過來的是否正確 private String code; public SmsAuthenticationToken(String mobile, Object credentials){ super(null); this.principal = mobile; this.credentials = credentials; this.code = code; setAuthenticated(false); } public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities, Object credentials){ super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public void eraseCredentials() { super.eraseCredentials(); credentials = null; } }
在SmsAuthenticationToken 類中,我們?cè)黾恿艘粋€(gè)code屬性,其實(shí)該屬性不是必須的,我這里是為了方便傳遞存儲(chǔ)在session會(huì)話中的驗(yàn)證碼而添加的,如果使用緩存或數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ)驗(yàn)證碼,該屬性就可以省略。
在AuthenticationManager的authenticate()方法中,會(huì)根據(jù)Authentication類型選擇AuthenticationProvider對(duì)象,所以我們這里自定義短信驗(yàn)證碼需要的AuthenticationProvider對(duì)象,實(shí)現(xiàn)如下:
@Component public class SmsAuthenticationProvider implements AuthenticationProvider{ @Autowired @Qualifier("smsUserDetailsService") private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { SmsAuthenticationToken token = (SmsAuthenticationToken) authentication; String mobile = (String)token.getPrincipal(); //首先,驗(yàn)證驗(yàn)證碼是否正確 String code = (String)token.getCredentials(); String sCode = token.getCode(); if(StringUtils.isEmpty(code) || !code.equalsIgnoreCase(sCode)){ throw new BadCredentialsException("手機(jī)驗(yàn)證碼錯(cuò)誤(Bad credentials),請(qǐng)重試!"); } //然后,查詢對(duì)應(yīng)用戶 UserDetails user = userDetailsService.loadUserByUsername(mobile); if (Objects.isNull(user)) { throw new InternalAuthenticationServiceException("根據(jù)手機(jī)號(hào):" + mobile + ",無(wú)法獲取對(duì)應(yīng)的用戶信息!"); } SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials()); authenticationResult.setDetails(token.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { return SmsAuthenticationToken.class.isAssignableFrom(authentication); } }
在SmsAuthenticationProvider 中,supports()方法決定了該實(shí)例對(duì)象僅支持SmsAuthenticationToken對(duì)象的驗(yàn)證。同時(shí),根據(jù)authenticate()方法傳遞參數(shù)authentication對(duì)象(包括了登錄信息:手機(jī)號(hào)和驗(yàn)證碼,session存儲(chǔ)的驗(yàn)證碼),我們這里session存儲(chǔ)的驗(yàn)證碼,是因?yàn)槲覀儾捎昧藭?huì)話存儲(chǔ)的方式,如果使用數(shù)據(jù)庫(kù),我們這里就可以通過手機(jī)號(hào),去數(shù)據(jù)庫(kù)或緩存查詢對(duì)應(yīng)的驗(yàn)證碼,然后和authentication對(duì)象傳遞過來的驗(yàn)證碼進(jìn)行比對(duì),驗(yàn)證成功,說明登錄認(rèn)證成功,否則登錄認(rèn)證失敗。登錄成功后,我們就可以調(diào)用userDetailsService對(duì)象的loadUserByUsername()方法獲取登錄用戶的其他相關(guān)信息(權(quán)限等),具體實(shí)現(xiàn)在自定義的SmsUserDetailsService類中實(shí)現(xiàn),具體如下:
@Component("smsUserDetailsService") public class SmsUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(SmsUserDetailsService.class); @Autowired private SysUserService sysUserService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //1、查詢用戶信息 SysUser user = new SysUser(); user.setMobile(username); SysUser qUser = sysUserService.getOne(new QueryWrapper<>(user),true); if(qUser == null) { logger.info("手機(jī)號(hào)為”" + username + "“的用戶不存在!?。?); throw new UsernameNotFoundException("手機(jī)號(hào)為”" + username + "“的用戶不存在?。?!"); } //2、封裝用戶角色 UserRole userRole = sysUserService.getRoleByUserId(qUser.getId()); Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId()))); return new LoginUser(qUser.getUsername(), qUser.getPassword(),authorities); } }
2.GitHub登錄
和短信驗(yàn)證碼登錄認(rèn)證相比,Github登錄又會(huì)有自己的特殊性,我們這里先梳理一下基于Github進(jìn)行登錄驗(yàn)證的大致邏輯:首先,點(diǎn)擊Github登錄認(rèn)證按鈕,然后會(huì)跳轉(zhuǎn)到github登錄界面,輸入github系統(tǒng)的用戶名密碼,登錄成功,就會(huì)跳轉(zhuǎn)到我們自己的系統(tǒng)中的首頁(yè)。和基于用戶名密碼的登錄方式相比,Github登錄不需要類似用戶名和密碼這樣的輸入(在自己的系統(tǒng)中),同時(shí)又需要根據(jù)獲取到的github用戶信息,換取在自己系統(tǒng)對(duì)應(yīng)的用戶信息。具體實(shí)現(xiàn)步驟如下:
在github的配置省略
@Controller @RequestMapping("/login") public class GithubValidateController { @Autowired private GithubClientService githubClientService; @RequestMapping("/authorization_code") public void authorization_code(HttpServletRequest request, HttpServletResponse response, String code) throws ServletRequestBindingException, IOException { //github登錄驗(yàn)證,并獲取access_token Map<String,String> resp = githubClientService.queryAccessToken(code); //跳轉(zhuǎn)本系統(tǒng)的登錄流程,獲取用戶信息,實(shí)現(xiàn)兩個(gè)系統(tǒng)用戶的對(duì)接 String url = "http://localhost:8888/qriver-admin/login/doLogin"; this.sendByPost(response, url,resp.get("access_token"),"github"); //this.sendByPost(response, url,"access_token","github"); } public void sendByPost(HttpServletResponse response,String url, String principal, String authType) throws IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>Post 方法</TITLE></HEAD>"); out.println(" <BODY>"); out.println("<form name=\"submitForm\" action=\"" + url + "\" method=\"post\">"); out.println("<input type=\"hidden\" name=\"principal\" value=\"" + principal + "\"/>"); out.println("<input type=\"hidden\" name=\"authType\" value=\"" + authType + "\"/>"); out.println("</from>"); out.println("<script>window.document.submitForm.submit();</script> "); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } }
“/login/authorization_code”接口對(duì)應(yīng)了我們?cè)贕ithub中配置的回調(diào)函數(shù),即在Github登錄驗(yàn)證成功后,就會(huì)回調(diào)該接口,我們就是就在回調(diào)方法中,模擬了用戶名密碼登錄的方式,調(diào)用了SpringSecurity登錄認(rèn)證需要的“/login/doLogin”接口。這里,我們通過queryAccessToken()方法根據(jù)回調(diào)傳遞的code獲取對(duì)應(yīng)的accessToken,然后把a(bǔ)ccessToken作為登錄使用的principal 參數(shù)值,之而立不需要傳遞密碼,因?yàn)槲覀兘?jīng)過Github授權(quán),就可以認(rèn)為完成了登錄認(rèn)證的判斷過程了。
其中GithubClientService類,提供了獲取accessToken和用戶信息的兩個(gè)方法,具體實(shí)現(xiàn)方式如下:
@Service public class GithubClientService { //前面在github中配置時(shí)產(chǎn)生的 private String clientId = "######"; private String clientSecret = "######"; private String state = "123"; private String redirectUri = "http://localhost:8888/qriver-admin/login/authorization_code"; @Autowired private RestTemplate restTemplate; @Nullable private WebApplicationContext webApplicationContext; //獲取accessToken public Map<String, String> queryAccessToken(String code ){ Map<String, String> map = new HashMap<>(); map.put("client_id", clientId); map.put("client_secret", clientSecret); map.put("state", state); map.put("code", code); map.put("redirect_uri", redirectUri); Map<String,String> resp = restTemplate.postForObject("https://github.com/login/oauth/access_token", map, Map.class); return resp; } //獲取用戶信息 public Map<String, Object> queryUser(String accessToken){ HttpHeaders httpheaders = new HttpHeaders(); httpheaders.add("Authorization", "token " + accessToken); HttpEntity<?> httpEntity = new HttpEntity<>(httpheaders); ResponseEntity<Map> exchange = restTemplate.exchange("https://api.github.com/user", HttpMethod.GET, httpEntity, Map.class); System.out.println("exchange.getBody() = " + exchange.getBody()); return exchange == null ? null : exchange.getBody(); } }
其實(shí),完成了上述的配置和方式后,后續(xù)的方式就和短信驗(yàn)證碼的邏輯一樣了,這里我們簡(jiǎn)要的再梳理一下。
首先,我們也需要定義一個(gè)基于Github登錄需要的Authentication實(shí)現(xiàn)類,具體實(shí)現(xiàn)和前面的SmsAuthenticationToken類似,這里不再重復(fù)貼代碼了。
然后,我們?cè)俣x一個(gè)AuthenticationProvider實(shí)現(xiàn)類GithubAuthenticationProvider,具體實(shí)現(xiàn)如下:
@Component public class GithubAuthenticationProvider implements AuthenticationProvider{ @Autowired @Qualifier("githubUserDetailsService") private UserDetailsService userDetailsService; @Autowired private GithubClientService githubClientService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { GithubAuthenticationToken token = (GithubAuthenticationToken) authentication; String accessToken = (String)token.getPrincipal(); //根據(jù)accessToken 獲取github用戶信息 Map<String, Object> userInfo = githubClientService.queryUser(accessToken); //然后,根據(jù)github用戶,查詢對(duì)應(yīng)系統(tǒng)用戶信息 UserDetails user = userDetailsService.loadUserByUsername((String)userInfo.get("login")); if (Objects.isNull(user)) { throw new InternalAuthenticationServiceException("根據(jù)accessToken:" + accessToken + ",無(wú)法獲取對(duì)應(yīng)的用戶信息!"); } GithubAuthenticationToken authenticationResult = new GithubAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials()); authenticationResult.setDetails(token.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { return GithubAuthenticationToken.class.isAssignableFrom(authentication); } }
在GithubAuthenticationProvider 類的authenticate()方法中,參數(shù)authentication中對(duì)應(yīng)的是Github授權(quán)后傳遞的accessToken值,我們這里需要根據(jù)accessToken值換取Github用戶信息,這里通過queryUser()方法實(shí)現(xiàn),然后根據(jù)github用戶名去獲取對(duì)應(yīng)的系統(tǒng)用戶信息。如果根據(jù)github用戶名用戶獲取的系統(tǒng)用戶為空,我們可以根據(jù)自己的需求,自動(dòng)生成一個(gè)用戶或者跳轉(zhuǎn)到注冊(cè)頁(yè)面,讓用戶注冊(cè)一個(gè)頁(yè)面,這里為了簡(jiǎn)單,我們直接拋出了一個(gè)異常。
關(guān)于自定義UserDetailsService實(shí)現(xiàn)類,主要需要實(shí)現(xiàn)根據(jù)github用戶名查詢對(duì)應(yīng)系統(tǒng)用戶的功能
當(dāng)認(rèn)證完成后要返回token可以實(shí)現(xiàn)AuthenticationSuccessHandler
import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final JwtTokenProvider jwtTokenProvider; // 假設(shè)你有一個(gè)JwtTokenProvider類來生成JWT public CustomAuthenticationSuccessHandler(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { // 生成JWT String token = jwtTokenProvider.generateToken(authentication); // 將JWT添加到響應(yīng)頭中 response.setHeader("Authorization", "Bearer " + token); // 或者將JWT添加到響應(yīng)體中(取決于你的API設(shè)計(jì)) // response.getWriter().write(token); response.setStatus(HttpServletResponse.SC_OK); } }
并在securityconfig中設(shè)置
到此這篇關(guān)于SpringSecurity集成第三方登錄的文章就介紹到這了,更多相關(guān)SpringSecurity登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)
- springsecurity實(shí)現(xiàn)用戶登錄認(rèn)證快速使用示例代碼(前后端分離項(xiàng)目)
- SpringSecurity自動(dòng)登錄流程與實(shí)現(xiàn)詳解
- SpringSecurity6自定義JSON登錄的實(shí)現(xiàn)
- SpringSecurity6.x多種登錄方式配置小結(jié)
- 如何使用JWT的SpringSecurity實(shí)現(xiàn)前后端分離
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- springSecurity之如何添加自定義過濾器
- springSecurity自定義登錄接口和JWT認(rèn)證過濾器的流程
相關(guān)文章
使用JMeter從JSON響應(yīng)的URL參數(shù)中提取特定值
在使用Apache JMeter進(jìn)行API測(cè)試時(shí),我們經(jīng)常需要從JSON格式的響應(yīng)中提取特定字段的值,這可以通過使用JMeter內(nèi)置的JSON提取器和正則表達(dá)式提取器來完成,本文介紹JMeter JSON提取特定值的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2024-03-03Netty結(jié)合Protobuf進(jìn)行編解碼的方法
這篇文章主要介紹了Netty結(jié)合Protobuf進(jìn)行編解碼,通過文檔表述和代碼實(shí)例充分說明了如何進(jìn)行使用和操作,需要的朋友可以參考下2021-06-06SpringCloud Eureka實(shí)現(xiàn)服務(wù)注冊(cè)與發(fā)現(xiàn)
Eureka是一種基于REST(具像狀態(tài)傳輸)的服務(wù),主要用于AWS云中定位服務(wù),以實(shí)現(xiàn)中間層服務(wù)器的負(fù)載平衡和故障轉(zhuǎn)移。本文記錄一個(gè)簡(jiǎn)單的服務(wù)注冊(cè)與發(fā)現(xiàn)實(shí)例。感興趣的小伙伴們可以參考一下2019-01-01SpringBoot搭建Dubbo項(xiàng)目實(shí)現(xiàn)斐波那契第n項(xiàng)詳解
這篇文章主要講解了“SpringBoot+Dubbo怎么實(shí)現(xiàn)斐波那契第N項(xiàng)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)吧2022-06-06java語(yǔ)言與平臺(tái)基礎(chǔ)知識(shí)點(diǎn)
在本篇文章里小編給大家整理的是一篇關(guān)于java語(yǔ)言與平臺(tái)基礎(chǔ)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們跟著學(xué)習(xí)下。2019-11-11java發(fā)送HttpClient請(qǐng)求及接收請(qǐng)求結(jié)果過程的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)硪黄猨ava發(fā)送HttpClient請(qǐng)求及接收請(qǐng)求結(jié)果過程的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11Java使用JDK與Cglib動(dòng)態(tài)代理技術(shù)統(tǒng)一管理日志記錄
這篇文章主要介紹了Java使用JDK與Cglib動(dòng)態(tài)代理技術(shù)統(tǒng)一管理日志記錄,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05