手撕@Transactional!别再问事务为什么失效了!Spring-tx源码全面解析!

📝 本文基于 Spring Framework 6.1.4 版本源码分析,

为便于理解,部分源码做了适度简化与伪代码化处理,如需查看完整实现,请参考:

前言

最近写了好几篇关于Spring事务控制的文章,总觉意犹未尽。

于是决定好好来扒一扒@Transactional的底裤,看看它到底是怎么实现的。

这次来个大招,从源码的角度把整个流程梳理一遍。

从源头开始,到代理对象的创建,再到事务的开启、提交、回滚,每一个环节都不放过。

整篇文章较长,属实费了很多个晚上,熬掉了多少头发,就为了把这个看似简单实则巨复杂的机制盘透彻。

如果你也和我一样好奇,那就一起来看看Spring是如何用一个小小的注解,撑起整个事务管理的吧。

当然,在源码的理解上难免有偏差,如果文章中有错误的地方,还请各位大佬不吝赐教,多多指正。

正文

一切都是代理

很多人觉得@Transactional很神奇,加个注解就能管理事务。其实,这背后的原理并不复杂,核心就是AOP(面向切面编程)。

实际上,Spring 是通过动态代理(JDK 或 CGLIB)来实现事务增强的。你以为调用的是你写的 createUser 方法,其实背后 Spring 替你调用的是代理类的方法。

为了更直观地理解事务的执行流程,我们可以用一段"简化后的伪代码"来展示核心逻辑(该伪代码并不是真实存在的代理类,而是模仿其结构来解释 Spring 内部是如何控制事务的):

代理模式

让我们先来看看最原始的样子:

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 其他业务逻辑
    }
}

当Spring容器启动时,它会发现UserService里有标注了@Transactional的方法,于是会创建一个代理对象。这个代理对象大概长这样:

java 复制代码
// 这是Spring内部生成的代理类(伪代码)
public class UserService$$EnhancerBySpringCGLIB extends UserService {
    
    private TransactionInterceptor transactionInterceptor;
    
    @Override
    public void createUser(User user) {
        // 开始事务
        TransactionInfo txInfo = transactionInterceptor.createTransactionIfNecessary(...);
        try {
            // 调用原始方法
            super.createUser(user);
            // 提交事务
            transactionInterceptor.commitTransactionAfterReturning(txInfo);
        } catch (Exception e) {
            // 回滚事务
            transactionInterceptor.completeTransactionAfterThrowing(txInfo, e);
            throw e;
        }
    }
}

看到了吗?你以为你调用的是原始的createUser方法,实际上调用的是代理类的方法。这就是为什么@Transactional能够工作的根本原因。

两种代理方式:JDK vs CGLIB

Spring支持两种代理方式,选择哪种取决于你的类是否实现了接口。

JDK动态代理:如果你的类实现了接口,Spring会使用JDK动态代理。

java 复制代码
public interface UserService {
    void createUser(User user);
}

@Service
public class UserServiceImpl implements UserService {
    
    @Transactional
    public void createUser(User user) {
        // 业务逻辑
    }
}

CGLIB代理:如果你的类没有实现接口,Spring会使用CGLIB字节码生成技术。

java 复制代码
@Service
public class UserService {  // 没有实现接口
    
    @Transactional
    public void createUser(User user) {
        // 业务逻辑
    }
}

这两种方式各有优劣。JDK动态代理性能更好,但要求必须有接口。CGLIB更灵活,但会有一些限制(比如不能代理final方法)。

有机会的话,后面单独写一篇来讲讲这两种代理。

事务拦截器:划重点

在 Spring 中,TransactionInterceptor 是事务管理的门面类,它实现了 MethodInterceptor,用于拦截方法调用。

然而它本身并不包含具体的事务执行逻辑,而是将事务的真正处理交给了它的父类:TransactionAspectSupport。我们可以从它的 invoke(...) 方法看出这一点。

这里为了避免代码篇幅过长,对部分进行删减,尽可能在有限的篇幅中讲清楚原理。

虽然源码有变化,但是对于理解背后的实现并不会造成障碍,甚至会更轻松。

