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

Spring Security架構(gòu)以及源碼詳析

 更新時(shí)間:2018年06月08日 10:08:39   作者:JadePeng  
這篇文章主要給大家介紹了關(guān)于Spring Security架構(gòu)以及源碼的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

現(xiàn)在流行的通用授權(quán)框架有apache的shiro和Spring家族的Spring Security,在涉及今天的微服務(wù)鑒權(quán)時(shí),需要利用我們的授權(quán)框架搭建自己的鑒權(quán)服務(wù),今天總理了Spring Security。

Spring Security 主要實(shí)現(xiàn)了Authentication(認(rèn)證,解決who are you? ) 和 Access Control(訪問控制,也就是what are you allowed to do?,也稱為Authorization)。Spring Security在架構(gòu)上將認(rèn)證與授權(quán)分離,并提供了擴(kuò)展點(diǎn)。

核心對象

主要代碼在spring-security-core包下面。要了解Spring Security,需要先關(guān)注里面的核心對象。

SecurityContextHolder, SecurityContext 和 Authentication

SecurityContextHolder 是 SecurityContext的存放容器,默認(rèn)使用ThreadLocal 存儲(chǔ),意味SecurityContext在相同線程中的方法都可用。

SecurityContext主要是存儲(chǔ)應(yīng)用的principal信息,在Spring Security中用Authentication 來表示。

獲取principal:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

在Spring Security中,可以看一下Authentication定義:

public interface Authentication extends Principal, Serializable {
 Collection<? extends GrantedAuthority> getAuthorities();
 /**
 * 通常是密碼
 */
 Object getCredentials();
 /**
 * Stores additional details about the authentication request. These might be an IP
 * address, certificate serial number etc.
 */
 Object getDetails();

 /**
 * 用來標(biāo)識(shí)是否已認(rèn)證,如果使用用戶名和密碼登錄,通常是用戶名 
 */
 Object getPrincipal();
 /**
 * 是否已認(rèn)證
 */
 boolean isAuthenticated();
 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

在實(shí)際應(yīng)用中,通常使用UsernamePasswordAuthenticationToken:

public abstract class AbstractAuthenticationToken implements Authentication,
 CredentialsContainer {
 }
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
}

一個(gè)常見的認(rèn)證過程通常是這樣的,創(chuàng)建一個(gè)UsernamePasswordAuthenticationToken,然后交給authenticationManager認(rèn)證(后面詳細(xì)說明),認(rèn)證通過則通過SecurityContextHolder存放Authentication信息。

 UsernamePasswordAuthenticationToken authenticationToken =
 new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());

Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);

UserDetails與UserDetailsService

UserDetails 是Spring Security里的一個(gè)關(guān)鍵接口,他用來表示一個(gè)principal。

public interface UserDetails extends Serializable {
 /**
 * 用戶的授權(quán)信息,可以理解為角色
 */
 Collection<? extends GrantedAuthority> getAuthorities();
 /**
 * 用戶密碼
 *
 * @return the password
 */
 String getPassword();
 /**
 * 用戶名 
 * */
 String getUsername();
 boolean isAccountNonExpired();
 boolean isAccountNonLocked();
 boolean isCredentialsNonExpired();
 boolean isEnabled();
}

UserDetails提供了認(rèn)證所需的必要信息,在實(shí)際使用里,可以自己實(shí)現(xiàn)UserDetails,并增加額外的信息,比如email、mobile等信息。

在Authentication中的principal通常是用戶名,我們可以通過UserDetailsService來通過principal獲取UserDetails:

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

GrantedAuthority

在UserDetails里說了,GrantedAuthority可以理解為角色,例如 ROLE_ADMINISTRATOR or ROLE_HR_SUPERVISOR。

小結(jié)

  • SecurityContextHolder, 用來訪問 SecurityContext.
  • SecurityContext, 用來存儲(chǔ)Authentication .
  • Authentication, 代表憑證.
  • GrantedAuthority, 代表權(quán)限.
  • UserDetails, 用戶信息.
  • UserDetailsService,獲取用戶信息.

Authentication認(rèn)證

AuthenticationManager

實(shí)現(xiàn)認(rèn)證主要是通過AuthenticationManager接口,它只包含了一個(gè)方法:

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

authenticate()方法主要做三件事:

  • 如果驗(yàn)證通過,返回Authentication(通常帶上authenticated=true)。
  • 認(rèn)證失敗拋出AuthenticationException
  • 如果無法確定,則返回null

