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

詳解Spring Security認(rèn)證流程

 更新時(shí)間:2020年02月06日 08:34:07   作者:donespeak  
這篇文章主要介紹了Spring Security認(rèn)證流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

Spring Seuciry相關(guān)的內(nèi)容看了實(shí)在是太多了,但總覺(jué)得還是理解地不夠鞏固,還是需要靠知識(shí)輸出做鞏固。

相關(guān)版本:

java: jdk 8 
spring-boot: 2.1.6.RELEASE 

過(guò)濾器鏈和認(rèn)證過(guò)程

一個(gè)認(rèn)證過(guò)程,其實(shí)就是過(guò)濾器鏈上的一個(gè)綠色矩形Filter所要執(zhí)行的過(guò)程。

基本的認(rèn)證過(guò)程有三步驟:

  1. Filter攔截請(qǐng)求,生成一個(gè)未認(rèn)證的Authentication,交由AuthenticationManager進(jìn)行認(rèn)證;
  2. AuthenticationManager的默認(rèn)實(shí)現(xiàn)ProviderManager會(huì)通過(guò)AuthenticationProvider對(duì)Authentication進(jìn)行認(rèn)證,其本身不做認(rèn)證處理;
  3. 如果認(rèn)證通過(guò),則創(chuàng)建一個(gè)認(rèn)證通過(guò)的Authentication返回;否則拋出異常,以表示認(rèn)證不通過(guò)。

要理解這個(gè)過(guò)程,可以從類UsernamePasswordAuthenticationFilterProviderManager,DaoAuthenticationProviderInMemoryUserDetailsManagerUserDetailsService實(shí)現(xiàn)類,由UserDetailsServiceAutoConfiguration默認(rèn)配置提供)進(jìn)行了解。只要?jiǎng)?chuàng)建一個(gè)含有spring-boot-starter-security的springboot項(xiàng)目,在適當(dāng)?shù)卮蛏蠑帱c(diǎn)接口看到這個(gè)流程。

用認(rèn)證部門(mén)進(jìn)行講解

)

請(qǐng)求到前臺(tái)之后,負(fù)責(zé)該請(qǐng)求的前臺(tái)會(huì)將請(qǐng)求的內(nèi)容封裝為一個(gè)Authentication對(duì)象交給認(rèn)證管理部門(mén)認(rèn)證管理部門(mén)僅管理認(rèn)證部門(mén),不做具體的認(rèn)證操作,具體的操作由與該前臺(tái)相關(guān)的認(rèn)證部門(mén)進(jìn)行處理。當(dāng)然,每個(gè)認(rèn)證部門(mén)需要判斷Authentication是否為該部門(mén)負(fù)責(zé),是則由該部門(mén)負(fù)責(zé)處理,否則交給下一個(gè)部門(mén)處理。認(rèn)證部門(mén)認(rèn)證成功之后會(huì)創(chuàng)建一個(gè)認(rèn)證通過(guò)的Authentication返回。否則要么拋出異常表示認(rèn)證不通過(guò),要么交給下一個(gè)部門(mén)處理。

如果需要新增認(rèn)證類型,只要增加相應(yīng)的前臺(tái)(Filter)和與該前臺(tái)(Filter)想對(duì)應(yīng)的認(rèn)證部門(mén)(AuthenticationProvider)就即可,當(dāng)然也可以增加一個(gè)與已有前臺(tái)對(duì)應(yīng)的認(rèn)證部門(mén)認(rèn)證部門(mén)會(huì)通過(guò)前臺(tái)生成的Authentication來(lái)判斷該認(rèn)證是否由該部門(mén)負(fù)責(zé),因而也許提供一個(gè)兩者相互認(rèn)同的Authentication.

認(rèn)證部門(mén)需要人員資料時(shí),則可以從人員資料部門(mén)獲取。不同的系統(tǒng)有不同的人員資料部門(mén),需要我們提供該人員資料部門(mén),否則將拿到空白檔案。當(dāng)然,人員資料部門(mén)不一定是唯一的,認(rèn)證部門(mén)可以有自己的專屬資料部門(mén)。

上圖還可以有如下的畫(huà)法:

這個(gè)畫(huà)法可能會(huì)和FilterChain更加符合。每一個(gè)前臺(tái)其實(shí)就是FilterChain中的一個(gè),客戶拿著請(qǐng)求逐個(gè)前臺(tái)請(qǐng)求認(rèn)證,找到正確的前臺(tái)之后進(jìn)行認(rèn)證判斷。

前臺(tái)(Filter)

這里的前臺(tái)Filter僅僅指實(shí)現(xiàn)認(rèn)證的Filter,Spring Security Filter Chain中處理這些Filter還有其他的Filter,比如CsrfFilter。如果非要給角色給他們,那么就當(dāng)他們是保安人員吧。

Spring Security為我們提供了3個(gè)已經(jīng)實(shí)現(xiàn)的Filter。UsernamePasswordAuthenticationFilter,BasicAuthenticationFilterRememberMeAuthenticationFilter。如果不做任何個(gè)性化的配置,UsernamePasswordAuthenticationFilterBasicAuthenticationFilter會(huì)在默認(rèn)的過(guò)濾器鏈中。這兩種認(rèn)證方式也就是默認(rèn)的認(rèn)證方式。

UsernamePasswordAuthenticationFilter僅僅會(huì)對(duì)/login路徑生效,也就是說(shuō)UsernamePasswordAuthenticationFilter負(fù)責(zé)發(fā)布認(rèn)證,發(fā)布認(rèn)證的接口為/login。

public class UsernamePasswordAuthenticationFilter extends
    AbstractAuthenticationProcessingFilter {
  ...
  public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
  }
  ...
}

UsernamePasswordAuthenticationFilter為抽象類AbstractAuthenticationProcessingFilter的一個(gè)實(shí)現(xiàn),而BasicAuthenticationFilter為抽象類BasicAuthenticationFilter的一個(gè)實(shí)現(xiàn)。這四個(gè)類的源碼提供了不錯(cuò)的前臺(tái)(Filter)實(shí)現(xiàn)思路。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 提供了認(rèn)證前后需要做的事情,其子類只需要提供實(shí)現(xiàn)完成認(rèn)證的抽象方法attemptAuthentication(HttpServletRequest, HttpServletResponse)即可。使用AbstractAuthenticationProcessingFilter時(shí),需要提供一個(gè)攔截路徑(使用AntPathMatcher進(jìn)行匹配)來(lái)攔截對(duì)應(yīng)的特定的路徑。

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter作為實(shí)際的前臺(tái),會(huì)將客戶端提交的username和password封裝成一個(gè)UsernamePasswordAuthenticationToken交給認(rèn)證管理部門(mén)(AuthenticationManager)進(jìn)行認(rèn)證。如此,她的任務(wù)就完成了。

BasicAuthenticationFilter
前臺(tái)(Filter)只會(huì)處理含有Authorization的Header,且小寫(xiě)化后的值以basic開(kāi)頭的請(qǐng)求,否則該前臺(tái)(Filter)不負(fù)責(zé)處理。該Filter會(huì)從header中獲取Base64編碼之后的username和password,創(chuàng)建UsernamePasswordAuthenticationToken提供給認(rèn)證管理部門(mén)(AuthenticationMananager)進(jìn)行認(rèn)證。

認(rèn)證資料(Authentication)

前臺(tái)接到請(qǐng)求之后,會(huì)從請(qǐng)求中獲取所需的信息,創(chuàng)建自家認(rèn)證部門(mén)(AuthenticationProvider)所認(rèn)識(shí)的認(rèn)證資料(Authentication),認(rèn)證部門(mén)(AuthenticationProvider)則主要是通過(guò)認(rèn)證資料(Authentication)的類型判斷是否由該部門(mén)處理。

public interface Authentication extends Principal, Serializable {
  