如果你发现这里的源码和实际的源码有差异,莫要惊慌~

java 复制代码
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取目标类
        Class<?> targetClass = (invocation.getThis() != null ? 
            AopUtils.getTargetClass(invocation.getThis()) : null);
        
        // 委托给父类的核心方法处理事务逻辑
        return invokeWithinTransaction(invocation.getMethod(), targetClass, 
            new CoroutinesInvocationCallback() {
                @Override
                public Object proceedWithInvocation() throws Throwable {
                    return invocation.proceed();
                }
            });
    }
}

核心的事务处理逻辑实际上在父类 TransactionAspectSupportinvokeWithinTransaction 方法中:

java 复制代码
// 位于 TransactionAspectSupport 类中
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, 
        final InvocationCallback invocation) throws Throwable {
    
    // 获取事务属性
    TransactionAttributeSource tas = getTransactionAttributeSource();
    final TransactionAttribute txAttr = (tas != null ? 
        tas.getTransactionAttribute(method, targetClass) : null);
    
    // 获取事务管理器
    final TransactionManager tm = determineTransactionManager(txAttr);
    
    // 创建事务信息
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
    Object retVal;
    try {
        // 执行目标方法
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable ex) {
        // 异常处理和回滚
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
        // 清理事务信息
        cleanupTransactionInfo(txInfo);
    }
    
    // 提交事务
    commitTransactionAfterReturning(txInfo);
    return retVal;
}

这段代码里有几个关键点:

  1. 获取事务属性 :解析@Transactional注解的各种配置
  2. 创建事务:根据传播行为决定是否开启新事务
  3. 执行业务方法:通过回调机制调用你写的业务逻辑
  4. 处理结果:根据执行结果决定提交还是回滚

事务提交的内部机制

让我们深入看看commitTransactionAfterReturning方法是如何工作的:

java 复制代码
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        // 获取事务管理器并提交
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

这里调用的是PlatformTransactionManager的commit方法。对于不同的数据源,会有不同的实现。以DataSourceTransactionManager为例:

java 复制代码
@Override
protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    
    try {
        // 核心:调用JDBC的commit方法
        con.commit();
    } catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit JDBC transaction", ex);
    }
}

看到没?最终还是调用了JDBC Connection的commit()方法,Spring只是在这个基础上做了封装和抽象。

异常回滚的深层逻辑

completeTransactionAfterThrowing方法更有意思,它需要判断什么情况下回滚:

java 复制代码
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        // 关键判断:是否需要回滚
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 执行回滚
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            } catch (TransactionSystemException ex2) {
                ex2.initApplicationException(ex);
                throw ex2;
            }
        } else {
            // 即使有异常,但不满足回滚条件,仍然提交
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            } catch (TransactionSystemException ex2) {
                ex2.initApplicationException(ex);
                throw ex2;
            }
        }
    }
}

这里有个很重要的细节:rollbackOn(ex)方法的判断逻辑。默认实现(在DefaultTransactionAttribute)是这样的:

java 复制代码
@Override
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

这就解释了为什么只有RuntimeExceptionError会触发回滚。如果是检查型异常,Spring认为这是业务层面的正常流程,不应该回滚事务。

事务状态的生命周期管理

每个事务都有自己的状态信息,Spring通过TransactionStatus来跟踪:

java 复制代码
public class DefaultTransactionStatus implements TransactionStatus {
    
    private final Object transaction;           // 具体的事务对象(如JDBC Connection)
    private final boolean newTransaction;       // 是否是新事务(影响提交回滚策略)
    private boolean rollbackOnly = false;      // 是否只能回滚(一旦标记无法提交)
    private boolean completed = false;         // 是否已完成(防止重复操作)
    private Object savepoint;                  // 保存点(用于嵌套事务的部分回滚)
    
    // 其他字段和方法...
}

这个状态对象就是事务的身份证,记录了事务的关键信息。

newTransaction字段要格外注意,它决定了当前方法是否能够提交或回滚事务。如果是false,那说明当前方法参与的是外层事务,不能擅自处理当前事务。

