实战 - 利用 ThreadLocal 线程局部变量实现数据缓存

文章目录

    • 1. 利用 ThreadLocal 缓存 AssetBranchCache 数据
      • 1. 定义 AssetBranchCache 类
      • 2. 定义 BranchContext 类操作 AssetBranchCache 对象
      • 3. 配置拦截器实时更新和清除缓存数据
      • 4. 定义 SaasThreadContextDataHolderBranch 类持有 AssetBranchCache 对象
      • 5. 定义 SaasThreadContextHolder 接口
      • 6. 定义 SaasThreadContextHolderBranch 组件
      • 7. 定义 SaasThreadContextUtil 工具类
      • 8. 定义 BranchNameCache 组件获取 AssetBranchCache 数据
    • 2. 业务使用
    • 1. 请求入口 IncidentController
      • 1. 请求参数的封装
      • 2. 响应实体的封装
    • 2. 获取延迟加载数据 IEventDelayLoadService

业务逻辑:安全事件,安全告警,风险主机列表页面会需要一些延迟加载数据,因此当我们进入这个页面时就会请求该接口获取延迟加载数据。

1. 利用 ThreadLocal 缓存 AssetBranchCache 数据

ThreadLocal 是 Java 中的一个类,它提供了一种线程局部变量的机制。线程局部变量是指只能被同一个线程访问和修改的变量,不同线程之间互不干扰。ThreadLocal 可以用来解决多线程并发访问共享变量的问题。

ThreadLocal可以用来实现数据缓存,即在一个线程中缓存一些上下文相关的数据,以便在该线程的后续操作中使用。在使用ThreadLocal实现上下文缓存时,可以将需要缓存的数据存储在ThreadLocal对象中,然后在需要使用这些数据的地方,通过ThreadLocal对象获取数据。由于每个线程都有自己的ThreadLocal对象,因此不同线程之间的数据不会相互干扰,从而保证了线程安全性。

1. 定义 AssetBranchCache 类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AssetBranchCache {
    private UnmodifiableMap<Integer, String> idToNameMap;
}

2. 定义 BranchContext 类操作 AssetBranchCache 对象

BranchContext 类定义了一个静态的 ThreadLocal 变量 BRANCH_CACHE_THREAD_LOCAL,用于存储当前线程的 AssetBranchCache 对象:

public class BranchContext {

    private static final ThreadLocal<@Nullable AssetBranchCache> BRANCH_CACHE_THREAD_LOCAL = new TransmittableThreadLocal<>();

    /**
     * 设置 assetBranchCache
     */
    public static void load(@Nullable AssetBranchCache assetBranchCache) {
        BRANCH_CACHE_THREAD_LOCAL.set(assetBranchCache);
    }

    /**
     * 获取 assetBranchCache
     */
    @Nullable
    public static AssetBranchCache save() {
        return BRANCH_CACHE_THREAD_LOCAL.get();
    }

    /**
     * 清除 assetBranchCache 信息
     */
    public static void remove() {
        BRANCH_CACHE_THREAD_LOCAL.remove();
    }
}

3. 配置拦截器实时更新和清除缓存数据

在使用ThreadLocal时需要注意内存泄漏问题,因为ThreadLocal对象是存储在每个线程的ThreadLocalMap中的,如果不及时清除ThreadLocal对象,可能会导致内存泄漏。

@Data
@Configuration
@CustomLog
public class IncidentInterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors( @NotNull InterceptorRegistry registry) {
        registry.addInterceptor(
                new HandlerInterceptorAdapter() {
                    // preHandle方法:在请求处理前会执行
                    @Override
                    public boolean preHandle( @NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
                        BranchContext.remove();
                        return true;
                    }
                    // afterCompletion方法:在请求处理后会执行
                    @Override
                    public void afterCompletion(@NotNull HttpServletRequest request,@NotNull HttpServletResponse response, @NotNull Object handler,@Nullable Exception ex) throws Exception {
                        BranchContext.remove();
                    }
                }
        );
    }
}