  // 該principal具有的權(quán)限。AuthorityUtils工具類提供了一些方便的方法。
  Collection<? extends GrantedAuthority> getAuthorities();
  // 證明Principal的身份的證書(shū),比如密碼。
  Object getCredentials();
  // authentication request的附加信息,比如ip。
  Object getDetails();
  // 當(dāng)事人。在username+password模式中為username,在有userDetails之后可以為userDetails。
  Object getPrincipal();
  // 是否已經(jīng)通過(guò)認(rèn)證。
  boolean isAuthenticated();
  // 設(shè)置通過(guò)認(rèn)證。
  void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication被認(rèn)證之后,會(huì)保存到一個(gè)thread-local的SecurityContext中。

// 設(shè)置
SecurityContextHolder.getContext().setAuthentication(anAuthentication);
// 獲取
Authentication existingAuth = SecurityContextHolder.getContext()
        .getAuthentication();

在寫(xiě)前臺(tái)Filter的時(shí)候,可以先檢查SecurityContextHolder.getContext()中是否已經(jīng)存在通過(guò)認(rèn)證的Authentication了,如果存在,則可以直接跳過(guò)該Filter。已經(jīng)通過(guò)驗(yàn)證的Authentication建議設(shè)置為一個(gè)不可修改的實(shí)例。

目前從Authentication的類圖中看到的實(shí)現(xiàn)類,均為Authentication的抽象子類AbstractAuthenticationToken的實(shí)現(xiàn)類。實(shí)現(xiàn)類有好幾個(gè),與前面的講到的Filter相關(guān)的有UsernamePasswordAuthenticationTokenRememberMeAuthenticationToken。

AbstractAuthenticationTokenCredentialsContainerAuthentication的子類。實(shí)現(xiàn)了一些簡(jiǎn)單的方法,但主要的方法還需要實(shí)現(xiàn)。該類的getName()方法的實(shí)現(xiàn)可以看到常用的principal類為UserDetails、AuthenticationPrincipalPrincial。如果有需要將對(duì)象設(shè)置為principal,可以考慮繼承這三個(gè)類中的一個(gè)。

public String getName() {
  if (this.getPrincipal() instanceof UserDetails) {
    return ((UserDetails) this.getPrincipal()).getUsername();
  }
  if (this.getPrincipal() instanceof AuthenticatedPrincipal) {
    return ((AuthenticatedPrincipal) this.getPrincipal()).getName();
  }
  if (this.getPrincipal() instanceof Principal) {
    return ((Principal) this.getPrincipal()).getName();
  }

  return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();
}

認(rèn)證管理部門(mén)(AuthenticationManager)

AuthenticationManager是一個(gè)接口,認(rèn)證Authentication,如果認(rèn)證通過(guò)之后,返回的Authentication應(yīng)該帶上該principal所具有的GrantedAuthority。

public interface AuthenticationManager {
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
}

該接口的注釋中說(shuō)明,必須按照如下的異常順序進(jìn)行檢查和拋出:

  • DisabledException:賬號(hào)不可用
  • LockedException:賬號(hào)被鎖
  • BadCredentialsException:證書(shū)不正確

Spring Security提供一個(gè)默認(rèn)的實(shí)現(xiàn)ProviderManager。認(rèn)證管理部門(mén)(ProviderManager)僅執(zhí)行管理職能,具體的認(rèn)證職能由認(rèn)證部門(mén)(AuthenticationProvider)執(zhí)行。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
    InitializingBean {
  ...

  public ProviderManager(List<AuthenticationProvider> providers) {
    this(providers, null);
  }

  public ProviderManager(List<AuthenticationProvider> providers,
      AuthenticationManager parent) {
    Assert.notNull(providers, "providers list cannot be null");
    this.providers = providers;
    this.parent = parent;
    checkState();
  }

  public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();

    for (AuthenticationProvider provider : getProviders()) {
      // #1, 檢查是否由該認(rèn)證部門(mén)進(jìn)行認(rèn)證`AuthenticationProvider`
      if (!provider.supports(toTest)) {
        continue;
      }

      if (debug) {
        logger.debug("Authentication attempt using "
            + provider.getClass().getName());
      }

      try {
        // #2, 認(rèn)證部門(mén)進(jìn)行認(rèn)證
        result = provider.authenticate(authentication);

        if (result != null) {
          copyDetails(authentication, result);
          // #3,認(rèn)證通過(guò)則不再進(jìn)行下一個(gè)認(rèn)證部門(mén)的認(rèn)證,否則拋出的異常被捕獲,執(zhí)行下一個(gè)認(rèn)證部門(mén)(AuthenticationProvider)
          break;
        }
      }
      catch (AccountStatusException e) {
        prepareException(e, authentication);
        // SEC-546: Avoid polling additional providers if auth failure is due to
        // invalid account status
        throw e;
      }
      catch (InternalAuthenticationServiceException e) {
        prepareException(e, authentication);
        throw e;
      }
      catch (AuthenticationException e) {
        lastException = e;
      }
    }

    if (result == null && parent != null) {
      // Allow the parent to try.
      try {
        result = parentResult = parent.authenticate(authentication);
      }
      catch (ProviderNotFoundException e) {
        // ignore as we will throw below if no other exception occurred prior to
        // calling parent and the parent
        // may throw ProviderNotFound even though a provider in the child already
        // handled the request
      }
      catch (AuthenticationException e) {
        lastException = parentException = e;
      }
    }
    // #4, 如果認(rèn)證通過(guò),執(zhí)行認(rèn)證通過(guò)之后的操作
    if (result != null) {
      if (eraseCredentialsAfterAuthentication
          && (result instanceof CredentialsContainer)) {
        // Authentication is complete. Remove credentials and other secret data
        // from authentication
        ((CredentialsContainer) result).eraseCredentials();
      }

      // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
      // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
      if (parentResult == null) {
        eventPublisher.publishAuthenticationSuccess(result);
      }
      return result;
    }

    // Parent was null, or didn't authenticate (or throw an exception).
    // #5,如果認(rèn)證不通過(guò),必然有拋出異常,否則表示沒(méi)有配置相應(yīng)的認(rèn)證部門(mén)(AuthenticationProvider)
    if (lastException == null) {
      lastException = new ProviderNotFoundException(messages.getMessage(
          "ProviderManager.providerNotFound",
          new Object[] { toTest.getName() },
          "No AuthenticationProvider found for {0}"));
    }

    // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
    // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
    if (parentException == null) {
      prepareException(lastException, authentication);
    }

    throw lastException;
  }
  ...
}

