前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Security 中,想在权限中使用通配符,怎么做?

Spring Security 中,想在权限中使用通配符,怎么做?

作者头像
江南一点雨
发布于 2022-06-29 08:58:00
发布于 2022-06-29 08:58:00
87400
代码可运行
举报
文章被收录于专栏:玩转JavaEE玩转JavaEE
运行总次数:0
代码可运行

松哥最近正在录制 TienChin 项目视频~采用 Spring Boot+Vue3 技术栈,里边会涉及到各种好玩的技术,小伙伴们来和松哥一起做一个完成率超 90% 的项目,戳戳戳这里-->TienChin 项目配套视频来啦


小伙伴们知道,在 Shiro 中,默认是支持权限通配符的,例如系统用户有如下一些权限:

  • system:user:add
  • system:user:delete
  • system:user:select
  • system:user:update
  • ...

现在给用户授权的时候,我们可以像上面这样,一个权限一个权限的配置,也可以直接用通配符:

  • system:user:*

这个通配符就表示拥有针对用户的所有权限。

当然这是 Shiro 里边的,对 Shiro 不熟悉的小伙伴,可以在公众号后台回复 shiro,查看松哥之前录的视频教程。

今天我们来聊聊 Spring Security 中对此如何处理,也顺便来看看 TienChin 项目中,这块该如何改进。

1. SpEL

要搞明白基于注解的权限管理,那么得首先理解 SpEL,不需要了解多深入,我这里就简单介绍下。

Spring Expression Language(简称 SpEL)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。它的语法类似于传统 EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。

SpEL 给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性基于 Spring 产品的需求而设计,这是它出现的一大特色。

在我们离不开 Spring 框架的同时,其实我们也已经离不开 SpEL 了,因为它太好用、太强大了,SpEL 在整个 Spring 家族中也处于一个非常重要的位置。但是很多时候,我们对它的只了解一个大概,其实如果你系统的学习过 SpEL,那么上面 Spring Security 那个注解其实很好理解。

我先通过一个简单的例子来和大家捋一捋 SpEL。

为了省事,我就创建一个 Spring Boot 工程来和大家演示,创建的时候不用加任何额外的依赖,就最最基础的依赖即可。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String expressionStr = "1 + 2";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expressionStr);

expressionStr 是我们自定义的一个表达式字符串,这个字符串通过一个 ExpressionParser 对象将之解析为一个 Expression,接下来就可以执行这个 exp 了。

执行的时候有两种方式,对于我们上面这种不带任何额外变量的,我们可以直接执行,直接执行的方式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Object value = exp.getValue();
System.out.println(value.toString());

这个打印结果为 3。

我记得之前有个小伙伴在群里问想执行一个字符串表达式,但是不知道怎么办,js 中有 eval 函数很方便,我们 Java 中也有 SpEL,一样也很方便。

不过很多时候,我们要执行的表达式可能比较复杂,这时候上面这种调用方式就不太够用了。

此时我们可以为要调用的表达式设置一个上下文环境,这个时候就会用到 EvaluationContext 或者它的子类,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
StandardEvaluationContext context = new StandardEvaluationContext();
System.out.println(exp.getValue(context));

当然上面这个表达式不需要设置上下文环境,我举一个需要设置上下文环境的例子。

例如我现在有一个 User 类,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class User {
    private Integer id;
    private String username;
    private String address;
    //省略 getter/setter
}

现在我的表达式是这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String expression = "#user.username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setVariable("user", user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

这个表达式就表示获取 user 对象的 username 属性。将来创建一个 user 对象,放到 StandardEvaluationContext 中,并基于此对象执行表达式,就可以打印出来想要的结果。

如果我们将 user 对象设置为 rootObject,那么表达式中就不需要 user 了,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String expression = "username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

表达式就一个 username 字符串,将来执行的时候,会自动从 user 中找到 username 的值并返回。

当然表达式也可以是方法,例如我在 User 类中添加如下两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public String sayHello(Integer age) {
    return "hello " + username + ";age=" + age;
}
public String sayHello() {
    return "hello " + username;
}

我们就可以通过表达式调用这两个方法,如下:

调用有参的 sayHello:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String expression = "sayHello(99)";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

就直接写方法名然后执行就行了。

调用无参的 sayHello:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String expression = "sayHello";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

这些就都好懂了。

甚至,我们的表达式也可以涉及到 Spring 中的一个 Bean,例如我们向 Spring 中注册如下 Bean:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service("us")
public class UserService {
    public String sayHello(String name) {
        return "hello " + name;
    }
}