AuthenticationException是運(yùn)行時(shí)異常,它通常由應(yīng)用程序按通用方式處理,用戶代碼通常不用特意被捕獲和處理這個(gè)異常。

AuthenticationManager的默認(rèn)實(shí)現(xiàn)是ProviderManager,它委托一組AuthenticationProvider實(shí)例來實(shí)現(xiàn)認(rèn)證。
AuthenticationProvider和AuthenticationManager類似,都包含authenticate,但它有一個(gè)額外的方法supports,以允許查詢調(diào)用方是否支持給定Authentication類型:

public interface AuthenticationProvider {
 Authentication authenticate(Authentication authentication)
 throws AuthenticationException;
 boolean supports(Class<?> authentication);
}

ProviderManager包含一組AuthenticationProvider,執(zhí)行authenticate時(shí),遍歷Providers,然后調(diào)用supports,如果支持,則執(zhí)行遍歷當(dāng)前provider的authenticate方法,如果一個(gè)provider認(rèn)證成功,則break。

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

 for (AuthenticationProvider provider : getProviders()) {
 if (!provider.supports(toTest)) {
 continue;
 }

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

 try {
 result = provider.authenticate(authentication);

 if (result != null) {
  copyDetails(authentication, result);
  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 = 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 = e;
 }
 }

 if (result != null) {
 if (eraseCredentialsAfterAuthentication
  && (result instanceof CredentialsContainer)) {
 // Authentication is complete. Remove credentials and other secret data
 // from authentication
 ((CredentialsContainer) result).eraseCredentials();
 }
 eventPublisher.publishAuthenticationSuccess(result);
 return result;
 }

 // Parent was null, or didn't authenticate (or throw an exception).
 if (lastException == null) {
 lastException = new ProviderNotFoundException(messages.getMessage(
  "ProviderManager.providerNotFound",
  new Object[] { toTest.getName() },
  "No AuthenticationProvider found for {0}"));
 }
 prepareException(lastException, authentication);
 throw lastException;
 }

從上面的代碼可以看出, ProviderManager有一個(gè)可選parent,如果parent不為空,則調(diào)用parent.authenticate(authentication)

AuthenticationProvider

AuthenticationProvider有多種實(shí)現(xiàn),大家最關(guān)注的通常是DaoAuthenticationProvider,繼承于AbstractUserDetailsAuthenticationProvider,核心是通過UserDetails來實(shí)現(xiàn)認(rèn)證,DaoAuthenticationProvider默認(rèn)會(huì)自動(dòng)加載,不用手動(dòng)配。

先來看AbstractUserDetailsAuthenticationProvider,看最核心的authenticate:

public Authentication authenticate(Authentication authentication)
 throws AuthenticationException {
 // 必須是UsernamePasswordAuthenticationToken
 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
 messages.getMessage(
  "AbstractUserDetailsAuthenticationProvider.onlySupports",
  "Only UsernamePasswordAuthenticationToken is supported"));

 // 獲取用戶名
 String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
 : authentication.getName();

 boolean cacheWasUsed = true;
 // 從緩存獲取
 UserDetails user = this.userCache.getUserFromCache(username);

 if (user == null) {
 cacheWasUsed = false;

 try {
 // retrieveUser 抽象方法,獲取用戶
 user = retrieveUser(username,
  (UsernamePasswordAuthenticationToken) authentication);
 }
 catch (UsernameNotFoundException notFound) {
 logger.debug("User '" + username + "' not found");

 if (hideUserNotFoundExceptions) {
  throw new BadCredentialsException(messages.getMessage(
  "AbstractUserDetailsAuthenticationProvider.badCredentials",
  "Bad credentials"));
 }
 else {
  throw notFound;
 }
 }
 
 Assert.notNull(user,
  "retrieveUser returned null - a violation of the interface contract");
 }

 try {
 // 預(yù)先檢查,DefaultPreAuthenticationChecks,檢查用戶是否被lock或者賬號(hào)是否可用
 preAuthenticationChecks.check(user);
 
 // 抽象方法,自定義檢驗(yàn)
 additionalAuthenticationChecks(user,
  (UsernamePasswordAuthenticationToken) authentication);
 }
 catch (AuthenticationException exception) {
 if (cacheWasUsed) {
 // There was a problem, so try again after checking
 // we're using latest data (i.e. not from the cache)
 cacheWasUsed = false;
 user = retrieveUser(username,
  (UsernamePasswordAuthenticationToken) authentication);
 preAuthenticationChecks.check(user);
 additionalAuthenticationChecks(user,
  (UsernamePasswordAuthenticationToken) authentication);
 }
 else {
 throw exception;
 }
 }
 
 // 后置檢查 DefaultPostAuthenticationChecks,檢查isCredentialsNonExpired
 postAuthenticationChecks.check(user);
 if (!cacheWasUsed) {
 this.userCache.putUserInCache(user);
 }

 Object principalToReturn = user;
 if (forcePrincipalAsString) {
 principalToReturn = user.getUsername();
 }
 
 return createSuccessAuthentication(principalToReturn, authentication, user);
 }