遍歷所有的認(rèn)證部門(mén)(AuthenticationProvider),找到支持的認(rèn)證部門(mén)進(jìn)行認(rèn)證認(rèn)證部門(mén)進(jìn)行認(rèn)證認(rèn)證通過(guò)則不再進(jìn)行下一個(gè)認(rèn)證部門(mén)的認(rèn)證,否則拋出的異常被捕獲,執(zhí)行下一個(gè)認(rèn)證部門(mén)(AuthenticationProvider)如果認(rèn)證通過(guò),執(zhí)行認(rèn)證通過(guò)之后的操作如果認(rèn)證不通過(guò),必然有拋出異常,否則表示沒(méi)有配置相應(yīng)的認(rèn)證部門(mén)(AuthenticationProvider)

當(dāng)使用到Spring Security OAuth2的時(shí)候,會(huì)看到另一個(gè)實(shí)現(xiàn)OAuth2AuthenticationManager。

認(rèn)證部門(mén)(AuthenticationProvider)

認(rèn)證部門(mén)(AuthenticationProvider)負(fù)責(zé)實(shí)際的認(rèn)證工作,與認(rèn)證管理部門(mén)(ProvderManager)協(xié)同工作。也許其他的認(rèn)證管理部門(mén)(AuthenticationManager)并不需要認(rèn)證部門(mén)(AuthenticationProvider)的協(xié)作。

public interface AuthenticationProvider {
  // 進(jìn)行認(rèn)證
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
  // 是否由該AuthenticationProvider進(jìn)行認(rèn)證
  boolean supports(Class<?> authentication);
}

該接口有很多的實(shí)現(xiàn)類,其中包含了RememberMeAuthenticationProvider(直接AuthenticationProvider)和DaoAuthenticationProvider(通過(guò)AbastractUserDetailsAuthenticationProvider簡(jiǎn)介繼承)。這里重點(diǎn)講講AbastractUserDetailsAuthenticationProviderDaoAuthenticationProvider。

AbastractUserDetailsAuthenticationProvider

顧名思義,AbastractUserDetailsAuthenticationProvider是對(duì)UserDetails支持的Provider,其他的Provider,如RememberMeAuthenticationProvider就不需要用到UserDetails。該抽象類有兩個(gè)抽象方法需要實(shí)現(xiàn)類完成:

// 獲取 UserDetails
protected abstract UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

retrieveUser()方法為校驗(yàn)提供UserDetails。先看下UserDetails:

public interface UserDetails extends Serializable {
  
  Collection<? extends GrantedAuthority> getAuthorities();

  String getPassword();
  
  String getUsername();
  // 賬號(hào)是否過(guò)期
  boolean isAccountNonExpired();
  // 賬號(hào)是否被鎖
  boolean isAccountNonLocked();
  // 證書(shū)(password)是否過(guò)期
  boolean isCredentialsNonExpired();
  // 賬號(hào)是否可用
  boolean isEnabled();
}

AbastractUserDetailsAuthenticationProvider#authentication(Authentication)分為三步驗(yàn)證:

  • preAuthenticationChecks.check(user);
  • additionalAuthenticationChecks(user,
  • (UsernamePasswordAuthenticationToken) authentication);
  • postAuthenticationChecks.check(user);

preAuthenticationChecks的默認(rèn)實(shí)現(xiàn)為DefaultPreAuthenticationChecks,負(fù)責(zé)完成校驗(yàn):

  1. UserDetails#isAccountNonLocked()
  2. UserDetails#isEnabled()
  3. UserDetails#isAccountNonExpired()

postAuthenticationChecks的默認(rèn)實(shí)現(xiàn)為DefaultPostAuthenticationChecks,負(fù)責(zé)完成校驗(yàn):

UserDetails#user.isCredentialsNonExpired()

additionalAuthenticationChecks需要由實(shí)現(xiàn)類完成。

校驗(yàn)成功之后,AbstractUserDetailsAuthenticationProvider會(huì)創(chuàng)建并返回一個(gè)通過(guò)認(rèn)證的Authentication。

protected Authentication createSuccessAuthentication(Object principal,
    Authentication authentication, UserDetails user) {
  // Ensure we return the original credentials the user supplied,
  // so subsequent attempts are successful even with encoded passwords.
  // Also ensure we return the original getDetails(), so that future
  // authentication events after cache expiry contain the details
  UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
      principal, authentication.getCredentials(),
      authoritiesMapper.mapAuthorities(user.getAuthorities()));
  result.setDetails(authentication.getDetails());

  return result;
}

DaoAuthenticationProvider

如下為DaoAuthenticationProvider對(duì)AbstractUserDetailsAuthenticationProvider抽象方法的實(shí)現(xiàn)。

