Spring中 @Transactional 和 @Async注解 容易不消停

在Spring生态的企业级开发中,@Transactional(声明式事务管理)与@Async(异步任务执行)是两大核心增强注解。前者为数据一致性提供原子性保障,后者通过并发执行提升系统吞吐量与响应速度,二者的合理搭配是构建高性能、高可靠系统的关键。然而,由于Spring对事务上下文与线程模型的底层设计特性,两者混用时常出现事务失效、数据紊乱、异步任务执行异常等问题,成为开发者的高频"踩坑点"。本文将从注解底层实现原理切入,结合丰富的实战场景与进阶案例,深度拆解不同混用模式的运行机制、风险点及解决方案,同时补充问题排查技巧与优化实践,为开发者提供全方位的实践指南。

一、核心注解底层实现原理深度剖析

要彻底掌握两者的兼容性问题,必须先明晰其底层实现机制------两者均基于Spring AOP(面向切面编程)的动态代理技术,但核心设计目标与执行逻辑存在本质差异,这也是混用矛盾的根源所在。

1.1 @Transactional:基于AOP的事务上下文管理

Spring的声明式事务通过AOP动态代理机制实现,核心流程如下:

  1. 代理对象生成 :当Bean被@Transactional标记时,Spring会通过JDK动态代理(针对接口实现类)或CGLIB动态代理(针对无接口类)为其创建代理对象,原始Bean的方法逻辑被封装在代理逻辑中。

  2. 事务上下文初始化 :当调用代理对象的事务方法时,AOP切面会先获取当前线程的事务上下文(通过ThreadLocal存储),根据注解的propagation(传播属性)、isolation(隔离级别)、timeout(超时时间)等参数,判断是否需要创建新事务、加入现有事务或挂起现有事务。

  3. 事务执行与提交/回滚 :若创建新事务,会通过事务管理器(如DataSourceTransactionManager)获取数据库连接,关闭自动提交模式,然后执行原始方法逻辑;若方法执行无异常,切面会触发事务提交;若捕获到未被捕获的运行时异常(默认仅回滚运行时异常),则触发事务回滚,最终释放数据库连接。

关键特性:事务上下文与当前线程强绑定,依赖ThreadLocal实现线程隔离,确保不同线程的事务互不干扰。这一特性是后续与@Async混用出现问题的核心原因。

1.2 @Async:基于线程池的异步任务调度

Spring的异步执行机制核心是"线程切换",通过独立线程池脱离原调用线程执行任务,实现并行处理,核心流程如下:

  1. 异步切面拦截 :当方法被@Async标记时,Spring的AsyncAnnotationBeanPostProcessor会为其创建AOP切面,拦截方法调用。

  2. 线程池选择与任务提交 :切面会根据注解的value属性指定的线程池名称,从Spring容器中获取对应的线程池(默认使用SimpleAsyncTaskExecutor,但该线程池无上限,生产环境不推荐),将目标方法逻辑封装为RunnableCallable任务,提交至线程池。

  3. 异步任务执行 :线程池中的空闲线程会获取任务并执行,原调用线程无需等待任务完成,直接返回结果(若为无返回值方法则直接结束)。执行过程中,任务的异常会被线程池捕获(若为Callable任务,异常会封装在Future对象中)。

关键特性:异步方法与原调用线程属于不同的线程上下文,所有线程相关的状态(如ThreadLocal存储的信息)无法直接传递,这与@Transactional的线程绑定特性形成核心冲突。

1.3 两者混用的核心矛盾:事务上下文的跨线程传播问题

结合上述实现原理,两者混用的核心矛盾可概括为:

@Transactional的事务上下文依赖ThreadLocal与当前线程绑定,而@Async会触发线程切换,导致新线程无法获取原线程的事务上下文,进而引发事务传播失效、数据一致性破坏等问题。后续的实战场景分析,本质上都是围绕这一核心矛盾展开的不同表现形式。

二、实战场景深度解析:混用模式与风险规避

以企业级开发中最典型的"银行转账"业务为核心场景(需求:实现账户余额增减的原子性,同时支持异步打印转账凭证、异步记录操作日志等附加功能),拆解四种典型混用模式,深入分析其运行效果、底层原因及实践建议。