该类实现WebMvcConfigurer接口,用于配置拦截器。在addInterceptors方法中,注册了一个HandlerInterceptorAdapter类型的拦截器,该拦截器在请求处理前和请求处理后都会执行。在preHandle方法中,调用了BranchContext类的remove方法,用于清除ThreadLocal 缓存的数据。在afterCompletion方法中也调用了BranchContext类的remove方法,确保在请求处理完成后也清除当前线程的ThreadLocal 缓存的数据。

使用拦截器可以保证ThreadLocal中缓存的数据是实时更新的,每次请求进来都会清除缓存中的数据,重新加载。

4. 定义 SaasThreadContextDataHolderBranch 类持有 AssetBranchCache 对象

为了在项目的各个地方获取ThreadLocal中缓存的资产组数据,可以定义一个线程上下文资产组数据持有者类 SaasThreadContextDataHolderBranch

public interface SaasThreadContextDataHolder {

}
@Data
@AllArgsConstructor
public class SaasThreadContextDataHolderBranch implements SaasThreadContextDataHolder {

    @Nullable
    private final AssetBranchCache assetBranchCache;
}

5. 定义 SaasThreadContextHolder 接口

SaasThreadContextHolder 接口中定义了一些方法来管理 SaasThreadContextDataHolder 对象

public interface SaasThreadContextHolder<T extends SaasThreadContextDataHolder> {

    // 获取 SaasThreadContextDataHolder 类
    @NotNull
    Class<T> getSaasThreadContextDataHolderClass();

    // 尝试加载SaasThreadContextDataHolder
    default boolean tryLoad(@NotNull SaasThreadContextDataHolder holder) {
        if (!getSaasThreadContextDataHolderClass().isInstance(holder)) {
            return false;
        }
        this.load((T) holder);
        return true;
    }

    // 加载 SaasThreadContextDataHolder
    void load(@NotNull T holder);

    // 存储 SaasThreadContextDataHolder
    @NotNull
    T save();

    // 清理 SaasThreadContextDataHolder
    void remove();
}

6. 定义 SaasThreadContextHolderBranch 组件

// 应用程序中自动发现和注册该组件
@AutoService(SaasThreadContextHolder.class)
public class SaasThreadContextHolderBranch implements SaasThreadContextHolder<SaasThreadContextDataHolderBranch> {

    @NotNull
    @Override
    public Class<SaasThreadContextDataHolderBranch> getSaasThreadContextDataHolderClass() {
        return SaasThreadContextDataHolderBranch.class;
    }

     // 加载 SaasThreadContextDataHolderBranch 设置 ThreadLocal 缓存数据
    @Override
    public void load(@NotNull SaasThreadContextDataHolderBranch holder) {
        AssetBranchCache assetBranchCache = holder.getAssetBranchCache();
        if (assetBranchCache != null) {
            BranchContext.load(assetBranchCache);
        }
    }

    // 获取 ThreadLocal 缓存数据填充 SaasThreadContextDataHolderBranch 
    @NotNull
    @Override
    public SaasThreadContextDataHolderBranch save() {
        
        return new SaasThreadContextDataHolderBranch(BranchContext.save());
    }

    // 清除 ThreadLocal 缓存数据
    @Override
    public void remove() {
        BranchContext.remove();
    }
}

7. 定义 SaasThreadContextUtil 工具类

public class SaasThreadContextUtil {

    // 获取Spring容器中所有实现了SaasThreadContextHolder接口的组件
    @NotNull
    static List<SaasThreadContextHolder<?>> getSaasThreadContextHolders() {
        return (List) IterableUtils.toList(
                ServiceLoader.load(SaasThreadContextHolder.class)
        );
    }

    @NotNull
    public static List<SaasThreadContextDataHolder> save() {
        List<SaasThreadContextHolder<?>> saasThreadContextHolders = getSaasThreadContextHolders();
        List<SaasThreadContextDataHolder> saasThreadContextDataHolders = new ArrayList<>(
                saasThreadContextHolders.size()
        );
        for (SaasThreadContextHolder<?> saasThreadContextHolder : saasThreadContextHolders) {
            saasThreadContextDataHolders.add(saasThreadContextHolder.save());
        }
        return saasThreadContextDataHolders;
    }