TransactionAspectSupport内部调用createTransactionIfNecessary时,Spring会创建这个状态对象:

java 复制代码
protected TransactionInfo createTransactionIfNecessary(
        @Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

    // 获取事务状态
    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            // 这里是关键:根据传播行为创建或加入事务
            // getTransaction方法会根据@Transactional的propagation属性
            // 决定是开启新事务还是加入现有事务
            status = tm.getTransaction(txAttr);
        }
    }
    
    // 准备事务信息并绑定到ThreadLocal
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

这里的getTransaction方法看起来简单,但内部逻辑相当复杂。

它会检查当前线程是否已经有事务,然后根据传播行为(如REQUIREDREQUIRES_NEW等)决定具体的处理策略。

这里不作展开,后面有机会再来分解。

ThreadLocal:事务信息的载体

Spring使用ThreadLocal来存储当前线程的事务信息,这样确保了事务的线程安全性:

java 复制代码
// 静态ThreadLocal,每个线程都有自己的事务信息副本
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
        new NamedThreadLocal<>("Current aspect-driven transaction");

protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, String joinpointIdentification,
        @Nullable TransactionStatus status) {

    // 创建新的事务信息对象
    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        // 关联事务状态
        txInfo.newTransactionStatus(status);
    }

    // 绑定到当前线程
    txInfo.bindToThread();
    return txInfo;
}

private void bindToThread() {
    // 保存旧的事务信息(用于事务嵌套)
    // 这里实现了一个栈结构:新事务信息压栈,旧的暂存
    this.oldTransactionInfo = transactionInfoHolder.get();
    // 设置当前事务信息为线程的活跃事务
    transactionInfoHolder.set(this);
}

这种设计很巧妙,它支持事务的嵌套。当一个事务方法调用另一个事务方法时,新的事务信息会压栈,执行完毕后再出栈恢复之前的事务状态。

就像俄罗斯套娃,外层事务包含内层事务,每层都有自己的状态管理。通过oldTransactionInfo字段形成的链式结构,Spring可以在方法执行完毕后准确地恢复到上一层的事务状态,确保事务边界的正确性。

@Transactional的生命周期

让我们追踪一下@Transactional从配置到生效的完整过程。

1. 注解解析

您说得很对!让我补充一下 computeTransactionAttribute 方法的实现,这是整个事务属性解析的核心逻辑:

1. 注解解析:寻找事务的蛛丝马迹(补充完整版)

Spring 使用 AbstractFallbackTransactionAttributeSource 实现了事务属性的缓存和回退策略,之前提到的AnnotationTransactionAttributeSource 就是继承自它:

java 复制代码
// 父类的核心逻辑
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
    
    @Override
    public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
        // 1. 首先检查缓存
        Object cacheKey = getCacheKey(method, targetClass);
        TransactionAttribute cached = this.attributeCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // 2. 计算事务属性,遵循严格的回退策略
        TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
        
        // 3. 缓存结果(包括null值,避免重复计算)
        this.attributeCache.put(cacheKey, txAttr);
        return txAttr;
    }
    
    // 这里是事务属性解析的核心算法
    protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
        // 只处理public方法(Spring AOP的限制)
        if (!Modifier.isPublic(method.getModifiers())) {
            return null;
        }
        
        // 获取最具体的方法(可能是目标类中重写的方法)
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
        
        // 第一优先级:检查目标类中的具体方法
        TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
        if (txAttr != null) {
            return txAttr;
        }
        
        // 第二优先级:检查目标类上的类级别注解
        txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return txAttr;
        }
        
        // 如果目标类中没有找到,回退到原始方法
        if (specificMethod != method) {
            // 第三优先级:检查原始方法(通常是接口中的方法)
            txAttr = findTransactionAttribute(method);
            if (txAttr != null) {
                return txAttr;
            }
            
            // 第四优先级:检查原始方法所在类的注解(通常是接口上的注解)
            txAttr = findTransactionAttribute(method.getDeclaringClass());
            if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
                return txAttr;
            }
        }
        
        return null;
    }
}

