Commit ebb3a04251305d3e3cd5d220f267bd4b45a783c2

Authored by YunaiV
1 parent 6a371f06

优化多租户 Job 的实现,采用 AOP 替代 BeanPostProcessor,提升启动速度

yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.tenant.config;
2 2  
3   -import cn.hutool.core.annotation.AnnotationUtil;
4 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 4 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
6   -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
7 5 import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
8 6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
9 7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
10   -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
11   -import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
  8 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
12 9 import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
13 10 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
14 11 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
... ... @@ -20,8 +17,6 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
20 17 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
21 18 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
22 19 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
23   -import org.springframework.beans.BeansException;
24   -import org.springframework.beans.factory.config.BeanPostProcessor;
25 20 import org.springframework.boot.autoconfigure.AutoConfiguration;
26 21 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27 22 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -100,25 +95,8 @@ public class YudaoTenantAutoConfiguration {
100 95 // ========== Job ==========
101 96  
102 97 @Bean
103   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
104   - public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
105   - return new BeanPostProcessor() {
106   -
107   - @Override
108   - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
109   - if (!(bean instanceof JobHandler)) {
110   - return bean;
111   - }
112   - // 有 TenantJob 注解的情况下,才会进行处理
113   - if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
114   - return bean;
115   - }
116   -
117   - // 使用 TenantJobHandlerDecorator 装饰
118   - return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
119   - }
120   -
121   - };
  98 + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
  99 + return new TenantJobAspect(tenantFrameworkService);
122 100 }
123 101  
124 102 // ========== Redis ==========
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJob.java
... ... @@ -8,7 +8,7 @@ import java.lang.annotation.Target;
8 8 /**
9 9 * 多租户 Job 注解
10 10 */
11   -@Target({ElementType.TYPE})
  11 +@Target({ElementType.METHOD})
12 12 @Retention(RetentionPolicy.RUNTIME)
13 13 public @interface TenantJob {
14 14 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java renamed to yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java
1 1 package cn.iocoder.yudao.framework.tenant.core.job;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.exceptions.ExceptionUtil;
4 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 6 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
8   -import lombok.AllArgsConstructor;
  7 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  8 +import lombok.RequiredArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.aspectj.lang.ProceedingJoinPoint;
  11 +import org.aspectj.lang.annotation.Around;
  12 +import org.aspectj.lang.annotation.Aspect;
9 13  
10 14 import java.util.List;
11 15 import java.util.Map;
12 16 import java.util.concurrent.ConcurrentHashMap;
13 17  
14 18 /**
15   - * 多租户 JobHandler 装饰器
  19 + * 多租户 JobHandler AOP
16 20 * 任务执行时,会按照租户逐个执行 Job 的逻辑
17 21 *
18 22 * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
19 23 *
20 24 * @author 芋道源码
21 25 */
22   -@AllArgsConstructor
23   -public class TenantJobHandlerDecorator implements JobHandler {
  26 +@Aspect
  27 +@RequiredArgsConstructor
  28 +@Slf4j
  29 +public class TenantJobAspect {
24 30  
25 31 private final TenantFrameworkService tenantFrameworkService;
26   - /**
27   - * 被装饰的 Job
28   - */
29   - private final JobHandler jobHandler;
30 32  
31   - @Override
32   - public final String execute(String param) throws Exception {
  33 + @Around("@annotation(tenantJob)")
  34 + public String around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) {
33 35 // 获得租户列表
34 36 List<Long> tenantIds = tenantFrameworkService.getTenantIds();
35 37 if (CollUtil.isEmpty(tenantIds)) {
... ... @@ -38,19 +40,15 @@ public class TenantJobHandlerDecorator implements JobHandler {
38 40  
39 41 // 逐个租户,执行 Job
40 42 Map<Long, String> results = new ConcurrentHashMap<>();
41   - tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
42   - try {
43   - // 设置租户
44   - TenantContextHolder.setTenantId(tenantId);
45   - // 执行 Job
46   - String result = jobHandler.execute(param);
47   - // 添加结果
48   - results.put(tenantId, result);
49   - } catch (Exception e) {
50   - throw new RuntimeException(e);
51   - } finally {
52   - TenantContextHolder.clear();
53   - }
  43 + tenantIds.parallelStream().forEach(tenantId -> {
  44 + // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
  45 + TenantUtils.execute(tenantId, () -> {
  46 + try {
  47 + joinPoint.proceed();
  48 + } catch (Throwable e) {
  49 + results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
  50 + }
  51 + });
54 52 });
55 53 return JsonUtils.toJsonString(results);
56 54 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/DemoJob.java
... ... @@ -11,14 +11,14 @@ import javax.annotation.Resource;
11 11 import java.util.List;
12 12  
13 13 @Component
14   -@TenantJob // 标记多租户
15 14 public class DemoJob implements JobHandler {
16 15  
17 16 @Resource
18 17 private AdminUserMapper adminUserMapper;
19 18  
20 19 @Override
21   - public String execute(String param) throws Exception {
  20 + @TenantJob // 标记多租户
  21 + public String execute(String param) {
22 22 System.out.println("当前租户:" + TenantContextHolder.getTenantId());
23 23 List<AdminUserDO> users = adminUserMapper.selectList();
24 24 return "用户数量:" + users.size();
... ...