    public static void load(
            @NotNull List<SaasThreadContextDataHolder> saasThreadContextDataHolders
    ) {
        for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
            for (SaasThreadContextDataHolder saasThreadContextDataHolder : saasThreadContextDataHolders) {
                if (saasThreadContextHolder.tryLoad(saasThreadContextDataHolder)) {
                    break;
                }
            }
        }
    }

    public static void remove() {
        for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
            saasThreadContextHolder.remove();
        }
    }
}

8. 定义 BranchNameCache 组件获取 AssetBranchCache 数据

@Component
@CustomLog
public class BranchNameCache {

    @Getter
    @Setter(onMethod_ = @Autowired)
    private IBranchService branchService;

    @Setter(onMethod_ = @Autowired)
    private ApplicationContext applicationContext;

    // 查询缓存数据 AssetBranchCache
    public AssetBranchCache getAssetBranchCache() {
        // 从ThreadLocal对象中获取缓存数据,如果不为null,直接返回
        AssetBranchCache result = BranchContext.save();
        if (result != null) {
            return result;
        }
        // 查询缓存数据
        BranchNameCache bean = applicationContext.getBean(BranchNameCache.class);
        String cacheKey = String.format(RedisKey.ASSET_BRANCH, Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getTenantId())+ CacheUtil.getCacheUserRegion();
        result = bean.getAssetBranchInfo(cacheKey);
        // 将缓存数据设置到ThreadLocal对象中
        BranchContext.load(result);
        return result;
    }

    // Spring Cache 注解
    @Cacheable(value = "assetBranchCache", key = "#cacheKey")
    public AssetBranchCache getAssetBranchInfo(String cacheKey) {
        List<Branch> branches = this.getBranchService().listAll();
        Map<Integer, String> idToNameMap = new HashMap<>(branches.size());
        branches.forEach(branch -> {
            idToNameMap.put(branch.getId().intValue(), branch.getName());
        });
        return new AssetBranchCache( (UnmodifiableMap<Integer, String>) UnmodifiableMap.unmodifiableMap(idToNameMap));
    }
    
    public Map<Integer, String> getBranchIdToNameMap() {
        return this.getAssetBranchCache().getIdToNameMap();
    }
}

① getAssetBranchCache方法:获取 AssetBranchCache数据,先从ThreadLocal对象中查询,如果查询结果不为null,直接返回,否则调用资产服务查询,最后将查询结果设置到ThreadLocal对象中。

② @Cacheable注解:Spring框架的注解,用于缓存方法的返回值。具体来说,@Cacheable注解表示该方法的返回值应该被缓存,value属性指定了缓存的名称,key属性指定了缓存的键值,即用于查找缓存的唯一标识符。在这个例子中,缓存的名称为"assetBranchCache",缓存的键值为cacheKeycacheKey是一个方法参数或者是一个表达式,用于生成缓存的键值。如果缓存中已经存在相同的键值,则直接返回缓存中的值,否则执行方法并将返回值存入缓存中。

2. 业务使用

1. 请求入口 IncidentController

@Api("安全事件信息")
@CustomLog
@Validated
@ResponseResult
@RestController
@RequestMapping("/api/v1/incidents")
public class IncidentController {

    @CheckValidateAble
    @PreAuthorize("hasAnyAuthority('superAdmin','incidentQuery')")
    @PostMapping("/delayloaddata")
    @ApiOperation("安全事件额外数据延迟加载")
    @OperateLog(handle = { LoggerEnum.operation }, target = "operate.incident.log", action = "operate.incident.delayloaddata.log")
    public IncidentDelayLoadData delayLoadData(
            @RequestBody @Validated IncidentDelayLoadDataQo qo
    ) {
        Map<String, IncidentDelayLoadData.EachIncidentDelayLoadData> incidentMap = delayLoadService.richInfo(qo);
        return IncidentDelayLoadData.builder().incidentMap(incidentMap).build();
    }
}

1. 请求参数的封装

@Data
@Validated
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadDataQo {

    @ApiModelProperty(value = "数据类型", example = "INCIDENT")
    RiskTypeEnum riskType = RiskTypeEnum.INCIDENT;

    @Valid
    @Size(max = 1000, message = "延迟加载的数据最大1000条")
    @NotNull
    private List<SecurityEntry> data;
}

