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

關(guān)于SpringSecurity認(rèn)證邏輯源碼分析

 更新時間:2024年07月12日 10:29:24   作者:讓你三行代碼QAQ  
這篇文章主要介紹了關(guān)于SpringSecurity認(rèn)證邏輯源碼分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

SpringSecurity源碼分析-認(rèn)證邏輯

1. Spring-security-core包中的三個重要類

SecurityContext

  • 這個類中就兩個方法getAuthentication()和setAuthentication()
  • 這個類用來存儲Authentication對象
public interface SecurityContext extends Serializable {
    Authentication getAuthentication();
    void setAuthentication(Authentication var1);
}

Authentication

  • 這個類是貫穿SpringSecurity整個流程的一個類。
  • 它是一個接口,它的實(shí)現(xiàn)類中的UsernamePasswordAuthenticationToken是通過用戶名密碼認(rèn)證的實(shí)現(xiàn)
  • 登錄成功后用來存儲當(dāng)前的登錄信息。

其中三個方法:

  • getCredentials():獲取當(dāng)前用戶憑證
  • getDetails():獲取當(dāng)前登錄用戶詳情
  • getPrincipal():獲取當(dāng)前登錄用戶對象
  • isAuthenticated():是否登錄

GrantedAuthority類是用來存儲權(quán)限的,它是一個接口,常用的SimpleGrantedAuthority實(shí)現(xiàn)類,用來存儲用戶包含的權(quán)限

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

SecurityContextHolder

  • 這個對象用來存儲SecurityContext對象
  • 其中有兩個靜態(tài)方法getContext()和setContext()
  • 因此,獲得SecurityContextHolder對象就能獲得SecurityContext對象,也就可以獲取Authentication對象,也就可以獲取當(dāng)前的登錄信息。
  • initialize():獲取存儲策略,全局、本地線程、父子線程三種,默認(rèn)本地線程。
public class SecurityContextHolder {
    public static SecurityContext getContext() {
        return strategy.getContext();
    }
    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            strategyName = "MODE_THREADLOCAL";
        }

        if (strategyName.equals("MODE_THREADLOCAL")) {
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }
        ++initializeCount;
    }
    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }
}

小結(jié):Authentication用來存儲認(rèn)證信息,SecurityContext用來存儲認(rèn)證信息的容器,SecurityContextHolder用來定義容器的存儲策略。

2. 基于用戶名密碼認(rèn)證的流程

找到UsernamePasswordAuthenticationFilter,找到doFilter方法

  • 發(fā)現(xiàn)沒有doFilter方法,去父類AbstractAuthenticationProcessingFilter中查看
  • 其實(shí)是這樣一個邏輯
  • 所有AbstractAuthenticationProcessingFilter的實(shí)現(xiàn)類都調(diào)用父類中的doFilter方法
  • 在doFilter方法中調(diào)用了attemptAuthentication方法
  • attemptAuthentication方法是一個抽象方法,子類去實(shí)現(xiàn)AbstractAuthenticationProcessingFilter抽象方法

UsernamePasswordAuthenticationFilter類

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
                        //…… ……
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();
        //封裝成Authentication對象
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		setDetails(request, authRequest);
        //認(rèn)證操作,ProviderManager中執(zhí)行
		return this.getAuthenticationManager().authenticate(authRequest);
	}
    //…… ……
}

AbstractAuthenticationProcessingFilter

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Request is to process authentication");
		}

		Authentication authResult;

		try {
            //認(rèn)證邏輯
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		catch (AuthenticationException failed) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// Authentication success
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
        //認(rèn)證成功后的邏輯.......
		successfulAuthentication(request, response, chain, authResult);
	}
    //認(rèn)證邏輯的抽象方法,交給子類去實(shí)現(xiàn)
	public abstract Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException, IOException,
			ServletException;
}

查看子類UsernamePasswordAuthenticationFilter中的attemptAuthentication方法

  • 認(rèn)證的邏輯在這里: this.getAuthenticationManager().authenticate(authRequest)
  • 認(rèn)證完畢后封裝Authentication對象,返回。
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();

		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