然后通过 SpEL 表达式来调用这个名为 us 的 bean 中的 sayHello 方法,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Autowired
BeanFactory beanFactory;
@Test
void contextLoads() {
    String expression = "@us.sayHello('javaboy')";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}

给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法。

当然,关于 SpEL 的玩法还有很多,我就不一一列举了。这里主要是想让小伙伴们知道,有这么个技术,方便大家理解 @PreAuthorize 注解的原理。

总结一下:

  1. 在使用 SpEL 的时候,如果表达式直接写的就是方法名,那是因为在构建 SpEL 上下文的时候,已经设置了 RootObject 了,我们所调用的方法,实际上就是 RootObject 对象中的方法。
  2. 在使用 SpEL 对象的时候,如果像调用非 RootObject 对象中的方法,那么表达式需要加上 @对象名 作为前缀,例如前面案例的 @us

2. 自定义权限该如何写

那么自定义权限到底该如何写呢?首先我们来看下在 Spring Security 中,不涉及到通配符的权限该怎么处理。

松哥举一个简单的例子,我们创建一个 Spring Boot 工程,引入 Web 和 Security 依赖,为了方便,这里的用户我直接创建在内存中,配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager m = new InMemoryUserDetailsManager();
        m.createUser(User.withUsername("javaboy").password("{noop}123").authorities("system:user:add","system:user:delete").build());
        return m;
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll();
        return http.build();
    }

}

都是常规配置,没啥好说的。注意前面的注解,开启基于注解的权限控制。

❝这里我多啰嗦一句,大家看创建用户的时候,调用的是 authorities 方法去设置权限的,这个跟 roles 方法其实没啥大的区别,调用 roles 方法会自动为你设置的字符串添加一个 ROLE_ 前缀,其他的其实都一样。在 Spring Security 中,role 和 permission 仅仅只是人为划分出来的东西,底层的实现包括判断逻辑基本上都是没有区别的。

接下来我们定义四个测试接口,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class UserController {

    @GetMapping("/add")
    @PreAuthorize("hasPermission('/add','system:user:add')")
    public String addUser() {
        return "add";
    }
    @GetMapping("/delete")
    @PreAuthorize("hasPermission('/delete','system:user:delete')")
    public String deleteUser() {
        return "delete";
    }
    @GetMapping("/update")
    @PreAuthorize("hasPermission('/update','system:user:update')")
    public String updateUser() {
        return "update";
    }
    @GetMapping("/select")
    @PreAuthorize("hasPermission('/select','system:user:select')")
    public String selectUser() {
        return "select";
    }
}

接口访问都需要不同的权限。

此时如果大家启动项目去此时,系统会提示你四个接口统统都不具备权限,这是啥原因呢?我们来继续分析。

小伙伴们看这里,调用的时候 @PreAuthorize 注解中执行写方法名,不用写对象名,说明调用的方法是 RootObject 中的方法,这里的 RootObject 实际上就是 SecurityExpressionRoot,我们来看看这个对象中的 hasPermission 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public boolean hasPermission(Object target, Object permission) {
 return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
}
@Override
public boolean hasPermission(Object targetId, String targetType, Object permission) {
 return this.permissionEvaluator.hasPermission(this.authentication, (Serializable) targetId, targetType,
   permission);
}

最终的调用又指向了 permissionEvaluator 对象。

在 Spring Security 中,permissionEvaluator 有一个统一的接口就是 PermissionEvaluator,但是这个接口只有一个实现类,就是 DenyAllPermissionEvaluator,看名字就知道,这是拒绝所有。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class DenyAllPermissionEvaluator implements PermissionEvaluator {

 private final Log logger = LogFactory.getLog(getClass());

 /**
  * @return false always
  */
 @Override
 public boolean hasPermission(Authentication authentication, Object target, Object permission) {
  return false;
 }

 /**
  * @return false always
  */
 @Override
 public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
   Object permission) {
  return false;
 }

}

这两个方法里啥都没干,直接返回了 false,这下就破案了!

所以,在 Spring Security 中,如果想判断权限,需要自己提供一个 PermissionEvaluator 的实例,我们来看下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (authority.getAuthority().equals(permission)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

我这里的判断逻辑比较简单,所以只需要实现第一个方法就行了,这个方法三个参数,第一个参数就是当前登录成功的用户对象,后面两个参数则是我们在 @PreAuthorize("hasPermission('/select','system:user:select')") 注解中的两个参数,现在该有的东西都有了,我们只需要判断需要的权限当前用户是否有就行了。

这个自定义的权限评估器写好之后,注册到 Spring 容器就行了,其他什么事情都不用做。

接下来我们就可以对刚才的四个接口进行测试了,测试过程我就不演示了,小伙伴们自行用 postman 测试就行了。

3. 权限通配符

看明白了上面的逻辑,现在不用我说,大家也知道权限通配符在 Spring Security 中是不支持的(无论你在 @PreAuthorize 注解中写的 SpEL 是哪个,调用的是哪个方法,都是不支持权限通配符的)。

例如我现在这样描述我的用户权限:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Bean
UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager m = new InMemoryUserDetailsManager();
    m.createUser(User.withUsername("javaboy").password("{noop}123").authorities("system:user:*").build());
    return m;
}

