SpringBoot中整合Shiro實(shí)現(xiàn)權(quán)限管理的示例代碼
之前在 SSM 項(xiàng)目中使用過(guò) shiro,發(fā)現(xiàn) shiro 的權(quán)限管理做的真不錯(cuò),但是在 SSM 項(xiàng)目中的配置太繁雜了,于是這次在 SpringBoot 中使用了 shiro,下面一起看看吧
一、簡(jiǎn)介
Apache Shiro是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼和會(huì)話管理。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。
三個(gè)核心組件:
1、Subject
即“當(dāng)前操作用戶”。但是,在 Shiro 中,Subject 這一概念并不僅僅指人,也可以是第三方進(jìn)程、后臺(tái)帳戶(Daemon Account)或其他類(lèi)似事物。它僅僅意味著“當(dāng)前跟軟件交互的東西”。Subject 代表了當(dāng)前用戶的安全操作,SecurityManager 則管理所有用戶的安全操作。
2、SecurityManager
它是Shiro 框架的核心,典型的 Facade 模式,Shiro 通過(guò) SecurityManager 來(lái)管理內(nèi)部組件實(shí)例,并通過(guò)它來(lái)提供安全管理的各種服務(wù)。
3、Realm
Realm 充當(dāng)了 Shiro 與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”。也就是說(shuō),當(dāng)對(duì)用戶執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問(wèn)控制)驗(yàn)證時(shí),Shiro 會(huì)從應(yīng)用配置的 Realm 中查找用戶及其權(quán)限信息。從這個(gè)意義上講,Realm 實(shí)質(zhì)上是一個(gè)安全相關(guān)的 DAO:它封裝了數(shù)據(jù)源的連接細(xì)節(jié),并在需要時(shí)將相關(guān)數(shù)據(jù)提供給 Shiro。當(dāng)配置 Shiro 時(shí),你必須至少指定一個(gè) Realm,用于認(rèn)證和(或)授權(quán)。配置多個(gè) Realm 是可以的,但是至少需要一個(gè)。Shiro 內(nèi)置了可以連接大量安全數(shù)據(jù)源(又名目錄)的 Realm,如 LDAP、關(guān)系數(shù)據(jù)庫(kù)(JDBC)、類(lèi)似 INI 的文本配置資源以及屬性文件等。如果缺省的 Realm 不能滿足需求,你還可以插入代表自定義數(shù)據(jù)源的自己的 Realm 實(shí)現(xiàn)。
二、整合 shiro
1、引入 maven 依賴
<!-- web支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Shiro 權(quán)限管理 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> <!-- 為了能夠在 html 中使用 shiro 的標(biāo)簽引入 --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
我使用的 SpringBoot 版本是 2.3.1,其它依賴自己看著引入吧
2、創(chuàng)建 shiro 配置文件
關(guān)于 shiro 的配置信息,我們都放在 ShiroConfig.java 文件中
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * shiro配置類(lèi) */ @Configuration public class ShiroConfig { /** * 注入這個(gè)是是為了在thymeleaf中使用shiro的自定義tag。 */ @Bean(name = "shiroDialect") public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 地址過(guò)濾器 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 設(shè)置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 設(shè)置登錄url shiroFilterFactoryBean.setLoginUrl("/login"); // 設(shè)置主頁(yè)url shiroFilterFactoryBean.setSuccessUrl("/"); // 設(shè)置未授權(quán)的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 開(kāi)放登錄接口 filterChainDefinitionMap.put("/doLogin", "anon"); // 開(kāi)放靜態(tài)資源文件 filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/layui/**", "anon"); // 其余url全部攔截,必須放在最后 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 自定義安全管理策略 */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); /** 設(shè)置自定義的relam */ securityManager.setRealm(loginRelam()); return securityManager; } /** * 登錄驗(yàn)證 */ @Bean public LoginRelam loginRelam() { return new LoginRelam(); } /** * 以下是為了能夠使用@RequiresPermission()等標(biāo)簽 */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } }
上面開(kāi)放靜態(tài)資源文件,其它博客說(shuō)的是 **filterChainDefinitionMap.put("/static/**", "anon");** ,但我發(fā)現(xiàn),我們?cè)?html 文件中引入靜態(tài)文件時(shí),請(qǐng)求路徑根本沒(méi)有經(jīng)過(guò) static,thymeleaf 自動(dòng)默認(rèn)配置 **static/** 下面就是靜態(tài)資源文件,所以,我們開(kāi)放靜態(tài)資源文件需要指定響應(yīng)的目錄路徑
2、登錄驗(yàn)證管理
關(guān)于登錄驗(yàn)證的一些邏輯,以及賦權(quán)等操作,我們都放在 LoginRelam.java 文件中
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.RolePermissionService; import com.zyxx.sbm.service.UserInfoService; import com.zyxx.sbm.service.UserRoleService; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import java.util.Set; /** * 登錄授權(quán) */ public class LoginRelam extends AuthorizingRealm { @Autowired private UserInfoService userInfoService; @Autowired private UserRoleService userRoleService; @Autowired private RolePermissionService rolePermissionService; /** * 身份認(rèn)證 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 獲取基于用戶名和密碼的令牌:實(shí)際上這個(gè)authcToken是從LoginController里面currentUser.login(token)傳過(guò)來(lái)的 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //根據(jù)用戶名查找到用戶信息 QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("account", token.getUsername()); UserInfo userInfo = userInfoService.getOne(queryWrapper); // 沒(méi)找到帳號(hào) if (null == userInfo) { throw new UnknownAccountException(); } // 校驗(yàn)用戶狀態(tài) if ("1".equals(userInfo.getStatus())) { throw new DisabledAccountException(); } // 認(rèn)證緩存信息 return new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getAccount()), getName()); } /** * 角色授權(quán) * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { UserInfo authorizingUser = (UserInfo) principalCollection.getPrimaryPrincipal(); if (null != authorizingUser) { //權(quán)限信息對(duì)象info,用來(lái)存放查出的用戶的所有的角色(role)及權(quán)限(permission) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //獲得用戶角色列表 Set<String> roleSigns = userRoleService.listUserRoleByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addRoles(roleSigns); //獲得權(quán)限列表 Set<String> permissionSigns = rolePermissionService.listRolePermissionByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addStringPermissions(permissionSigns); return simpleAuthorizationInfo; } return null; } /** * 自定義加密規(guī)則 * * @param credentialsMatcher */ @Override public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { // 自定義認(rèn)證加密方式 CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher(); // 設(shè)置自定義認(rèn)證加密方式 super.setCredentialsMatcher(customCredentialsMatcher); } }
以上就是登錄時(shí),需要指明 shiro 對(duì)用戶的一些驗(yàn)證、授權(quán)等操作,還有自定義密碼驗(yàn)證規(guī)則,在第3步會(huì)講到,獲取角色列表,權(quán)限列表,需要獲取到角色與權(quán)限的標(biāo)識(shí),每一個(gè)角色,每一個(gè)權(quán)限都有唯一的標(biāo)識(shí),裝入 Set 中
3、自定義密碼驗(yàn)證規(guī)則
密碼的驗(yàn)證規(guī)則,我們放在了 CustomCredentialsMatcher.java 文件中
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import org.apache.shiro.crypto.hash.SimpleHash; /** * @ClassName CustomCredentialsMatcher * 自定義密碼加密規(guī)則 * @Author Lizhou * @Date 2020-07-10 16:24:24 **/ public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; //加密類(lèi)型,密碼,鹽值,迭代次數(shù) Object tokenCredentials = new SimpleHash("md5", token.getPassword(), token.getUsername(), 6).toHex(); // 數(shù)據(jù)庫(kù)存儲(chǔ)密碼 Object accountCredentials = getCredentials(info); // 將密碼加密與系統(tǒng)加密后的密碼校驗(yàn),內(nèi)容一致就返回true,不一致就返回false return equals(tokenCredentials, accountCredentials); } }
我們采用的密碼加密方式為 MD5 加密,加密 6 次,使用登錄賬戶作為加密密碼的鹽進(jìn)行加密
4、密碼加密工具
上面我們自定義了密碼加密規(guī)則,我們創(chuàng)建一個(gè)密碼加密的工具類(lèi) PasswordUtils.java 文件
import org.apache.shiro.crypto.hash.Md5Hash; /** * 密碼加密的處理工具類(lèi) */ public class PasswordUtils { /** * 迭代次數(shù) */ private static final int ITERATIONS = 6; private PasswordUtils() { throw new AssertionError(); } /** * 字符串加密函數(shù)MD5實(shí)現(xiàn) * * @param password 密碼 * @param loginName 用戶名 * @return */ public static String getPassword(String password, String loginName) { return new Md5Hash(password, loginName, ITERATIONS).toString(); } }
三、開(kāi)始登錄
上面,我們已經(jīng)配置了 shiro 的一系列操作,從登錄驗(yàn)證、密碼驗(yàn)證規(guī)則、用戶授權(quán)等等,下面我們就開(kāi)始登錄,登錄的操作,放在了 LoginController.java 文件中
import com.zyxx.common.consts.SystemConst; import com.zyxx.common.enums.StatusEnums; import com.zyxx.common.kaptcha.KaptchaUtil; import com.zyxx.common.shiro.SingletonLoginUtils; import com.zyxx.common.utils.PasswordUtils; import com.zyxx.common.utils.ResponseResult; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.PermissionInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName LoginController * @Description * @Author Lizhou * @Date 2020-07-02 10:54:54 **/ @Api(tags = "后臺(tái)管理端--登錄") @Controller public class LoginController { @Autowired private PermissionInfoService permissionInfoService; @ApiOperation(value = "請(qǐng)求登錄頁(yè)面", notes = "請(qǐng)求登錄頁(yè)面") @GetMapping("login") public String init() { return "login"; } @ApiOperation(value = "請(qǐng)求主頁(yè)面", notes = "請(qǐng)求主頁(yè)面") @GetMapping("/") public String index() { return "index"; } @ApiOperation(value = "登錄驗(yàn)證", notes = "登錄驗(yàn)證") @ApiImplicitParams({ @ApiImplicitParam(name = "account", value = "賬號(hào)", required = true), @ApiImplicitParam(name = "password", value = "密碼", required = true), @ApiImplicitParam(name = "resCode", value = "驗(yàn)證碼", required = true), @ApiImplicitParam(name = "rememberMe", value = "記住登錄", required = true) }) @PostMapping("doLogin") @ResponseBody public ResponseResult doLogin(String account, String password, String resCode, Boolean rememberMe, HttpServletRequest request, HttpServletResponse response) throws Exception { // 驗(yàn)證碼 if (!KaptchaUtil.validate(resCode, request)) { return ResponseResult.getInstance().error(StatusEnums.KAPTCH_ERROR); } // 驗(yàn)證帳號(hào)和密碼 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(account, password); // 記住登錄狀態(tài) token.setRememberMe(rememberMe); try { // 執(zhí)行登錄 subject.login(token); // 將用戶保存到session中 UserInfo userInfo = (UserInfo) subject.getPrincipal(); request.getSession().setAttribute(SystemConst.SYSTEM_USER_SESSION, userInfo); return ResponseResult.getInstance().success(); } catch (UnknownAccountException e) { return ResponseResult.getInstance().error("賬戶不存在"); } catch (DisabledAccountException e) { return ResponseResult.getInstance().error("賬戶已被凍結(jié)"); } catch (IncorrectCredentialsException e) { return ResponseResult.getInstance().error("密碼不正確"); } catch (ExcessiveAttemptsException e) { return ResponseResult.getInstance().error("密碼連續(xù)輸入錯(cuò)誤超過(guò)5次,鎖定半小時(shí)"); } catch (RuntimeException e) { return ResponseResult.getInstance().error("未知錯(cuò)誤"); } } @ApiOperation(value = "登錄成功,跳轉(zhuǎn)主頁(yè)面", notes = "登錄成功,跳轉(zhuǎn)主頁(yè)面") @PostMapping("success") public String success() { return "redirect:/"; } @ApiOperation(value = "初始化菜單數(shù)據(jù)", notes = "初始化菜單數(shù)據(jù)") @GetMapping("initMenu") @ResponseBody public String initMenu() { return permissionInfoService.initMenu(); } @ApiOperation(value = "退出登錄", notes = "退出登錄") @GetMapping(value = "loginOut") public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "login"; } }
當(dāng)執(zhí)行 subject.login(token); 時(shí),就會(huì)進(jìn)入我們?cè)?第二步中第二條登錄驗(yàn)證中,對(duì)用戶密碼、狀態(tài)進(jìn)行檢查,對(duì)用戶授權(quán)等操作,登錄的密碼,一定是通過(guò)密碼加密工具得到的,不然驗(yàn)證不通過(guò)
四、頁(yè)面權(quán)限控制
我們本次使用的是 thymeleaf 模板引擎,我們需要在 html 文件中加入以下內(nèi)容
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
引入了 thymeleaf 的依賴,以及 shiro 的依賴,這樣我們就能在 html 文件中使用 thymeleaf、shiro 的標(biāo)簽了
例如:
1、判斷當(dāng)前用戶有無(wú)此權(quán)限,通過(guò)權(quán)限標(biāo)識(shí)
<button class="layui-btn" shiro:hasPermission="user_info_add"><i class="layui-icon"></i> 新增 </button>
2、與上面相反,判斷當(dāng)前用戶無(wú)此權(quán)限,通過(guò)權(quán)限標(biāo)識(shí),沒(méi)有時(shí)驗(yàn)證通過(guò)
<button class="layui-btn" shiro:lacksPermission="user_info_add"><i class="layui-icon"></i> 新增 </button>
3、判斷當(dāng)前用戶有無(wú)以下全部權(quán)限,通過(guò)權(quán)限標(biāo)識(shí)
<button class="layui-btn" shiro:hasAllPermissions="user_info_add"><i class="layui-icon"></i> 新增 </button>
4、判斷當(dāng)前用戶有無(wú)以下任一權(quán)限,通過(guò)權(quán)限標(biāo)識(shí)
<button class="layui-btn" shiro:hasAnyPermissions="user_info_add"><i class="layui-icon"></i> 新增 </button>
5、判斷當(dāng)前用戶有無(wú)此角色,通過(guò)角色標(biāo)識(shí)
<a shiro:hasRole="admin" href="admin.html" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Administer the system</a>
6、與上面相反,判斷當(dāng)前用戶無(wú)此角色,通過(guò)角色標(biāo)識(shí),沒(méi)有時(shí)驗(yàn)證通過(guò)
<a shiro:lacksRole="admin" href="admin.html" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Administer the system</a>
7、判斷當(dāng)前用戶有無(wú)以下全部角色,通過(guò)角色標(biāo)識(shí)
<a shiro:hasAllRoles="admin,role1,role2" href="admin.html" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Administer the system</a>
8、判斷當(dāng)前用戶有無(wú)以下任一角色,通過(guò)角色標(biāo)識(shí)
<a shiro:hasAnyRoles="admin,role1,role2" href="admin.html" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Administer the system</a>
到此這篇關(guān)于SpringBoot中整合Shiro實(shí)現(xiàn)權(quán)限管理的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot整合Shiro權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例
- SpringBoot集成shiro,MyRealm中無(wú)法@Autowired注入Service的問(wèn)題
- SpringBoot2.0整合Shiro框架實(shí)現(xiàn)用戶權(quán)限管理的示例
- SpringBoot+Shiro+LayUI權(quán)限管理系統(tǒng)項(xiàng)目源碼
- springboot集成shiro詳細(xì)總結(jié)
- SpringBoot整合Shiro框架,實(shí)現(xiàn)用戶權(quán)限管理
- 詳解springboot shiro jwt實(shí)現(xiàn)權(quán)限管理
- springboot集成shiro遭遇自定義filter異常的解決
- springboot集成shiro權(quán)限管理簡(jiǎn)單實(shí)現(xiàn)
相關(guān)文章
關(guān)于springboot整合swagger問(wèn)題及解決方法
這篇文章主要介紹了關(guān)于springboot整合swagger問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04關(guān)于Java中阻塞隊(duì)列BlockingQueue的詳解
這篇文章主要介紹了關(guān)于Java中阻塞隊(duì)列BlockingQueue的詳解,BlockingQueue是為了解決多線程中數(shù)據(jù)高效安全傳輸而提出的,從阻塞這個(gè)詞可以看出,在某些情況下對(duì)阻塞隊(duì)列的訪問(wèn)可能會(huì)造成阻塞,需要的朋友可以參考下2023-05-05Java String創(chuàng)建對(duì)象實(shí)例解析
這篇文章主要介紹了Java String創(chuàng)建對(duì)象實(shí)例解析,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02java并發(fā)之ArrayBlockingQueue詳細(xì)介紹
這篇文章主要介紹了java并發(fā)之ArrayBlockingQueue詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-05-05SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解
這篇文章主要介紹了SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解,Spring?的?AOP?中的一個(gè)核心概念是切點(diǎn)(Pointcut),切點(diǎn)表達(dá)式定義通知(Advice)執(zhí)行的范圍,需要的朋友可以參考下2023-08-08如何把spring boot應(yīng)用發(fā)布到Harbor
這篇文章主要介紹了如何把spring boot應(yīng)用發(fā)布到Harbor,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java 8中字符串拼接新姿勢(shì)StringJoiner詳解
在本篇文章里小編給大家整理了關(guān)于Java 8中字符串拼接新姿勢(shì)StringJoiner的詳解內(nèi)容,需要的朋友們參考下。2019-09-09