Spring?Security?基于URL的權(quán)限判斷源碼解析
1. FilterSecurityInterceptor 源碼閱讀
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
通過過濾器實現(xiàn)對HTTP資源進(jìn)行安全處理。
該安全攔截器所需的 SecurityMetadataSource 類型為 FilterInvocationSecurityMetadataSource。
doFilter方法中直接調(diào)用invoke方法
基本都是調(diào)用父類的方法,那下面就重點看下父類 AbstractSecurityInterceptor中相關(guān)方法
為安全對象實現(xiàn)安全攔截的抽象類。
AbstractSecurityInterceptor 將確保安全攔截器的正確啟動配置。 它還將實現(xiàn)對安全對象調(diào)用的正確處理,即:
1.從 SecurityContextHolder 獲取 Authentication 對象。
2.通過在SecurityMetadataSource中查找安全對象請求,確定請求是與安全調(diào)用還是公共調(diào)用相關(guān)(PS:簡單地來講,就是看一下請求的資源是不是受保護(hù)的,受保護(hù)的就是安全調(diào)用,就要權(quán)限,不受保護(hù)的就不需要權(quán)限就可以訪問)。
3.對于受保護(hù)的調(diào)用(有一個用于安全對象調(diào)用的 ConfigAttributes 列表):
- 如果 Authentication.isAuthenticated() 返回 false,或者 alwaysReauthenticate 為 true,則根據(jù)配置的 AuthenticationManager 對請求進(jìn)行身份驗證。 通過身份驗證后,將 SecurityContextHolder 上的 Authentication 對象替換為返回值。
- ?根據(jù)配置的AccessDecisionManager授權(quán)請求。
- 通過配置的RunAsManager執(zhí)行任何run-as替換。
- 將控制權(quán)傳遞回具體的子類,它實際上將繼續(xù)執(zhí)行對象。返回一個 InterceptorStatusToken 以便在子類完成對象的執(zhí)行后,其 finally 子句可以確保 AbstractSecurityInterceptor 被調(diào)用并使用 finallyInvocation(InterceptorStatusToken) 正確處理。
- 具體的子類將通過 afterInvocation(InterceptorStatusToken, Object) 方法重新調(diào)用 AbstractSecurityInterceptor。
- 如果 RunAsManager 替換了 Authentication 對象,則將 SecurityContextHolder 返回到調(diào)用 AuthenticationManager 后存在的對象。
- 如果定義了AfterInvocationManager,則調(diào)用它并允許它替換將要返回給調(diào)用者的對象。
4.對于公開的調(diào)用(安全對象調(diào)用沒有 ConfigAttributes):
如上所述,具體的子類將返回一個 InterceptorStatusToken,在執(zhí)行完安全對象后,該 InterceptorStatusToken 隨后被重新呈現(xiàn)給 AbstractSecurityInterceptor。 AbstractSecurityInterceptor 在它的 afterInvocation(InterceptorStatusToken, Object) 被調(diào)用時不會采取進(jìn)一步的行動。
5.控制再次返回到具體的子類,以及應(yīng)該返回給調(diào)用者的對象。然后子類會將該結(jié)果或異常返回給原始調(diào)用者。
下面具體來看
從這里我們可以知道返回null和空集合是一樣的。
接下來看授權(quán)
這是我們要重點關(guān)注的,可以看到,授權(quán)靠的是accessDecisionManager.decide(authenticated, object, attributes)
因此,我們想要實現(xiàn)自己的基于請求Url的授權(quán)只需自定義一個 AccessDecisionManager即可
接下來,我們來具體實現(xiàn)一下
2. 自定義基于url的授權(quán)
先把Spring Security授權(quán)的大致流程流程擺在這兒:
自定義FilterInvocationSecurityMetadataSource
package com.example.security.core; import com.example.security.service.SysPermissionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * @Author ChengJianSheng * @Date 2021/12/2 */ @Component public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired private SysPermissionService sysPermissionService; private final AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl(); String httpMethod = fi.getRequest().getMethod(); List<ConfigAttribute> attributes = new ArrayList<>(); Map<String, String> urlRoleMap = sysPermissionService.getAllUrlRole(); for (Map.Entry<String, String> entry : urlRoleMap.entrySet()) { if (antPathMatcher.match(entry.getKey(), url)) { return SecurityConfig.createList(entry.getValue()); } } // 返回null和空列表是一樣的,都表示當(dāng)前訪問的資源不需要權(quán)限,所有人都可以訪問 return attributes; // return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
這里需要說明一下,其實Spring Security里面說的role不一定表示的是我們自己建的那個角色表,我們可以這樣理解,就是它這里的所謂role只是一個權(quán)限標(biāo)識。我們在建表的時候,通常最基本的是5張表(用戶表、角色表、權(quán)限表、用戶角色關(guān)系表、角色權(quán)限關(guān)系表),我們可以把受保護(hù)的資源(通常是一個url)與角色關(guān)聯(lián)起來,建立哪些角色可以訪問哪些資源,也可以直接判斷資源的權(quán)限(通常是權(quán)限編碼/標(biāo)識)。
只要有這個關(guān)系,剩下的就是寫法不同而已。如果你把role理解成資源的權(quán)限標(biāo)識的話,那么返回的Collection<ConfigAttribute>中就最多有一個元素,如果理解成角色的話,那么可能有多個元素。就這么點兒東西,寫法不同而已,本質(zhì)是一樣的。
自定義AccessDecisionManager
package com.example.security.core; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; /** * @Author ChengJianSheng * @Date 2021/12/2 */ @Component public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); System.out.println(authorities); System.out.println(configAttributes); // 查看當(dāng)前用戶是否有對應(yīng)的權(quán)限訪問該保護(hù)資源 for (ConfigAttribute attribute : configAttributes) { for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(attribute.getAttribute())) { return; } } } throw new AccessDeniedException("Access is denied"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
decide方法的三個參數(shù),依次表示:
- 調(diào)用者(非空)
- 被調(diào)用的安全對象
- 與被調(diào)用的安全對象關(guān)聯(lián)的配置屬性
配置WebSecurityConfig
package com.example.security.config; import com.example.security.core.MyAccessDecisionManager; import com.example.security.core.MyFilterSecurityMetadataSource; import com.example.security.core.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Autowired private MyAccessDecisionManager myAccessDecisionManager; @Autowired private MyFilterSecurityMetadataSource myFilterSecurityMetadataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .and() .authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(myFilterSecurityMetadataSource); object.setAccessDecisionManager(myAccessDecisionManager); return object; } }) .anyRequest().authenticated(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
其它不重要的就直接貼出來了
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>security-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>security-demo</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
application.yml
spring: datasource: url: jdbc:mysql://localhost:3306/demo126?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 jpa: database: mysql show-sql: true
SysPermissionEntity.java
package com.example.security.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Getter @Setter @Entity @Table(name = "sys_permission") public class SysPermissionEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; /** 權(quán)限編碼(標(biāo)識) */ private String code; /** 權(quán)限名稱 */ private String name; /** 權(quán)限URL */ private String url; }
SysRoleEntity.java
package com.example.security.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.util.Set; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Getter @Setter @Entity @Table(name = "sys_role") public class SysRoleEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; /** 角色編碼 */ private String code; /** 角色名稱 */ private String name; @ManyToMany @JoinTable(name = "sys_role_permission", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id")}) private Set<SysPermissionEntity> permissions; }
SysUserEntity.java
package com.example.security.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.util.Set; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Getter @Setter @Entity @Table(name = "sys_user") public class SysUserEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; /** 用戶名 */ private String username; /** 密碼 */ private String password; @ManyToMany @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRoleEntity> roles; }
SysUserRepository.java
package com.example.security.repository; import com.example.security.entity.SysUserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; /** * @Author ChengJianSheng * @Date 2021/12/6 */ public interface SysUserRepository extends JpaRepository<SysUserEntity, Integer>, JpaSpecificationExecutor<SysUserEntity> { SysUserEntity findByUsername(String username); }
SysPermissionServiceImpl.java
package com.example.security.service.impl; import com.example.security.entity.SysPermissionEntity; import com.example.security.repository.SysPermissionRepository; import com.example.security.service.SysPermissionService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Service public class SysPermissionServiceImpl implements SysPermissionService { @Resource private SysPermissionRepository sysPermissionRepository; @Override public Map<String, String> getAllUrlRole() { List<SysPermissionEntity> list = sysPermissionRepository.findAll(); return list.stream().collect(Collectors.toMap(SysPermissionEntity::getUrl, SysPermissionEntity::getCode)); } }
MyUserDetails.java
package com.example.security.domain; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @NoArgsConstructor @AllArgsConstructor public class MyUserDetails implements UserDetails { private String username; private String password; private boolean enabled; private Set<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } }
MyUserDetailsService.java
package com.example.security.core; import com.example.security.domain.MyUserDetails; import com.example.security.entity.SysPermissionEntity; import com.example.security.entity.SysUserEntity; import com.example.security.repository.SysUserRepository; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.transaction.Transactional; import java.util.Set; import java.util.stream.Collectors; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @Transactional @Service public class MyUserDetailsService implements UserDetailsService { @Resource private SysUserRepository sysUserRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserEntity sysUserEntity = sysUserRepository.findByUsername(username); if (null == sysUserEntity) { throw new UsernameNotFoundException("用戶不存在"); } Set<SimpleGrantedAuthority> authorities = sysUserEntity.getRoles().stream() .flatMap(roleId->roleId.getPermissions().stream()) .map(SysPermissionEntity::getCode) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); return new MyUserDetails(sysUserEntity.getUsername(), sysUserEntity.getPassword(), true, authorities); } }
HelloController.java
package com.example.security.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author ChengJianSheng * @Date 2021/12/6 */ @RestController @RequestMapping("/hello") public class HelloController { @GetMapping("/sayHello") public String sayHello() { return "Hello"; } @GetMapping("/sayHi") public String sayHi() { return "Hi"; } }
數(shù)據(jù)庫腳本如下
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_permission`; CREATE TABLE `sys_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '權(quán)限編碼(標(biāo)識)', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '權(quán)限名稱', `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '權(quán)限URL', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_permission -- ---------------------------- INSERT INTO `sys_permission` VALUES (1, 'home', '首頁', '/home/**'); INSERT INTO `sys_permission` VALUES (2, 'user:add', '添加用戶', '/user/add'); INSERT INTO `sys_permission` VALUES (3, 'user:delete', '刪除用戶', '/user/delete'); INSERT INTO `sys_permission` VALUES (4, 'hello:sayHello', '打招呼', '/hello/sayHello'); -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色編碼', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名稱', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES (1, 'employee', '員工'); INSERT INTO `sys_role` VALUES (2, 'engineer', '工程師'); INSERT INTO `sys_role` VALUES (3, 'leader', '組長'); -- ---------------------------- -- Table structure for sys_role_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_role_permission`; CREATE TABLE `sys_role_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) NOT NULL COMMENT '角色I(xiàn)D', `permission_id` int(11) NOT NULL COMMENT '權(quán)限ID', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role_permission -- ---------------------------- INSERT INTO `sys_role_permission` VALUES (1, 1, 1); INSERT INTO `sys_role_permission` VALUES (2, 2, 1); INSERT INTO `sys_role_permission` VALUES (3, 2, 2); INSERT INTO `sys_role_permission` VALUES (4, 3, 1); INSERT INTO `sys_role_permission` VALUES (5, 3, 2); INSERT INTO `sys_role_permission` VALUES (6, 3, 3); INSERT INTO `sys_role_permission` VALUES (7, 3, 4); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用戶名', `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密碼', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES (1, 'zhangsan', '$2a$10$e4wFsFHQCNjPe5tTJMPkRuKGwmMGC45pfjMupY9nwbTuoKQ0bKc/u'); -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '用戶ID', `role_id` int(11) NOT NULL COMMENT '角色I(xiàn)D', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (1, 1, 1); INSERT INTO `sys_user_role` VALUES (2, 1, 2); INSERT INTO `sys_user_role` VALUES (3, 1, 3); SET FOREIGN_KEY_CHECKS = 1;
瀏覽器訪問http://localhost:8080/hello/sayHi 正常返回,不用登錄,因為沒有在sys_permission表中配置該資源,也就是說它不是一個受保護(hù)的資源(公開資源)
訪問http://localhost:8080/hello/sayHello則需要先登錄,用zhangsan登錄成功以后正確返回
項目結(jié)構(gòu)如下
到此這篇關(guān)于Spring?Security?基于URL的權(quán)限判斷的文章就介紹到這了,更多相關(guān)Spring?Security權(quán)限判斷內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Java中的dozer對象轉(zhuǎn)換問題
Dozer是Java?Bean到Java?Bean映射器,它以遞歸方式將數(shù)據(jù)從一個對象復(fù)制到另一個對象,這篇文章主要介紹了Java中的dozer對象轉(zhuǎn)換的操作方法,需要的朋友可以參考下2022-08-08SpringBoot項目實用功能之實現(xiàn)自定義參數(shù)解析器
這篇文章主要介紹了SpringBoot項目實用功能之實現(xiàn)自定義參數(shù)解析器,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08全解史上最快的JOSN解析庫alibaba Fastjson
這篇文章主要介紹了史上最快的JOSN解析庫alibaba Fastjson,對FastJson感興趣的同學(xué),一定要看一下2021-04-04關(guān)于SpringMVC中數(shù)據(jù)綁定@ModelAttribute注解的使用
這篇文章主要介紹了關(guān)于SpringMVC中數(shù)據(jù)綁定@ModelAttribute注解的使用,SpringMVC是一個基于Spring框架的Web框架,它提供了一種簡單、靈活的方式來開發(fā)Web應(yīng)用程序,在開發(fā)Web應(yīng)用程序時,我們需要將用戶提交的數(shù)據(jù)綁定到我們的Java對象上,需要的朋友可以參考下2023-07-07Java實現(xiàn)深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)算法
深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)是兩種基本的圖搜索算法,可用于圖的遍歷、路徑搜索等問題。DFS采用棧結(jié)構(gòu)實現(xiàn),從起點開始往深處遍歷,直到找到目標(biāo)節(jié)點或遍歷完整個圖;BFS采用隊列結(jié)構(gòu)實現(xiàn),從起點開始往廣處遍歷,直到找到目標(biāo)節(jié)點或遍歷完整個圖2023-04-04Spring多線程通過@Scheduled實現(xiàn)定時任務(wù)
這篇文章主要介紹了Spring多線程通過@Scheduled實現(xiàn)定時任務(wù),@Scheduled?定時任務(wù)調(diào)度注解,是spring定時任務(wù)中最重要的,下文關(guān)于其具體介紹,需要的小伙伴可以參考一下2022-05-05