我想用 system:user:* 字符串表示 javaboy 具有针对用户的所有权限。

直接这样写肯定是不行的,最终字符串比较一定是不会通过的。

那么怎么办呢?用正则似乎也不太行,因为 * 在正则中不代表所有字符,如果拆解字符串去比较,功能虽然也行得通,但是比较麻烦。

想来想去,想到一个办法,不知道小伙伴们是否还记得我们之前在 vhr 中用过的 AntPathMatcher,用这个不就行了!

修改后的 CustomPermissionEvaluator 如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (antPathMatcher.match(authority.getAuthority(), (String) permission)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

修改之后,现在只要用户具备 system:user:* 权限,就四个接口都能访问了。

4. TienChin 项目怎么做的?

TienChin 项目用的是 RuoYi-Vue 脚手架,我们来看下这个脚手架的实现方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@PreAuthorize("@ss.hasPermi('tienchin:channel:query')")
@GetMapping("/list")
public TableDataInfo getChannelList() {
    startPage();
    List<Channel> list = channelService.list();
    return getDataTable(list);
}

看了前面的讲解,现在 @ss.hasPermi('tienchin:channel:query') 应该很好懂了:

  • ss 是一个注册在 Spring 容器中的 bean,对应的类位于 org.javaboy.tienchin.framework.web.service.PermissionService 中。
  • 很明显,hasPermi 就是这个类中的方法。

这个 hasPermi 方法的逻辑其实很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean hasPermi(String permission) {
    if (StringUtils.isEmpty(permission)) {
        return false;
    }
    LoginUser loginUser = SecurityUtils.getLoginUser();
    if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
        return false;
    }
    return hasPermissions(loginUser.getPermissions(), permission);
}
private boolean hasPermissions(Set<String> permissions, String permission) {
    return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}

这个判断逻辑很简单,就是获取到当前登录的用户,判断当前登录用户的权限集合中是否具备当前请求所需要的权限。具体的判断逻辑没啥好说的,就是看集合中是否存在某个字符串,从判断的逻辑中我们也可以看出来,这个权限也是不支持通配符的。