上面的檢驗(yàn)主要基于UserDetails實(shí)現(xiàn),其中獲取用戶和檢驗(yàn)邏輯由具體的類去實(shí)現(xiàn),默認(rèn)實(shí)現(xiàn)是DaoAuthenticationProvider,這個(gè)類的核心是讓開發(fā)者提供UserDetailsService來獲取UserDetails以及 PasswordEncoder來檢驗(yàn)密碼是否有效:

private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;

看具體的實(shí)現(xiàn),retrieveUser,直接調(diào)用userDetailsService獲取用戶:

protected final UserDetails retrieveUser(String username,
 UsernamePasswordAuthenticationToken authentication)
 throws AuthenticationException {
 UserDetails loadedUser;

 try {
 loadedUser = this.getUserDetailsService().loadUserByUsername(username);
 }
 catch (UsernameNotFoundException notFound) {
 if (authentication.getCredentials() != null) {
 String presentedPassword = authentication.getCredentials().toString();
 passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
  presentedPassword, null);
 }
 throw notFound;
 }
 catch (Exception repositoryProblem) {
 throw new InternalAuthenticationServiceException(
  repositoryProblem.getMessage(), repositoryProblem);
 }

 if (loadedUser == null) {
 throw new InternalAuthenticationServiceException(
  "UserDetailsService returned null, which is an interface contract violation");
 }
 return loadedUser;
 }

再來看驗(yàn)證:

protected void additionalAuthenticationChecks(UserDetails userDetails,
 UsernamePasswordAuthenticationToken authentication)
 throws AuthenticationException {
 Object salt = null;

 if (this.saltSource != null) {
 salt = this.saltSource.getSalt(userDetails);
 }

 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();
 // 比較passwordEncoder后的密碼是否和userdetails的密碼一致
 if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
 presentedPassword, salt)) {
 logger.debug("Authentication failed: password does not match stored value");

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

小結(jié):要自定義認(rèn)證,使用DaoAuthenticationProvider,只需要為其提供PasswordEncoder和UserDetailsService就可以了。

定制 Authentication Managers

Spring Security提供了一個(gè)Builder類AuthenticationManagerBuilder,借助它可以快速實(shí)現(xiàn)自定義認(rèn)證。

看官方源碼說明:

SecurityBuilder used to create an AuthenticationManager . Allows for easily building in memory authentication, LDAP authentication, JDBC based authentication, adding UserDetailsService , and adding AuthenticationProvider's.

AuthenticationManagerBuilder可以用來Build一個(gè)AuthenticationManager,可以創(chuàng)建基于內(nèi)存的認(rèn)證、LDAP認(rèn)證、 JDBC認(rèn)證,以及添加UserDetailsService和AuthenticationProvider。

簡單使用:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
 public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService,TokenProvider tokenProvider,CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
 this.authenticationManagerBuilder = authenticationManagerBuilder;
 this.userDetailsService = userDetailsService;
 this.tokenProvider = tokenProvider;
 this.corsFilter = corsFilter;
 this.problemSupport = problemSupport;
 }

 @PostConstruct
 public void init() {
 try {
 authenticationManagerBuilder
 .userDetailsService(userDetailsService)
 .passwordEncoder(passwordEncoder());
 } catch (Exception e) {
 throw new BeanInitializationException("Security configuration failed", e);
 }
 }

 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http
 .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
 .exceptionHandling()
 .authenticationEntryPoint(problemSupport)
 .accessDeniedHandler(problemSupport)
 .and()
 .csrf()
 .disable()
 .headers()
 .frameOptions()
 .disable()
 .and()
 .sessionManagement()
 .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 .and()
 .authorizeRequests()
 .antMatchers("/api/register").permitAll()
 .antMatchers("/api/activate").permitAll()
 .antMatchers("/api/authenticate").permitAll()
 .antMatchers("/api/account/reset-password/init").permitAll()
 .antMatchers("/api/account/reset-password/finish").permitAll()
 .antMatchers("/api/profile-info").permitAll()
 .antMatchers("/api/**").authenticated()
 .antMatchers("/management/health").permitAll()
 .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
 .antMatchers("/v2/api-docs/**").permitAll()
 .antMatchers("/swagger-resources/configuration/ui").permitAll()
 .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN)
 .and()
 .apply(securityConfigurerAdapter());
 }
}