2.1 模式1:@Async方法作为入口,内部调用@Transactional方法(推荐)

该模式是最安全、最常用的混用方式,核心逻辑为:异步方法接收请求后,在独立线程中调用事务方法执行核心业务(保证数据一致性),同时可并行处理其他无事务依赖的异步操作(如日志记录、凭证打印)。

2.1.1 完整代码实现

java 复制代码
// 账户服务接口
public interface AccountService {
    // 核心事务转账方法
    void transfer(Long depositorId, Long favoredId, BigDecimal amount);
}

// 账户服务实现类
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountRepository accountRepository;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void transfer(Long depositorId, Long favoredId, BigDecimal amount) {
        // 1. 校验账户合法性
        Account depositorAccount = accountRepository.findById(depositorId)
                .orElseThrow(() -> new IllegalArgumentException("转账账户不存在"));
        Account favoredAccount = accountRepository.findById(favoredId)
                .orElseThrow(() -> new IllegalArgumentException("收款账户不存在"));

        // 2. 校验余额充足性
        if (depositorAccount.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException("账户余额不足");
        }

        // 3. 执行转账逻辑
        depositorAccount.setBalance(depositorAccount.getBalance().subtract(amount));
        favoredAccount.setBalance(favoredAccount.getBalance().add(amount));

        // 4. 保存数据(事务管理下,两个保存操作原子性执行)
        accountRepository.save(depositorAccount);
        accountRepository.save(favoredAccount);
    }
}

// 异步任务服务类
@Service
@EnableAsync // 开启异步支持
public class AsyncTransferService {
    @Autowired
    private AccountService accountService;
    @Autowired
    private ReceiptService receiptService;
    @Autowired
    private OperationLogService logService;

    // 自定义线程池(生产环境推荐使用自定义线程池,避免默认线程池风险)
    @Async("transferThreadPool")
    public CompletableFuture<Boolean> transferAsync(Long depositorId, Long favoredId, BigDecimal amount) {
        try {
            // 调用事务方法执行核心转账逻辑
            accountService.transfer(depositorId, favoredId, amount);
            
            // 异步执行附加操作(与核心事务隔离,不影响事务结果)
            receiptService.printReceiptAsync(depositorId, favoredId, amount); // 异步打印凭证
            logService.recordLogAsync("TRANSFER", "转账成功:" + depositorId + "向" + favoredId + "转账" + amount); // 异步记录日志
            
            return CompletableFuture.completedFuture(true);
        } catch (Exception e) {
            // 异常处理:记录错误日志,返回失败结果
            logService.recordErrorLogAsync("TRANSFER", "转账失败:" + e.getMessage());
            return CompletableFuture.completedFuture(false);
        }
    }
}

// 自定义线程池配置
@Configuration
public class AsyncConfig {
    @Bean(name = "transferThreadPool")
    public Executor transferThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心线程数
        executor.setMaxPoolSize(10); // 最大线程数
        executor.setQueueCapacity(25); // 任务队列容量
        executor.setKeepAliveSeconds(60); // 空闲线程存活时间
        executor.setThreadNamePrefix("TransferAsync-"); // 线程名称前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
        executor.initialize();
        return executor;
    }
}

2.1.2 运行机制与优势

  1. 线程模型 :调用方通过transferAsync()发起请求后,立即返回CompletableFuture对象(非阻塞),核心逻辑在"TransferAsync-xxx"线程池中执行,不占用调用方线程资源。

  2. 事务传播transfer()方法被@Transactional标记,Spring会在异步线程中为其创建新的事务上下文(传播属性为REQUIRED,默认值),所有数据库操作纳入事务管理,确保原子性。

  3. 异常隔离 :若transfer()执行异常(如余额不足、账户不存在),事务会完整回滚,且仅影响核心转账逻辑;异步附加操作(打印凭证、记录日志)与核心事务隔离,即使附加操作失败,也不会导致转账事务回滚(符合"附加功能不影响核心业务"的设计原则)。

  4. 性能优势:核心转账逻辑执行完成后,打印凭证与记录日志可并行执行(线程池调度),相比同步执行大幅提升响应速度。

2.2 模式2:@Transactional方法作为入口,内部调用@Async方法(禁止)