不过我还是觉得官方的方案更好一些,接下来在视频中,我会带领小伙伴们对 RuoYi-Vue 脚手架进行一个小改造,把这个按照 Spring Security 官方的思路来定制一下,这个咱们视频中见,对视频感兴趣的小伙伴,戳戳戳这里:TienChin 项目配套视频来啦

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江南一点雨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
聊聊如何自定义parallelStream的线程池
org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java
code4it
2023/07/08
1.3K0
高并发之——创建线程池居然有这么多方式...
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
2780
【小家java】Java线程池之---ForkJoinPool线程池的使用以及原理
Java 7 引入了一种新的并发框架—— Fork/Join Framework。同时引入了一种新的线程池:ForkJoinPool(ForkJoinPool.coomonPool)
YourBatman
2019/09/03
2.1K0
【小家java】Java线程池之---ForkJoinPool线程池的使用以及原理
ForkJoin看这篇就够了![通俗易懂]
分治法是计算机领域常用的算法中的其中一个,主要思想就是将将一个规模为N的问题,分解成K个规模较小的子问题,这些子问题相互独立且与原问题性质相同;求解出子问题的解,合并得到原问题的解。
全栈程序员站长
2022/11/05
9090
ForkJoin看这篇就够了![通俗易懂]
java线程池(五):ForkJoinPool源码分析之一(外部提交及worker执行过程)
在前文中介绍了如何使用ForkJoinPool和ForkJoin的一些基本原理。现在继续来分析ForkJoin,原本计划从源码开始分析。但是ForkJoinPool的源码太过复杂。后续得分好几部分来讲解。今天先做一个总体的介绍。
冬天里的懒猫
2020/09/27
2.9K0
java线程池(五):ForkJoinPool源码分析之一(外部提交及worker执行过程)
五种线程池的对比与使用
通过源码可以看出底层调用的是ThreadPoolExecutor方法,传入一个同步的阻塞队列实现缓存。
爱撸猫的杰
2020/01/08
1K0
阿里代码规约为什么不让使用Executors包装好线程池呢?
在Executors下主要有5个静态方法: 1. Executors.newWorkStealingPool JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争,此构造方法把CPU数量设置为默认的并行度 public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExce
袁新栋-jeff.yuan
2020/08/26
6030
阿里代码规约为什么不让使用Executors包装好线程池呢?
谈谈fork/join实现原理
害,又是一个炒冷饭的时间。fork/join是在jdk1.7中出现的一个并发工作包,其特点是可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。从而达到多线程分发任务,达到高效处理的目的。
烂猪皮
2021/09/02
6820
阿里的面试题带你认识ForkJoinPool
随着在硬件上多核处理器的发展和广泛使用,并发编程成为程序员必须掌握的一门技术,在面试中也经常考查面试者并发相关的知识。
狼王编程
2021/06/01
4940
阿里的面试题带你认识ForkJoinPool
JUC并行计算框架 Fork/Join 原理图文详解&代码示例
关键词:divide and conquer algorithm,work-stealing,WorkQueue
一个会写诗的程序员
2020/05/08
3.3K0
任务拆分计算利器 ForkJoin 框架玩法详解
从 JDK 1.7 开始,引入了一种新的 Fork/Join 线程池框架,它可以把一个大任务拆成多个小任务并行执行,最后汇总执行结果。
Java极客技术
2024/01/19
2270
任务拆分计算利器 ForkJoin 框架玩法详解
还有年味的文章,ForkJoinPool 大型图文现场
并发工具类我们已经讲了很多,这些工具类的「目标」是让我们只关注任务本身,并且忽视线程间合作细节,简化了并发编程难度的同时,也增加了很多安全性。工具类的对使用者的「目标」虽然一致,但每一个工具类本身都有它独特的应用场景,比如:
用户4172423
2021/02/25
6960
还有年味的文章,ForkJoinPool 大型图文现场
啥?用了并行流还更慢了
Java 8给大家带来了一个非常便捷的多线程工具:并行流,一改往日Java多线程繁琐的编程规范,只需要一行代码,就可以让一个多线程跑起来,似乎让很多人忘记了被多线程支配的恐惧,这篇文章给大家分享一个真实的生产故障,由于在消费消息的处理器中使用了Java 8的并行流,导致集群消费消息的能力急速下降,造成线上消息堆积,引发故障。可能有朋友会好奇,到底是什么场景让并行流起了反作用?
苦味代码
2021/03/11
5780
啥?用了并行流还更慢了
压测引发的思考——高并发用同步还是异步好?
最近616大促,公司的服务需要进行压力测试,使用了公司自己的压测平台。对生产机器进行了摘流量压测。由于服务都是查询的接口,也算是很好压测的。这篇文章大概描述压测过程过程,主要是压测出的问题的解决以及对ForkJoinPool学习和了解。 (标题党???????)
袁新栋-jeff.yuan
2021/12/07
8740
压测引发的思考——高并发用同步还是异步好?
大任务拆分,让并行嗨起来!
之前我们学习了线程池ThreadPoolExecutor,它通过对任务队列和线程的有效管理实现了对并发任务的处理。
程序员蜗牛
2024/02/20
3340
大任务拆分,让并行嗨起来!
异步任务编排神器CompletableFuture
但是当异步任务繁多并且复杂,任务间可能存在依赖关系时,Future接口变得不太好用
菜菜的后端私房菜
2024/08/15
3580
Fork/Join框架原理和使用探秘 顶
Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。也是当前执行速度最快的并发框架。
算法之名
2019/08/26
1.1K0
Fork/Join框架原理和使用探秘
                                                                            顶
(juc系列)forkjoin框架源码学习
JUC系列提供的又一个线程池,采用分治思想,及工作窃取策略,能获得更高的并发性能.
呼延十
2021/10/18
4750
parallelStream的坑,不踩不知道,一踩吓一跳
很多同学喜欢使用lambda表达式,它允许你定义短小精悍的函数,体现你高超的编码水平。当然,这个功能在某些以代码行数来衡量工作量的公司来说,就比较吃亏一些。
xjjdog
2020/09/23
1.1K0
Fork/Join框架基本使用[通俗易懂]
ava.util.concurrent.ForkJoinPool由Java大师Doug Lea主持编写,它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。本文中对Fork/Join框架的讲解,基于JDK1.8+中的Fork/Join框架实现,参考的Fork/Join框架主要源代码也基于JDK1.8+。
全栈程序员站长
2022/07/22
3840
Fork/Join框架基本使用[通俗易懂]
推荐阅读
相关推荐
聊聊如何自定义parallelStream的线程池
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验