// 檢查密碼是否正確
protected void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  if (authentication.getCredentials() == null) {
    logger.debug("Authentication failed: no credentials provided");

    throw new BadCredentialsException(messages.getMessage(
        "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }

  String presentedPassword = authentication.getCredentials().toString();

  if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    logger.debug("Authentication failed: password does not match stored value");

    throw new BadCredentialsException(messages.getMessage(
        "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }
}
// 通過(guò)資料室(UserDetailsService)獲取UserDetails對(duì)象
protected final UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  prepareTimingAttackProtection();
  try {
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    if (loadedUser == null) {
      throw new InternalAuthenticationServiceException(
          "UserDetailsService returned null, which is an interface contract violation");
    }
    return loadedUser;
  }
  ...
}

在以上的代碼中,需要提供UserDetailsServicePasswordEncoder實(shí)例。只要實(shí)例化這兩個(gè)類,并放入到Spring容器中即可。

資料部門(mén)(UserDetailsService)

UserDetailsService接口提供認(rèn)證過(guò)程所需的UserDetails的類,如DaoAuthenticationProvider需要一個(gè)UserDetailsService實(shí)例。

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

Spring Security提供了兩個(gè)UserDetailsService的實(shí)現(xiàn):InMemoryUserDetailsManagerJdbcUserDetailsManager。InMemoryUserDetailsManager為默認(rèn)配置,從UserDetailsServiceAutoConfiguration的配置中可以看出。當(dāng)然也不容易理解,基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)需要增加數(shù)據(jù)庫(kù)的配置,不適合做默認(rèn)實(shí)現(xiàn)。這兩個(gè)類均為UserDetailsManager的實(shí)現(xiàn)類,UserDetailsManager定義了UserDetails的CRUD操作。InMemoryUserDetailsManager使用Map<String, MutableUserDetails>做存儲(chǔ)。

public interface UserDetailsManager extends UserDetailsService {
  void createUser(UserDetails user);

  void updateUser(UserDetails user);

  void deleteUser(String username);

  void changePassword(String oldPassword, String newPassword);

  boolean userExists(String username);
}

如果我們需要增加一個(gè)UserDetailsService,可以考慮實(shí)現(xiàn)UserDetailsService或者UserDetailsManager。

增加一個(gè)認(rèn)證流程

到這里,我們已經(jīng)知道Spring Security的流程了。從上面的內(nèi)容可以知道,如要增加一個(gè)新的認(rèn)證方式,只要增加一個(gè)[前臺(tái)(Filter) + 認(rèn)證部門(mén)(AuthenticationProvider) + 資料室(UserDetailsService)]組合即可。事實(shí)上,資料室(UserDetailsService)不是必須的,可根據(jù)認(rèn)證部門(mén)(AuthenticationProvider)需要實(shí)現(xiàn)。

我會(huì)在另一篇文章中以手機(jī)號(hào)碼+驗(yàn)證碼登錄為例進(jìn)行講解。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • java利用easyexcel實(shí)現(xiàn)導(dǎo)入與導(dǎo)出功能

    java利用easyexcel實(shí)現(xiàn)導(dǎo)入與導(dǎo)出功能

    這篇文章主要介紹了java利用easyexcel實(shí)現(xiàn)導(dǎo)入與導(dǎo)出功能,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-09-09
  • Spring Boot實(shí)現(xiàn)微信小程序登錄

    Spring Boot實(shí)現(xiàn)微信小程序登錄

    這篇文章主要為大家詳細(xì)介紹了Spring Boot實(shí)現(xiàn)微信小程序登錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • java網(wǎng)絡(luò)通信技術(shù)之簡(jiǎn)單聊天小程序

    java網(wǎng)絡(luò)通信技術(shù)之簡(jiǎn)單聊天小程序

    這篇文章主要為大家詳細(xì)介紹了java網(wǎng)絡(luò)通信技術(shù)之簡(jiǎn)單聊天小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • 一文搞懂SpringMVC中@InitBinder注解的使用

    一文搞懂SpringMVC中@InitBinder注解的使用

    @InitBinder方法可以注冊(cè)控制器特定的java.bean.PropertyEditor或Spring Converter和 Formatter組件。本文通過(guò)示例為大家詳細(xì)講講@InitBinder注解的使用,需要的可以參考一下
    2022-06-06
  • 詳解Java中Math.round()的取整規(guī)則

    詳解Java中Math.round()的取整規(guī)則

    這篇文章主要介紹了詳解Java中Math.round()的取整規(guī)則,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • Java利用策略模式實(shí)現(xiàn)條件判斷,告別if else

    Java利用策略模式實(shí)現(xiàn)條件判斷,告別if else

    策略模式定義了一系列算法,并且將每個(gè)算法封裝起來(lái),使得他們可以相互替換,而且算法的變化不會(huì)影響使用算法的客戶端。本文將通過(guò)案例講解如何利用Java的策略模式實(shí)現(xiàn)條件判斷,告別if----else條件硬編碼,需要的可以參考一下
    2022-02-02
  • Spring注解之@Import的簡(jiǎn)單介紹

    Spring注解之@Import的簡(jiǎn)單介紹

    @Import是Spring基于Java注解配置的主要組成部分,下面這篇文章主要給大家介紹了關(guān)于Spring注解之@Import的簡(jiǎn)單介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • SpringCloud Hystrix的使用

    SpringCloud Hystrix的使用

    這篇文章主要介紹了SpringCloud Hystrix的使用,幫助大家更好的理解和學(xué)習(xí)使用SpringCloud,感興趣的朋友可以了解下
    2021-04-04
  • Java?Chassis3熔斷機(jī)制的改進(jìn)路程技術(shù)解密

    Java?Chassis3熔斷機(jī)制的改進(jìn)路程技術(shù)解密

    這篇文章主要介紹了Java?Chassis?3技術(shù)解密之熔斷機(jī)制的改進(jìn)路程實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • springboot中如何配置LocalDateTime JSON返回時(shí)間戳

    springboot中如何配置LocalDateTime JSON返回時(shí)間戳

    這篇文章主要介紹了springboot中如何配置LocalDateTime JSON返回時(shí)間戳問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06

最新評(píng)論