授權(quán)與訪問控制

一旦認(rèn)證成功,我們可以繼續(xù)進(jìn)行授權(quán),授權(quán)是通過AccessDecisionManager來實(shí)現(xiàn)的??蚣苡腥N實(shí)現(xiàn),默認(rèn)是AffirmativeBased,通過AccessDecisionVoter決策,有點(diǎn)像ProviderManager委托給AuthenticationProviders來認(rèn)證。

public void decide(Authentication authentication, Object object,
 Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
 int deny = 0;
 // 遍歷DecisionVoter 
 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();
 }

來看AccessDecisionVoter:

boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
 Collection<ConfigAttribute> attributes);

object是用戶要訪問的資源,ConfigAttribute則是訪問object要滿足的條件,通常payload是字符串,比如ROLE_ADMIN 。所以我們來看下RoleVoter的實(shí)現(xiàn),其核心就是從authentication提取出GrantedAuthority,然后和ConfigAttribute比較是否滿足條件。

public boolean supports(ConfigAttribute attribute) {
 if ((attribute.getAttribute() != null)
 && attribute.getAttribute().startsWith(getRolePrefix())) {
 return true;
 }
 else {
 return false;
 }
 }
 
public boolean supports(Class<?> clazz) {
 return true;
 }

public int vote(Authentication authentication, Object object,
 Collection<ConfigAttribute> attributes) {
 if(authentication == null) {
 return ACCESS_DENIED;
 }
 int result = ACCESS_ABSTAIN;
 
 // 獲取GrantedAuthority信息
 Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);

 for (ConfigAttribute attribute : attributes) {
 if (this.supports(attribute)) {
 // 默認(rèn)拒絕訪問
 result = ACCESS_DENIED;

 // Attempt to find a matching granted authority
 for (GrantedAuthority authority : authorities) {
  // 判斷是否有匹配的 authority
  if (attribute.getAttribute().equals(authority.getAuthority())) {
  // 可訪問
  return ACCESS_GRANTED;
  }
 }
 }
 }
 return result;
 }

這里要疑問,ConfigAttribute哪來的?其實(shí)就是上面ApplicationSecurity的configure里的。

web security 如何實(shí)現(xiàn)

Web層中的Spring Security(用于UI和HTTP后端)基于Servlet Filters,下圖顯示了單個(gè)HTTP請求的處理程序的典型分層。

Spring Security通過FilterChainProxy作為單一的Filter注冊到web層,Proxy內(nèi)部的Filter。

FilterChainProxy相當(dāng)于一個(gè)filter的容器,通過VirtualFilterChain來依次調(diào)用各個(gè)內(nèi)部filter