可以看下这个回退策略的设计,它遵循了"最具体优先"的原则:

优先级顺序(从高到低)

  1. 实现类的方法@Transactional 直接标注在具体方法上
  2. 实现类的类@Transactional 标注在实现类上
  3. 接口的方法@Transactional 标注在接口方法上
  4. 接口的类@Transactional 标注在接口上
java 复制代码
// 子类 AnnotationTransactionAttributeSource 的实现
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource {
    
    @Override
    protected TransactionAttribute findTransactionAttribute(Method method) {
        // 遍历所有注册的事务注解解析器
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(method);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }
    
    @Override
    protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
        // 遍历所有注册的事务注解解析器
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(clazz);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }
}

实际的注解解析SpringTransactionAnnotationParser 完成:

java 复制代码
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser {
    
    @Override
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        // 查找@Transactional注解
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        if (attributes != null) {
            // 将注解属性转换为TransactionAttribute对象
            return parseTransactionAnnotation(attributes);
        }
        return null;
    }
    
    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
        
        // 解析传播行为
        Propagation propagation = attributes.getEnum("propagation");
        rbta.setPropagationBehavior(propagation.value());
        
        // 解析隔离级别
        Isolation isolation = attributes.getEnum("isolation");
        rbta.setIsolationLevel(isolation.value());
        
        // 解析超时时间
        rbta.setTimeout(attributes.getNumber("timeout").intValue());
        
        // 解析只读标志
        rbta.setReadOnly(attributes.getBoolean("readOnly"));
        
        // 解析事务管理器限定符
        rbta.setQualifier(attributes.getString("value"));
        
        // 解析回滚规则
        List<RollbackRule> rollbackRules = new ArrayList<>();
        for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
            rollbackRules.add(new RollbackRule(rbRule));
        }
        for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
            rollbackRules.add(new RollbackRule(rbRule));
        }
        for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
            rollbackRules.add(new NoRollbackRule(rbRule));
        }
        for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
            rollbackRules.add(new NoRollbackRule(rbRule));
        }
        rbta.setRollbackRules(rollbackRules);
        
        return rbta;
    }
}

整套设计的几个要点如下:

  1. 完整的回退策略:确保任何有效的事务配置都不会被遗漏
  2. 高效的缓存机制:避免重复解析相同的方法
  3. 灵活的解析器架构:支持不同的事务注解(Spring、JTA、EJB等)
  4. 最具体优先原则:方法级别的配置总是会覆盖类级别的配置

通过这套机制,Spring 可以准确地为每个方法确定其事务属性,为后续的事务管理提供了坚实的基础。

2. 从@EnableTransactionManagement到代理创建

@EnableTransactionManagement 负责注册必要的Spring组件来支持注解驱动的事务管理:

java 复制代码
@EnableTransactionManagement
public class AppConfig {
    // 应用配置
}

// @EnableTransactionManagement 内部机制
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    // 配置选项
}

// 选择器会根据mode()选择不同的配置类
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                // 默认情况下,导入代理模式的配置
                return new String[] {
                    AutoProxyRegistrar.class.getName(),
                    ProxyTransactionManagementConfiguration.class.getName()
                };
            case ASPECTJ:
                // AspectJ模式的配置
                return new String[] {
                    TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME
                };
            default:
                return null;
        }
    }
}

ProxyTransactionManagementConfiguration 是注册Spring基础设施Bean的配置类:

java 复制代码
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
    // 注册事务Advisor(切面)
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
            TransactionAttributeSource transactionAttributeSource,
            TransactionInterceptor transactionInterceptor) {
        
        BeanFactoryTransactionAttributeSourceAdvisor advisor = 
            new BeanFactoryTransactionAttributeSourceAdvisor();
        // 设置事务属性源(解析@Transactional注解)
        advisor.setTransactionAttributeSource(transactionAttributeSource);
        // 设置事务拦截器(实际的事务处理逻辑)
        advisor.setAdvice(transactionInterceptor);
        return advisor;
    }
    
    // 注册事务属性源
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }
    
    // 注册事务拦截器
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor(
            TransactionAttributeSource transactionAttributeSource) {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource);
        return interceptor;
    }
}