该模式是最容易出现问题的混用方式,核心矛盾是:事务上下文存储在原线程的ThreadLocal中,@Async触发线程切换后,新线程无法获取原事务上下文,导致异步方法的操作脱离事务管理,破坏数据一致性。

2.2.1 错误代码实现

java 复制代码
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountRepository accountRepository;
    @Autowired
    private ReceiptService receiptService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(Long depositorId, Long favoredId, BigDecimal amount) {
        // 1. 转账核心逻辑(与模式1一致)
        Account depositorAccount = accountRepository.findById(depositorId)
                .orElseThrow(() -> new IllegalArgumentException("转账账户不存在"));
        Account favoredAccount = accountRepository.findById(favoredId)
                .orElseThrow(() -> new IllegalArgumentException("收款账户不存在"));
        if (depositorAccount.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException("账户余额不足");
        }
        depositorAccount.setBalance(depositorAccount.getBalance().subtract(amount));
        favoredAccount.setBalance(favoredAccount.getBalance().add(amount));

        // 2. 调用异步方法打印凭证(核心问题点)
        receiptService.printReceiptAsync(depositorId, favoredId, amount);

        // 3. 保存数据
        accountRepository.save(depositorAccount);
        accountRepository.save(favoredAccount);
    }
}

// 凭证服务类
@Service
@EnableAsync
public class ReceiptService {
    @Autowired
    private ReceiptRepository receiptRepository;

    @Async
    public void printReceiptAsync(Long depositorId, Long favoredId, BigDecimal amount) {
        // 生成转账凭证并保存到数据库
        Receipt receipt = new Receipt();
        receipt.setDepositorId(depositorId);
        receipt.setFavoredId(favoredId);
        receipt.setAmount(amount);
        receipt.setCreateTime(LocalDateTime.now());
        receipt.setStatus("PRINTED");
        receiptRepository.save(receipt);
    }
}

2.2.2 核心风险与底层原因

  1. 事务上下文丢失transfer()在原线程执行,事务上下文存储在原线程的ThreadLocal中;printReceiptAsync()在新线程执行,新线程的ThreadLocal中无任何事务上下文,因此其内部的receiptRepository.save(receipt)操作属于"无事务"执行(即自动提交模式)。

  2. 数据一致性破坏

    场景A:转账逻辑执行成功(账户余额已更新),但printReceiptAsync()执行失败(如数据库连接异常),导致"转账成功但无凭证记录",破坏业务完整性。

  3. 场景B:printReceiptAsync()执行成功(凭证已保存),但转账逻辑后续执行异常(如保存账户时数据库崩溃),导致"凭证已生成但转账事务回滚",出现数据矛盾。

  4. 调试难度提升:由于异步方法在独立线程执行,异常堆栈信息分散在不同线程日志中,排查问题时需要关联多线程日志,定位成本极高。

2.2.3 问题修复方案

若业务必须在转账完成后执行凭证打印(强依赖关系),应放弃异步调用,改为同步执行;若需异步执行,需通过"消息队列"实现解耦,具体方案如下:

  1. 转账事务执行成功后,向消息队列(如RabbitMQ、RocketMQ)发送"转账完成"消息。

  2. 独立的消费者服务监听消息队列,接收消息后异步执行凭证打印逻辑。

  3. 通过消息队列的重试机制(如死信队列)处理打印失败的场景,确保业务最终一致性。

2.3 模式3:同一方法同时标注@Transactional与@Async(谨慎使用)

该模式是指单个方法同时添加两个注解,核心逻辑为"异步执行+事务管理",其运行效果依赖Spring AOP的切面执行顺序,存在一定的不确定性,需谨慎使用。

2.3.1 代码实现

java 复制代码
@Service
@EnableAsync
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountRepository accountRepository;

    @Override
    @Async("transferThreadPool")
    @Transactional(rollbackFor = Exception.class)
    public void transferAsyncWithTransaction(Long depositorId, Long favoredId, BigDecimal amount) {
        // 转账核心逻辑(与模式1一致)
        Account depositorAccount = accountRepository.findById(depositorId)
                .orElseThrow(() -> new IllegalArgumentException("转账账户不存在"));
        Account favoredAccount = accountRepository.findById(favoredId)
                .orElseThrow(() -> new IllegalArgumentException("收款账户不存在"));
        if (depositorAccount.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException("账户余额不足");
        }
        depositorAccount.setBalance(depositorAccount.getBalance().subtract(amount));
        favoredAccount.setBalance(favoredAccount.getBalance().add(amount));
        accountRepository.save(depositorAccount);
        accountRepository.save(favoredAccount);
    }
}