ProviderManager類中的authenticate方法

    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();
        Iterator var8 = this.getProviders().iterator();

        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    // 認(rèn)證邏輯
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (InternalAuthenticationServiceException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = parentResult = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var11) {
            } catch (AuthenticationException var12) {
                parentException = var12;
                lastException = var12;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

AbstractUserDetailsAuthenticationProvider中的Authentication方法

    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException{
        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;
        //緩存獲取user
        UserDetails user=this.userCache.getUserFromCache(username);

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

        try{
            //自定義獲取user,一般從數(shù)據(jù)庫讀取
        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{
        preAuthenticationChecks.check(user);
        //去比對密碼
        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;
        }
        }

        postAuthenticationChecks.check(user);

        if(!cacheWasUsed){
        this.userCache.putUserInCache(user);
        }

        Object principalToReturn=user;

        if(forcePrincipalAsString){
        principalToReturn=user.getUsername();
        }
        //封裝Authentication對象
        return createSuccessAuthentication(principalToReturn,authentication,user);
        }

DaoAuthenticationProvider中的retrieveUser方法

	protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
            //調(diào)用我們自己的loadUserByUsername方法獲取user
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

小結(jié):至此返回Authentication對象完成認(rèn)證

3. 認(rèn)證成功后的邏輯

	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}
        //將認(rèn)證后的對象放到SecurityContext中
		SecurityContextHolder.getContext().setAuthentication(authResult);
        //記住我的執(zhí)行邏輯
		rememberMeServices.loginSuccess(request, response, authResult);

		// 發(fā)布認(rèn)證成功后的時間,可以自定義監(jiān)聽器
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}
        //認(rèn)證成功后的執(zhí)行邏輯,默認(rèn)三種,可以通過實(shí)現(xiàn)AuthenticationSuccessHandler接口自定義認(rèn)證成功后邏輯
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

4. 記住我是如何實(shí)現(xiàn)的

AbstractRememberMeServices的loginSuccess方法和rememberMeRequested方法

 public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
    //判斷是否勾選remember
        if (!this.rememberMeRequested(request, this.parameter)) {
            this.logger.debug("Remember-me login not requested.");
        } else {
            this.onLoginSuccess(request, response, successfulAuthentication);
        }
    }
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        if (this.alwaysRemember) {
        return true;
        } else {
        String paramValue = request.getParameter(parameter);

        //判斷是否勾選remember,傳入的值可以為以下內(nèi)容
        if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) {
        return true;
        } else {
        if (this.logger.isDebugEnabled()) {
        this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
        }

        return false;
        }
        }
        }

PersistentTokenBasedRememberMeServices中的onLoginSuccess方法完成持久化操作

    protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

        try {
            //持久化操作
            this.tokenRepository.createNewToken(persistentToken);
            //將token放到cookie中,可以自定義
            this.addCookie(persistentToken, request, response);
        } catch (Exception var7) {
            this.logger.error("Failed to save persistent token ", var7);
        }

    }

持久化操作有兩個實(shí)現(xiàn)JdbcTokenRepositoryImpl存到數(shù)據(jù)庫,InMemoryTokenRepositoryImpl存到內(nèi)存中

5.Security中的ExceptionTranslationFilter過濾器

  • 這個過濾器不處理邏輯
  • 只捕獲Security中的異常
  • 捕獲異常后處理異常信息
public class ExceptionTranslationFilter extends GenericFilterBean {

    // ~ Instance fields
    // ================================================================================================

    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
    private AuthenticationEntryPoint authenticationEntryPoint;
    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    private RequestCache requestCache = new HttpSessionRequestCache();

    private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
        this(authenticationEntryPoint, new HttpSessionRequestCache());
    }

    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
                                      RequestCache requestCache) {
        Assert.notNull(authenticationEntryPoint,
                "authenticationEntryPoint cannot be null");
        Assert.notNull(requestCache, "requestCache cannot be null");
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.requestCache = requestCache;
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationEntryPoint,
                "authenticationEntryPoint must be specified");
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            /** 交給下一個過濾器處理*/
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            /** 捕獲Security中的異常,處理異常*/
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                if (response.isCommitted()) {
                    throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
                }
               /** 處理異常*/
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
                // Rethrow ServletExceptions and RuntimeExceptions as-is
                if (ex instanceof ServletException) {
                    throw (ServletException) ex;
                }
                else if (ex instanceof RuntimeException) {
                    throw (RuntimeException) ex;
                }

                // Wrap other Exceptions. This shouldn't actually happen
                // as we've already covered all the possibilities for doFilter
                throw new RuntimeException(ex);
            }
        }
    }

    public AuthenticationEntryPoint getAuthenticationEntryPoint() {
        return authenticationEntryPoint;
    }

    protected AuthenticationTrustResolver getAuthenticationTrustResolver() {
        return authenticationTrustResolver;
    }

    private void handleSpringSecurityException(HttpServletRequest request,
                                               HttpServletResponse response, FilterChain chain, RuntimeException exception)
            throws IOException, ServletException {
        
        /** 根據(jù)不同的異常,做出不同的處理*/
        if (exception instanceof AuthenticationException) {
            logger.debug(
                    "Authentication exception occurred; redirecting to authentication entry point",
                    exception);

            sendStartAuthentication(request, response, chain,
                    (AuthenticationException) exception);
        }
        else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);

                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                                messages.getMessage(
                                        "ExceptionTranslationFilter.insufficientAuthentication",
                                        "Full authentication is required to access this resource")));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);

                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }
    }

    protected void sendStartAuthentication(HttpServletRequest request,
                                           HttpServletResponse response, FilterChain chain,
                                           AuthenticationException reason) throws ServletException, IOException {
        // SEC-112: Clear the SecurityContextHolder's Authentication, as the
        // existing Authentication is no longer considered valid
        SecurityContextHolder.getContext().setAuthentication(null);
        requestCache.saveRequest(request, response);
        logger.debug("Calling Authentication entry point.");
        authenticationEntryPoint.commence(request, response, reason);
    }

    public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
        Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
        this.accessDeniedHandler = accessDeniedHandler;
    }

    public void setAuthenticationTrustResolver(
            AuthenticationTrustResolver authenticationTrustResolver) {
        Assert.notNull(authenticationTrustResolver,
                "authenticationTrustResolver must not be null");
        this.authenticationTrustResolver = authenticationTrustResolver;
    }

    public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
        Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null");
        this.throwableAnalyzer = throwableAnalyzer;
    }

    /**
     * Default implementation of <code>ThrowableAnalyzer</code> which is capable of also
     * unwrapping <code>ServletException</code>s.
     */
    private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
        /**
         * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
         */
        protected void initExtractorMap() {
            super.initExtractorMap();

            registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
                public Throwable extractCause(Throwable throwable) {
                    ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
                            ServletException.class);
                    return ((ServletException) throwable).getRootCause();
                }
            });
        }

    }
}