① 风险类型:安全事件,安全告警,风险资产

public enum RiskTypeEnum {

    /**
     * 安全事件
     */
    INCIDENT,

    /**
     * 安全告警
     */
    ALERT,

    MOCK,

    /**
     * 风险资产
     */
    RISK_ASSET;
}

② 事件延迟加载数据:

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class SecurityEntry {

    // 安全事件id、安全告警id、风险主机id
    @NotBlank
    @Pattern(regexp = "[^\"'&<>()+%\\\\]+")
    private String uuId;

    @Nullable
    private Long assetId;

    @Nullable
    private Long lastTime;

    @Nullable
    private List<@NotBlank @Pattern(regexp = "[^\"'&<>()+%\\\\]+") String> alertIds;

}

@Nullable 注解是一种用于 Java 代码中的注解,它用于标记一个方法的返回值、参数或字段可以为 null。

③ 请求参数示例:

{
    "data": [
        {
            "uuId": "alert-ad41da97-94b2-4091-9a6d-a6972a7ce919",
            "assetId": 17582,
            "lastTime": 1690460941
        },
        {
            "uuId": "alert-a6a2d31f-34df-4176-bca5-1ee368e41006",
            "assetId": 17581,
            "lastTime": 1690460896
        }
    ],
    "riskType": "ALERT"
}

2. 响应实体的封装

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadData {

    @ApiModelProperty("事件延迟加载数据")
    private Map<String, EachIncidentDelayLoadData> incidentMap;

    @Data
    @Builder
    @ApiModel(description = "每条事件的延迟加载数据")
    @NoArgsConstructor
    @AllArgsConstructor
    public static class EachIncidentDelayLoadData {

        @ApiModelProperty("一键遏制禁用标记")
        private Boolean oneClickDisposeDisabled;
        private String oneClickDisposeStatus;

        private String newestUsername;
        private String checkOutUsername;
        private String responsible;
        private AlertSeverityNumber alertSeverityNumber;
        private Long remarkNumber;
        private Integer connectStatus;

        @ApiModelProperty("当前资产类别信息")
        private MagnitudeInfo magnitude;

        @ApiModelProperty("处置入口威胁根除判断")
        private TableCellDataVo entryDisposal;

        @ApiModelProperty("主机IP")
        private HostIpDelayVo hostIp;

        @ApiModelProperty("主机资产组")
        private HostBranchDelayVo hostBranchId;

        @ApiModelProperty("主机业务组")
        private HostGroupDelayVo hostGroupIds;

        @ApiModelProperty("目的IP")
        private SrcDstIpDelayVo dstIp;

        @ApiModelProperty("源IP")
        private SrcDstIpDelayVo srcIp;

        @ApiModelProperty("源IP")
        private AssetUserDelayVo assetUser;
    }
}

① 外部类 IncidentDelayLoadData:包含一个名为incidentMapMap对象,其中键为事件ID,值为EachIncidentDelayLoadData对象,表示每个事件的延迟加载数据。

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadData {

    @ApiModelProperty("事件延迟加载数据")
    private Map<String, EachIncidentDelayLoadData> incidentMap;
    
    // ....
}

② 静态内部类 EachIncidentDelayLoadData:包含了事件的各种属性,如一键遏制禁用标记、最新用户名、检出用户名、责任人、告警严重性等等。其中,MagnitudeInfoTableCellDataVoHostIpDelayVoHostBranchDelayVoHostGroupDelayVoSrcDstIpDelayVoAssetUserDelayVo都是其他自定义类的对象,用于表示不同的属性。

③ 静态内部类的使用:

Java中的静态内部类是指在一个类的内部定义的静态类。静态内部类与非静态内部类的区别在于,静态内部类不依赖于外部类的实例,可以直接通过外部类名访问,而非静态内部类必须依赖于外部类的实例才能访问。

静态内部类可以访问外部类的静态成员和方法,但不能访问外部类的非静态成员和方法。静态内部类也可以定义静态成员和方法,这些静态成员和方法与外部类的静态成员和方法类似,可以直接通过类名访问。