public void doFilter(ServletRequest request, ServletResponse response,
  FilterChain chain) throws IOException, ServletException {
 boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
 if (clearContext) {
  try {
  request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  doFilterInternal(request, response, chain);
  }
  finally {
  SecurityContextHolder.clearContext();
  request.removeAttribute(FILTER_APPLIED);
  }
 }
 else {
  doFilterInternal(request, response, chain);
 }
 }

 private void doFilterInternal(ServletRequest request, ServletResponse response,
  FilterChain chain) throws IOException, ServletException {

 FirewalledRequest fwRequest = firewall
  .getFirewalledRequest((HttpServletRequest) request);
 HttpServletResponse fwResponse = firewall
  .getFirewalledResponse((HttpServletResponse) response);

 List<Filter> filters = getFilters(fwRequest);

 if (filters == null || filters.size() == 0) {
  if (logger.isDebugEnabled()) {
  logger.debug(UrlUtils.buildRequestUrl(fwRequest)
   + (filters == null ? " has no matching filters"
    : " has an empty filter list"));
  }

  fwRequest.reset();

  chain.doFilter(fwRequest, fwResponse);

  return;
 }

 VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
 vfc.doFilter(fwRequest, fwResponse);
 }
 
 private static class VirtualFilterChain implements FilterChain {
 private final FilterChain originalChain;
 private final List<Filter> additionalFilters;
 private final FirewalledRequest firewalledRequest;
 private final int size;
 private int currentPosition = 0;

 private VirtualFilterChain(FirewalledRequest firewalledRequest,
  FilterChain chain, List<Filter> additionalFilters) {
  this.originalChain = chain;
  this.additionalFilters = additionalFilters;
  this.size = additionalFilters.size();
  this.firewalledRequest = firewalledRequest;
 }

 public void doFilter(ServletRequest request, ServletResponse response)
  throws IOException, ServletException {
  if (currentPosition == size) {
  if (logger.isDebugEnabled()) {
   logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
    + " reached end of additional filter chain; proceeding with original chain");
  }

  // Deactivate path stripping as we exit the security filter chain
  this.firewalledRequest.reset();

  originalChain.doFilter(request, response);
  }
  else {
  currentPosition++;

  Filter nextFilter = additionalFilters.get(currentPosition - 1);

  if (logger.isDebugEnabled()) {
   logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
    + " at position " + currentPosition + " of " + size
    + " in additional filter chain; firing Filter: '"
    + nextFilter.getClass().getSimpleName() + "'");
  }

  nextFilter.doFilter(request, response, this);
  }
 }
 }

參考

https://spring.io/guides/topicals/spring-security-architecture/

https://docs.spring.io/spring-security/site/docs/5.0.5.RELEASE/reference/htmlsingle/#overall-architecture

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼

    SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼

    本文主要介紹了SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • SpringBoot整合MybatisPlus實(shí)現(xiàn)增刪改查功能

    SpringBoot整合MybatisPlus實(shí)現(xiàn)增刪改查功能

    MybatisPlus是國產(chǎn)的第三方插件,?它封裝了許多常用的CURDapi,免去了我們寫mapper.xml的重復(fù)勞動(dòng)。本文將整合MybatisPlus實(shí)現(xiàn)增刪改查功能,感興趣的可以了解一下
    2022-05-05
  • Java web spring異步方法實(shí)現(xiàn)步驟解析

    Java web spring異步方法實(shí)現(xiàn)步驟解析

    這篇文章主要介紹了Java web spring異步方法實(shí)現(xiàn)步驟解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java中LinkedList的模擬實(shí)現(xiàn)

    Java中LinkedList的模擬實(shí)現(xiàn)

    本文主要介紹了Java中LinkedList的模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 導(dǎo)致MyEclipse內(nèi)存不足的原因分析及解決辦法

    導(dǎo)致MyEclipse內(nèi)存不足的原因分析及解決辦法

    這篇文章主要介紹了導(dǎo)致MyEclipse內(nèi)存不足的原因分析及解決辦法的相關(guān)資料,需要的朋友可以參考下
    2016-01-01
  • Java IO流之Properties類的使用

    Java IO流之Properties類的使用

    這篇文章主要介紹了Java IO流之Properties類的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • 案例講解SpringBoot?Starter的使用教程

    案例講解SpringBoot?Starter的使用教程

    SpringBoot中的starter是一種非常重要的機(jī)制,能夠拋棄以前繁雜的配置,將其統(tǒng)一集成進(jìn)starter,應(yīng)用者只需要在maven中引入starter依賴,SpringBoot就能自動(dòng)掃描到要加載的信息并啟動(dòng)相應(yīng)的默認(rèn)配置,本文通過案例講解SpringBoot?Starter的使用,感興趣的朋友一起看看吧
    2023-12-12
  • 簡單了解Spring中的事務(wù)控制

    簡單了解Spring中的事務(wù)控制

    這篇文章主要介紹了簡單了解Spring中的事務(wù)控制,事務(wù)是一組操作的執(zhí)行單元,下面我們來簡單學(xué)習(xí)一下吧
    2019-05-05
  • 詳解mybatis通過mapper接口加載映射文件

    詳解mybatis通過mapper接口加載映射文件

    本篇文章主要介紹了mybatis通過mapper接口加載映射文件 ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • 更改idea的JDK版本超簡單便捷方法

    更改idea的JDK版本超簡單便捷方法

    idea很多地方都設(shè)置了jdk版本,不同模塊的jdk版本也可能不一樣,下面這篇文章主要給大家介紹了關(guān)于更改idea的JDK版本的超簡單便捷方法,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11

最新評論