2.3.2 运行机制与使用限制

  1. 切面执行顺序 :Spring AOP中,@Async切面的优先级高于@Transactional切面(可通过@Order注解调整),因此实际执行流程为:先触发异步切面,切换到独立线程,再在新线程中触发事务切面,创建事务上下文。

  2. 正常场景:若方法逻辑完全独立(无跨线程事务依赖、无外部资源调用),该模式可正常运行------方法异步执行,内部数据库操作被事务管理,异常时可正常回滚。

  3. 使用限制

    禁止调用本类其他事务方法:若该方法内部调用本类的其他@Transactional方法,由于是内部调用(未通过代理对象),事务注解会失效。

  4. 避免依赖外部线程状态:方法内部不可使用原调用线程的ThreadLocal数据(如用户登录信息、请求上下文),新线程无法获取。

  5. 异常处理需谨慎:异步方法的异常若未被捕获,会被线程池吞噬(默认情况下),需通过CompletableFuture返回结果或自定义AsyncUncaughtExceptionHandler处理异常。

2.4 模式4:类级别@Transactional与方法级@Async混用(严格限制)

该模式是指在类上添加@Transactional注解(所有公共方法默认纳入事务管理),同时在部分方法上添加@Async注解,其核心风险是"无差别事务管理"导致的资源浪费与异常传播问题。

2.4.1 代码实现与风险分析

java 复制代码
@Service
@Transactional(rollbackFor = Exception.class) // 类级别事务注解
@EnableAsync
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;
    @Autowired
    private LogRepository logRepository;

    // 异步事务方法
    @Async("transferThreadPool")
    public void transferAsync(Long depositorId, Long favoredId, BigDecimal amount) {
        // 转账核心逻辑(事务管理生效)
        // ... 与模式1一致 ...
    }

    // 同步事务方法
    public void transfer(Long depositorId, Long favoredId, BigDecimal amount) {
        // 转账核心逻辑(事务管理生效)
        // ... 与模式1一致 ...
    }

    // 仅需查询的方法(无事务必要,但被类级别注解强制纳入事务)
    public Account getAccountById(Long accountId) {
        return accountRepository.findById(accountId).orElse(null);
    }

    // 异步记录日志(无事务必要,但被类级别注解强制纳入事务)
    @Async("logThreadPool")
    public void recordLog(String operation, String content) {
        OperationLog log = new OperationLog();
        log.setOperation(operation);
        log.setContent(content);
        log.setCreateTime(LocalDateTime.now());
        logRepository.save(log);
    }
}

2.4.2 核心风险

  1. 资源浪费 :类级别@Transactional会为所有公共方法创建事务上下文,包括查询方法(getAccountById())、日志记录方法(recordLog())等无需事务的方法,导致数据库连接资源被占用,降低系统吞吐量。

  2. 事务传播异常 :异步方法(如recordLog())被强制纳入事务管理,但其逻辑无需原子性保障,反而可能因事务超时、锁竞争等问题导致执行失败。

  3. 维护成本高:后续新增方法时,若开发人员未注意类级别事务注解,容易误将无需事务的方法纳入事务管理,埋下性能隐患。

2.4.3 优化方案

  1. 移除类级别@Transactional注解,采用"方法级注解"精准控制事务范围。

  2. 将不同职责的方法拆分到不同服务类(如将日志记录方法拆分到LogService),避免职责混合导致的注解滥用。

  3. 对无需事务的方法,明确添加@Transactional(propagation = Propagation.NOT_SUPPORTED),声明不支持事务。

三、常见问题排查技巧与解决方案

@Transactional@Async混用场景中,常见问题包括"事务不生效""异步方法不执行""数据一致性破坏"等,以下是针对性的排查技巧与解决方案。

3.1 问题1:@Transactional注解失效