6.登錄頁面是如何產(chǎn)生的

答案在最后一個過濾器DefaultLoginPageGeneratingFilter中

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        boolean loginError = this.isErrorPage(request);
        boolean logoutSuccess = this.isLogoutSuccess(request);
        
        if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
            chain.doFilter(request, response);
        } else {
            //拼接生產(chǎn)html登錄頁面
            String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
            response.setContentType("text/html;charset=UTF-8");
            response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
            response.getWriter().write(loginPageHtml);
        }
    }

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Future與FutureTask接口實(shí)現(xiàn)示例詳解

    Future與FutureTask接口實(shí)現(xiàn)示例詳解

    這篇文章主要為大家介紹了Future與FutureTask接口實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Mybatis關(guān)聯(lián)映射舉例詳解

    Mybatis關(guān)聯(lián)映射舉例詳解

    關(guān)聯(lián)關(guān)系是面向?qū)ο蠓治?、面向?qū)ο笤O(shè)計最終的思想,Mybatis完全可以理解這種關(guān)聯(lián)關(guān)系,如果關(guān)系得當(dāng),Mybatis的關(guān)聯(lián)映射將可以大大簡化持久層數(shù)據(jù)的訪問
    2022-07-07
  • springboot項(xiàng)目不加端口號也可以訪問項(xiàng)目的方法步驟分析

    springboot項(xiàng)目不加端口號也可以訪問項(xiàng)目的方法步驟分析

    這篇文章主要介紹了springboot項(xiàng)目不加端口號也可以訪問項(xiàng)目的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • java 保留兩位小數(shù)的幾種方法

    java 保留兩位小數(shù)的幾種方法

    這篇文章主要介紹了JAVA中小數(shù)點(diǎn)后保留兩位的幾種方法,并有小實(shí)例,希望能幫助有所需要的同學(xué)
    2016-07-07
  • Spring Boot啟動流程分析

    Spring Boot啟動流程分析

    本文給大家介紹spring boot是怎樣啟動和啟動做了哪些事情。具體內(nèi)容詳情大家通過本文詳細(xì)學(xué)習(xí)吧
    2017-09-09
  • JFreeChart折線圖的生成方法

    JFreeChart折線圖的生成方法

    這篇文章主要為大家詳細(xì)介紹了JFreeChart折線圖的生成方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • Spring中的@ExceptionHandler注解詳解與應(yīng)用示例

    Spring中的@ExceptionHandler注解詳解與應(yīng)用示例

    本文詳細(xì)介紹了Spring框架中的@ExceptionHandler注解的用法,包括基本用法、全局異常處理、結(jié)合@ResponseStatus注解以及返回值類型,通過示例展示了如何使用@ExceptionHandler注解處理不同類型的異常,并提供定制化的異常處理響應(yīng),需要的朋友可以參考下
    2024-11-11
  • springboot 自定義屬性與加載@value示例詳解

    springboot 自定義屬性與加載@value示例詳解

    在SpringBoot框架中,自定義屬性通常通過application.properties文件配置,并使用@Value注解加載,雖然這是一種可行的方法,但存在一種更優(yōu)雅的實(shí)現(xiàn)方式,本文給大家介紹springboot 自定義屬性與加載@value的相關(guān)操作,感興趣的朋友一起看看吧
    2024-10-10
  • Oracle+Mybatis的foreach insert批量插入報錯的快速解決辦法

    Oracle+Mybatis的foreach insert批量插入報錯的快速解決辦法

    本文給大家介紹Oracle+Mybatis的foreach insert批量插入報錯的快速解決辦法,非常不錯,具有參考借鑒價值,感興趣的朋友參考下吧
    2016-08-08
  • mybatisPlus自動填充更新時間的示例代碼

    mybatisPlus自動填充更新時間的示例代碼

    本文主要介紹了mybatisPlus自動填充更新時間,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評論