public class OuterClass {
    // 外部类的成员和方法

    public static class StaticInnerClass {
        // 静态内部类的成员和方法
    }
}

静态内部类的实例化方式如下:

OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();

④ 响应数据示例:

{
    "strCode": null,
    "message": "成功",
    "data": {
        "incidentMap": {
            "lqlapp35test3-452799db-2557-424b-ba86-aa1f2d72eb3e": {
                "oneClickDisposeDisabled": true,
                "oneClickDisposeStatus": "WAIT_DEAL",
                "newestUsername": null,
                "checkOutUsername": null,
                "responsible": null,
                "alertSeverityNumber": null,
                "remarkNumber": 0,
                "connectStatus": null,
                "magnitude": null,
                "entryDisposal": {
                    "originalValue": true,
                    "renderValue": null
                },
                "hostIp": {
                    "originalValue": "6.6.6.7",
                    "renderValue": "6.6.6.7(美国)"
                },
                "hostBranchId": {
                    "originalValue": 0,
                    "renderValue": ""
                },
                "hostGroupIds": {
                    "count": 0,
                    "data": []
                },
                "dstIp": null,
                "srcIp": null,
                "assetUser": null
            },
            "16c28c0d-649a-dispose-entity111-5d419e8790129": {
                "oneClickDisposeDisabled": false,
                "oneClickDisposeStatus": "WAIT_DEAL",
                "newestUsername": null,
                "checkOutUsername": null,
                "responsible": null,
                "alertSeverityNumber": null,
                "remarkNumber": 0,
                "connectStatus": null,
                "magnitude": null,
                "entryDisposal": {
                    "originalValue": true,
                    "renderValue": null
                },
                "hostIp": {
                    "originalValue": "192.168.40.29",
                    "renderValue": "192.168.40.29(管理IP范围)"
                },
                "hostBranchId": {
                    "originalValue": 1,
                    "renderValue": ""
                },
                "hostGroupIds": {
                    "count": 0,
                    "data": []
                },
                "dstIp": null,
                "srcIp": null,
                "assetUser": null
            }
        }
    },
    "code": 0
}

2. 获取延迟加载数据 IEventDelayLoadService

利用线程池管理并发任务的执行。通过将任务提交到线程池中,让线程池自动分配线程来执行任务,从而实现并发执行。线程池还可以控制并发任务的数量,避免系统资源被过度占用,从而提高系统的稳定性和可靠性。

@CustomLog
@Service
public class EventDelayLoadServiceImpl implements IEventDelayLoadService, ApplicationListener<ContextStoppedEvent> {

   // 定义一个线程池
   private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            5,
            20,
            4,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(),
            new ThreadFactoryBuilder().setNameFormat(IncidentResponseDispositionServiceImpl.class.getSimpleName() + "-pool-%d")
                    .setDaemon(true).build(),
            new ThreadPoolExecutor.DiscardOldestPolicy()
    );

    // 监听应用程序上下文停止事件,用于关闭线程资源
    @Override
    public void onApplicationEvent(@NotNull ContextStoppedEvent ignored) {
        try {
            THREAD_POOL_EXECUTOR.shutdown();
        } catch (Exception e) {
            log.error("停止线程池失败", e);
        }
    }
    
    @Override
    public Map<String, IncidentDelayLoadData.EachIncidentDelayLoadData> richInfo(IncidentDelayLoadDataQo delayLoadDataQo) {
        List<SecurityEntry> securityEntryList = delayLoadDataQo.getData();
        if (CollectionUtils.isEmpty(securityEntryList)) {
            return Collections.emptyMap();
        }
        RiskTypeEnum riskType = delayLoadDataQo.getRiskType();
        ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data = new ConcurrentHashMap<>(securityEntryList.size());
        securityEntryList.forEach(entry -> data.put(entry.getUuId(), new IncidentDelayLoadData.EachIncidentDelayLoadData()));
        
        List<Callable<Void>> callables;
        // 根据riskType的不同,调用不同的方法获取Callable列表callables
        switch (riskType) {
        case INCIDENT:
            callables = getIncidentCallableList(securityEntryList, data);
            break;       
        case ALERT:
            callables = getAlertCallableList(securityEntryList, data);
            break;       
        case RISK_ASSET:
            callables = getRiskAssetCallableList(securityEntryList, data);
            break;
        default:
            return Collections.emptyMap();
        }
        // 通过线程池执行callables中的任务,并将结果存储在ConcurrentHashMap类型的数据data中
        try {
            List<Future<Void>> futures = THREAD_POOL_EXECUTOR.invokeAll(callables, 1, TimeUnit.MINUTES);
            for (Future<Void> future : futures) {
                try {
                    future.get();
                } catch (Exception e) {
                    log.warn("incident delay data,future get warn", e);
                }
            }
        } catch (Exception e) {
            log.warn("incident delay data warn ", e);
        }
        return data;
    }
}