3.1.1 典型现象

方法执行异常时,数据库操作未回滚;或通过日志发现"未创建事务上下文"。

3.1.2 排查步骤与解决方案

  1. 检查是否通过代理对象调用

    原因:内部调用(如本类方法调用本类事务方法)未通过Spring代理对象,AOP切面无法拦截,事务注解失效。

  2. 解决方案:通过ApplicationContext获取代理对象,或拆分方法到不同服务类。

  3. 检查异步调用是否导致事务上下文丢失

    原因:事务方法内部调用异步方法,异步方法的操作脱离事务管理。

  4. 解决方案:调整调用顺序,采用"异步入口+内部事务"模式,或通过消息队列解耦。

  5. 检查事务注解参数是否正确

    原因:未指定rollbackFor = Exception.class,默认仅回滚运行时异常;或传播属性设置错误(如Propagation.NOT_SUPPORTED)。

  6. 解决方案:统一设置@Transactional(rollbackFor = Exception.class),根据业务需求选择正确的传播属性。

3.2 问题2:@Async注解不生效

3.2.1 典型现象

方法仍在原线程执行,未切换到异步线程池;或日志中无异步线程名称前缀。

3.2.2 排查步骤与解决方案

  1. 检查是否开启异步支持

    原因:未在配置类或启动类上添加@EnableAsync注解,Spring无法识别@Async注解。

  2. 解决方案:在Spring Boot启动类上添加@EnableAsync注解。

  3. 检查是否通过代理对象调用

    原因:内部调用(如本类方法调用本类异步方法)未通过代理对象,AOP切面无法拦截。

  4. 解决方案:拆分方法到不同服务类,或通过ApplicationContext获取代理对象调用。

  5. 检查线程池配置是否正确

    原因:自定义线程池未正确初始化,或@Asyncvalue属性指定的线程池名称不存在(默认使用SimpleAsyncTaskExecutor)。

  6. 解决方案:确保线程池Bean正确配置并初始化,@Asyncvalue属性与线程池Bean名称一致。

3.3 问题3:异步方法执行异常被吞噬

3.3.1 典型现象

异步方法执行失败,但无任何异常日志,调用方无法感知失败。

3.3.2 排查步骤与解决方案

  1. 检查异常是否被捕获

    原因:无返回值的异步方法(void类型)若未捕获异常,异常会被AsyncUncaughtExceptionHandler默认处理(仅打印WARN日志,易被忽略)。

  2. 解决方案:将异步方法返回值改为CompletableFuture,通过whenComplete()捕获异常;或自定义AsyncUncaughtExceptionHandler,统一处理无返回值异步方法的异常。

  3. 示例:自定义AsyncUncaughtExceptionHandler
    @Configuration @EnableAsync public class AsyncConfig { @Bean public AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler() { return (ex, method, params) -> { log.error("异步方法执行失败,方法名:{},参数:{},异常信息:{}", method.getName(), Arrays.toString(params), ex.getMessage(), ex); }; } }

四、进阶优化实践:高性能与高可靠的混用方案

在掌握基础混用模式与问题排查技巧后,结合企业级开发需求,以下是针对@Transactional@Async混用的进阶优化实践,兼顾性能、可靠性与可维护性。

4.1 自定义线程池优化异步执行

Spring默认的SimpleAsyncTaskExecutor无线程数限制,高并发场景下会导致线程泛滥,引发系统资源耗尽。生产环境必须自定义线程池,按业务场景拆分线程池(如转账线程池、日志线程池、凭证打印线程池),避免线程竞争。

4.1.1 线程池配置原则

  1. 核心线程数(corePoolSize) :根据CPU核心数与业务类型配置,CPU密集型任务(如计算)建议设置为CPU核心数 + 1,IO密集型任务(如数据库操作、文件读写)建议设置为2 * CPU核心数 + 1

  2. 最大线程数(maxPoolSize):避免设置过大(建议不超过100),防止线程切换开销过大。

  3. 任务队列容量(queueCapacity):根据业务峰值QPS配置,避免队列溢出(建议设置为1000~5000)。

  4. 拒绝策略(rejectedExecutionHandler) :生产环境推荐使用ThreadPoolExecutor.CallerRunsPolicy(当队列满时,由调用方线程执行任务,避免任务丢失),配合限流机制使用。