关键在于 AutoProxyRegistrar 会注册 InfrastructureAdvisorAutoProxyCreator,这个类负责:

  1. 扫描所有Bean:寻找带有@Transactional注解的类和方法
  2. 创建代理:Spring构建动态CGLib代理,可以为你打开和关闭数据库事务
  3. 织入Advisor :将 BeanFactoryTransactionAttributeSourceAdvisor 织入到代理中

3. 事务执行

当代理方法被调用时,TransactionAspectSupport会创建事务信息:

java 复制代码
// TransactionAspectSupport类中的核心方法
protected TransactionInfo createTransactionIfNecessary(
        @Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr,
        final String joinpointIdentification) {
    
    // 如果没有事务属性,直接返回空的TransactionInfo
    if (txAttr == null) {
        return new TransactionInfo(tm, txAttr, joinpointIdentification);
    }
    
    // 如果没有指定名称,使用方法签名作为事务名称
    if (txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName() {
                return joinpointIdentification;
            }
        };
    }
    
    TransactionStatus status = null;
    if (tm != null) {
        // 这里是关键:根据传播行为创建或加入事务
        // getTransaction方法会根据@Transactional的propagation属性
        // 决定是开启新事务还是加入现有事务
        status = tm.getTransaction(txAttr);
    }
    
    // 准备事务信息并绑定到ThreadLocal
    // 这个TransactionInfo对象包含了本次事务的所有上下文信息
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

完整的调用链路

  1. @EnableTransactionManagement ➡️ TransactionManagementConfigurationSelector
  2. ProxyTransactionManagementConfiguration ➡️ 注册关键Bean
  3. AutoProxyRegistrar ➡️ InfrastructureAdvisorAutoProxyCreator ➡️ 创建代理
  4. 方法调用 ➡️ TransactionInterceptor ➡️ TransactionAspectSupport
  5. 创建事务 ➡️ 执行业务逻辑 ➡️ 提交/回滚事务

整个生命周期体现了Spring框架的设计哲学:通过声明式配置简化复杂的事务管理,让开发者专注于业务逻辑而不是底层的事务控制细节。

附录:源码参考文件列表

总结

写到这里,终于把@Transactional从头到尾扒了个遍。从一个简单的注解开始,到代理对象的创建,再到事务的开启、提交、回滚,整个过程涉及了Spring框架的多个核心机制。

不得不说,Spring的设计着实精妙,叹为观止。

当然,这篇文章主要是从源码角度来分析,可能有些地方讲得不够深入,有些地方理解得也不够准确。

毕竟Spring的源码博大精深,我这点水平也只是管中窥豹。如果文章中有什么错误或者遗漏的地方,欢迎大家在评论区指正,我会及时修正。

最后,如果这篇文章对你有帮助,不妨点个赞支持一下。如果你对Spring的其他机制也感兴趣,可以关注我,后续会继续分享更多的源码分析文章。

学无止境,我们一起进步!

相关推荐
默默coding的程序猿15 分钟前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
到账一个亿40 分钟前
后端树形结构
后端
武子康43 分钟前
大数据-31 ZooKeeper 内部原理 Leader选举 ZAB协议
大数据·后端·zookeeper
我是哪吒44 分钟前
分布式微服务系统架构第155集:JavaPlus技术文档平台日更-Java线程池实现原理
后端·面试·github
代码老y1 小时前
Spring Boot + 本地部署大模型实现:安全性与可靠性保障
spring boot·后端·bootstrap
LaoZhangAI1 小时前
OpenAI API 账号分层完全指南:2025年最新Tier系统、速率限制与升级攻略
前端·后端
红衣信1 小时前
前端与后端存储全解析:从 Cookie 到缓存策略
前端·后端·面试
Kyrie_Li1 小时前
(十五)Spring Test
java·后端·spring
WildBlue1 小时前
🎉 手写call的魔法冒险:前端开发者的“换身份”指南🚀
前端·后端
fortmin1 小时前
使用Apache Pdfbox生成pdf
后端