以获取安全告警页面延迟加载数据的线程执行任务为例:getAlertCallableList

private List<Callable<Void>> getAlertCallableList(List<SecurityEntry> securityEntryList, ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data) {
    HashMap<@NotNull String, @Nullable Long> uuIdToAssetId = new HashMap<>(securityEntryList.size());
    for (SecurityEntry securityEntry : securityEntryList) {
        uuIdToAssetId.put(securityEntry.getUuId(), securityEntry.getAssetId());
    }
    
    try {
        List<Alert> alerts = alertDao.getAlerts(securityEntryList.stream().map(SecurityEntry::getUuId).collect(Collectors.toList()));
        if (CollectionUtils.isEmpty(alerts)) {
            log.warn("no alert data to deal");
            data.clear();
            return List.of();
        }
        // 尝试加载ThreadLocal对象中的缓存数据
        List<SaasThreadContextDataHolder> contextDataHolders = SaasThreadContextUtil.save();
        
        Callable<Void> assetCallable = getAssetCallable(securityEntryList, data, uuIdToAssetId, contextDataHolders);
        Callable<Void> assetUserCallable = getAssetUserCallable(securityEntryList, data, uuIdToAssetId, contextDataHolders);
        Callable<Void> assetBranchCallable = getAlertAssetBranchCallable(securityEntryList, alerts, data, contextDataHolders);
        Callable<Void> remarkNumberCallable = getRemarkNumberCallable(securityEntryList, ALERT, data, contextDataHolders);
        return Arrays.asList(
                assetCallable,
                assetUserCallable,
                assetBranchCallable,
                remarkNumberCallable
        );
    } catch (IOException | JsonSerializeException e) {
        throw new IncidentRuntimeException(I18nUtils.i18n(I18nConstant.AlertOperateConstant.EXCEPTION_TO_BE_TRANSFERRED), e);
    }
}

在该方法中定义了一个获取安全告警资产组信息线程体,继续看下 getAlertAssetBranchCallable 方法:

@CustomLog
@Service
public class EventDelayLoadServiceImpl implements IEventDelayLoadService, ApplicationListener<ContextStoppedEvent> {
    
    @Setter(onMethod_ = { @Autowired })
    private BranchNameCache branchNameCache; 
    
    private Callable<Void> getAssetBranchCallable(
            List<SecurityEntry> securityEntryList, List<Incident> incidentList, ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data,
            List<SaasThreadContextDataHolder> contextDataHolders
    ) {
        return () -> {
            try {
                // 设置ThreadLocal对象中的缓存数据
                SaasThreadContextUtil.load(contextDataHolders);
                // BranchNameCache 获取 AssetBranchCache 数据
                Map<Integer, String> branchIdToNameMap = branchNameCache.getBranchIdToNameMap();
                for (SecurityEntry securityEntry : securityEntryList) {
                    //设置响应数据
                    eachIncidentDelayLoadData.setHostBranchId(
                            new HostBranchDelayVo(branchId, HostIpUtils.getHostBranch(assetId, branchId, branchIdToFullNameMap))
                    );
                }
                return null;
            } finally {
                // 清除ThreadLocal对象中的缓存数据
                SaasThreadContextUtil.remove();
            }
        };
    }
}

你可能感兴趣的:(【实战项目总结】,缓存,java,jvm)