4.2 事务参数精细化配置

根据业务需求精准配置@Transactional的参数,避免资源浪费与事务异常:

  1. 传播属性(propagation)

    核心业务方法:使用Propagation.REQUIRED(默认值),确保创建新事务。

  2. 查询方法:使用Propagation.NOT_SUPPORTED,不支持事务,释放数据库连接资源。

  3. 嵌套业务方法:使用Propagation.NESTED,创建嵌套事务,仅回滚当前嵌套部分。

  4. 隔离级别(isolation)

    默认使用Isolation.DEFAULT(跟随数据库隔离级别)。

  5. 高并发读写场景:使用Isolation.READ_COMMITTED,避免脏读、不可重复读,兼顾一致性与性能。

  6. 超时时间(timeout):根据业务执行耗时配置(建议设置为3~5秒),避免长事务占用数据库连接。

4.3 日志与监控体系搭建

混用场景下,日志与监控是排查问题的关键,需重点关注以下内容:

  1. 日志打印

    事务方法:打印事务开启、提交、回滚日志,包含事务ID、线程ID。

  2. 异步方法:打印线程名称、任务提交时间、执行开始时间、执行结束时间。

  3. 监控指标

    线程池指标:核心线程数、活跃线程数、任务队列大小、拒绝任务数。

  4. 事务指标:事务提交数、事务回滚数、长事务数(超时事务)。

  5. 推荐工具:Spring Boot Actuator + Prometheus + Grafana,实现指标采集与可视化。

4.4 分布式场景下的扩展方案

在分布式系统中,@Transactional(本地事务)与@Async的混用需结合分布式事务方案(如Seata、Saga),确保跨服务的数据一致性:

  1. 采用"异步消息+分布式事务"模式:核心业务通过分布式事务保证一致性,附加操作通过异步消息队列执行。

  2. 使用CompletableFuture实现跨服务异步调用的结果聚合,配合分布式事务的回滚机制,确保最终一致性。

五、核心结论与实践规范

通过对@Transactional@Async注解底层实现、实战场景、问题排查及优化方案的深度分析,可总结出以下核心结论与实践规范:

  1. 推荐模式优先 :优先采用"@Async作为入口,内部调用@Transactional方法"的模式,兼顾性能与数据一致性。

  2. 严格禁止模式 :禁止在@Transactional方法内部调用@Async方法,避免事务上下文丢失导致的数据一致性问题。

  3. 谨慎使用模式:同一方法同时标注两个注解或类级别事务与方法级异步混用,仅在方法完全独立、无跨线程依赖的场景下使用,且需做好异常处理与日志监控。

  4. 核心设计原则

    事务上下文与线程强绑定,任何跨线程操作需避免依赖事务上下文。

  5. 核心业务与附加业务解耦,附加业务通过异步或消息队列实现,不影响核心事务。

  6. 生产环境必须自定义线程池,精细化配置事务参数,搭建完善的日志与监控体系。

掌握两者的混用技巧,不仅能解决日常开发中的高频问题,更能提升系统的性能与可靠性。在实际开发中,需结合业务场景灵活选择模式,同时遵循实践规范,规避潜在风险。

相关推荐
q_19132846951 小时前
基于Springboot+uniapp的智慧停车场收费小程序
java·vue.js·spring boot·小程序·uni-app·毕业设计·计算机毕业设计
JessonLv1 小时前
单商户商城说明文档-支持小程序及APP,JAVA+VUE开发
java·软件开发
鲸沉梦落1 小时前
网络原理-初识
java·网络
任子菲阳1 小时前
学Java第五十二天——IO流(下)
java·开发语言·intellij-idea
ArabySide1 小时前
【Java Web】过滤器的核心原理、实现与执行顺序配置
java·spring boot·java-ee
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 Java 返回最长有效子串长度
java·数据结构·后端·算法
我超级能吃的1 小时前
线程池核心原理及使用
java·开发语言
路边草随风1 小时前
java 实现 flink 读 kafka 写 delta
java·大数据·flink·kafka
逆风局?1 小时前
后端Web实战(部门管理)——日志技术
java·前端