【Spring 事务】【二】 Spring 事务的加载过程
1 前言
这节我们就来看看事务的加载过程,前置知识是大家可以先看看我之前写的SpringBoot的自动装配过程哈。因为事务在SpringBoot中,是通过自动装配作为入口点,然后转换为加载AOP的执行流程。
2 加载过程
SpringBoot启动会扫描spring.factories
文件,加载所有需要自动装配的类信息,TransactionAutoConfiguration 就在其中,该类就是事物自动装配的入口。
那我们就来看看这个类。
2.1 TransactionAutoConfiguration类
@Configuration // PlatformTransactionManager加载的情况下,才会有它 默认使用的jdbc,也可指定其他的。 @ConditionalOnClass(PlatformTransactionManager.class) // 在这些类后边加载 @AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class }) // 配置相关的 spring.transaction @EnableConfigurationProperties(TransactionProperties.class) public class TransactionAutoConfiguration { @Bean @ConditionalOnMissingBean public TransactionManagerCustomizers platformTransactionManagerCustomizers( ObjectProvider<PlatformTransactionManagerCustomizer<?>> customizers) { return new TransactionManagerCustomizers(customizers.orderedStream().collect(Collectors.toList())); } // 声明式事物的模板 @Configuration @ConditionalOnSingleCandidate(PlatformTransactionManager.class) public static class TransactionTemplateConfiguration { private final PlatformTransactionManager transactionManager; public TransactionTemplateConfiguration(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } @Bean @ConditionalOnMissingBean public TransactionTemplate transactionTemplate() { return new TransactionTemplate(this.transactionManager); } } // 重点在这儿,开启事物 @Configuration // 必须存在事物管理器才会加载 @ConditionalOnBean(PlatformTransactionManager.class) // 不存在AbstractTransactionManagementConfiguration,代表事物管理的配置类已经加载过了,无需再次加载 @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class) public static class EnableTransactionManagementConfiguration { // 两种模式,JDK动态代理或CGLIB动态代理,根据proxy-target-class来确定模式,在springboot2.0+默认从jdk改为了cglib,也就是默认都为true。 @Configuration @EnableTransactionManagement(proxyTargetClass = false) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) public static class JdkDynamicAutoProxyConfiguration { } @Configuration @EnableTransactionManagement(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) public static class CglibAutoProxyConfiguration { } } }
TransactionAutoConfiguration这个类主要看:
(1)2个类注解
- @ConditionalOnClass(PlatformTransactionManager.class)即类路径下包含PlatformTransactionManager这个类时这个自动配置生效,这个类是spring事务的核心包,肯定引入了。
- @AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class }),这个配置在括号中的4个配置类后才生效。
(2)2个内部类
- TransactionTemplateConfiguration事务模板配置类
- EnableTransactionManagementConfiguration 开启事务管理器配置类,其中
@EnableTransactionManagement
注解自动配置类加载的重点,通过@Import
进一步的加载。
事务管理器的接口是PlatformTransactionManager
,它的实现类有很多,比如结合JDBC操作的DataSourceTransactionManager
、配置JTA、Hibernate的事务管理器。其中定义了三个接口方法如下:
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException
:获取事务,如果当前没有事务,那么就根据传播级别创建一个事务。TransactionDefinition
:其中定义了事务的传播属性,比如默认的传播属性(当前没有事务就开启事务)等。
void commit(TransactionStatus status) throws TransactionException;
:提交事务TransactionStatus
:其中定义了一些事务的状态和查询、判断事务状态的方法
void rollback(TransactionStatus status) throws TransactionException;
:回滚事务
2.2 @EnableTransactionManagement
那我们就来看看 @EnableTransactionManagement:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
TransactionManagementConfigurationSelector
类是一个 ImportSelector,在解析@Import
的过程中会进入 selectImports ()
方法,该方法实际调用的是其父类的:
2.2.1 AdviceModeImportSelector & selectImports方法
//### AdviceModeImportSelector @Override public final String[] selectImports(AnnotationMetadata importingClassMetadata) { // 当前对象类型是 EnableTransactionManagement Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector"); // 获取注解的属性 proxyTargetClass、mode、order AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); if (attributes == null) { throw new IllegalArgumentException(String.format( "@%s is not present on importing class '%s' as expected", annType.getSimpleName(), importingClassMetadata.getClassName())); } /** * 获取mode属性,默认是 PROXY * AdviceMode mode() default AdviceMode.PROXY; */ AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName()); /** * 根据mode获取要加载的类 * PROXY 获取到的是 AutoProxyRegistrar.class和 ProxyTransactionManagementConfiguration.class * ASPECTJ 获取到的是 AspectJJtaTransactionManagementConfiguration 或者 AspectJTransactionManagementConfiguration * 我们这里看 PROXY 模式的哈 */ String[] imports = selectImports(adviceMode); if (imports == null) { throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode); } return imports; }
2.2.2 TransactionManagementConfigurationSelector & selectImports
// ###TransactionManagementConfigurationSelector @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } }
TransactionManagementConfigurationSelector 的主要流程就是根据@EnableTransactionManagement
中mode属性返回对应的BeanName,如果值是PROXY
【默认】,那么就会注入AutoProxyRegistrar
、ProxyTransactionManagementConfiguration
这两个Bean,那么这个方法的作用就是如此,因此我们需要看看注入的两个Bean到底是什么作用。
2.3 AutoProxyRegistrar类
该类实现了ImportBeanDefinitionRegistrar
,主要的作用就是根据@EnableTransactionManagement
属性中的mode
和proxyTargetClass
,注入对应的AutoProxyCreator
,我们来看下:
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; // 获取类上的所有注解信息 这里主要是分析@EnableTransactionManagement Set<String> annTypes = importingClassMetadata.getAnnotationTypes(); for (String annType : annTypes) { // 获取指定注解的全部属性的值 AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); if (candidate == null) { continue; } // 获取注解上的 mode 、 proxyTargetClass 值 Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); // 如果这些值都存在说明该配置类上标注了 @EnableTransactionManagement 这个注解 if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { candidateFound = true; // 根据 mode 的值,注入不同的AutoProxyCreator if (mode == AdviceMode.PROXY) { // 注册创建代理的类 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); if ((Boolean) proxyTargetClass) { // 强制使用子类代理【cglib代理】 实际就是设置了proxyTargetClass为true AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } if (!candidateFound && logger.isInfoEnabled()) { String name = getClass().getSimpleName(); logger.info(String.format("%s was imported but no annotations were found " + "having both 'mode' and 'proxyTargetClass' attributes of type " + "AdviceMode and boolean respectively. This means that auto proxy " + "creator registration and configuration may not have occurred as " + "intended, and components may not be proxied as expected. Check to " + "ensure that %s has been @Import'ed on the same class where these " + "annotations are declared; otherwise remove the import of %s " + "altogether.", name, name, name)); } }
2.3.1 AutoProxyRegistrar#registerAutoProxyCreatorIfNecessary
其中核心的代码就是注入不同的APC的代码,AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); 实际作用的源码如下:
private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); // 判断对应的APC是否已经注入了 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } // 没有注入的话,直接使用RootBeanDefinition注入,BeanName是org.springframework.aop.config.internalAutoProxyCreator RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }
2.3.2 AutoProxyRegistrar#forceAutoProxyCreatorToUseClassProxying
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
:强制使用子类代理【cglib代理】,实际的作用就是设置了proxyTargetClass为true,源码如下:
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE); } }
2.3.3 InfrastructureAdvisorAutoProxyCreator
该APC是在@EnableTransactionMangement注解作用下注入到ioc容器中的,代理创建器,继承了AbstractAutoProxyCreator
,这个和注解版AOP的代理创建器(AnnotationAwareAspectJAutoProxyCreator
)继承的是一个类,主要的作用就是创建代理对象,我们之前也是知道事务的底层其实就是使用AOP进行操作的,继承的关系图如下:
从上面的继承关系图可以很清晰的看到,这个类实现了不少的关于Bean生命周期的接口,因此我们只需要把实现的这些接口打上断点,即可清楚的分析出执行的流程了。这个和AOP讲解的类似,有些东西就不再一一讲述了,主要的功能就是为标注了@Transactional
创建代理对象。
小结:AutoProxyRegistrar 该类是TransactionManagementSlector选择器注入的类,主要作用就是根据@EnableTranactionMangement注解中的mode和proxyTarget的值注入AutoProxyCreator,实际的AutoProxyCreator的类型是 InfrastructureAdvisorAutoProxyCreator
2.4 ProxyTransactionManagementConfiguration类
该类是一个配置类,如下:
/** * 继承了 AbstractTransactionManagementConfiguration 抽象类 * public abstract class AbstractTransactionManagementConfiguration implements ImportAware */ @Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { // 内部封装了下面两个bean,后续只需要获取该advisor就可以了 @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource); advisor.setAdvice(transactionInterceptor); if (this.enableTx != null) { advisor.setOrder(this.enableTx.<Integer>getNumber("order")); } 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); if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; } }
可以看到AbstractTransactionManagementConfiguration
类实现了ImportAware
,那么其中的重载的方法一定是很重要的,代码如下:
@Override public void setImportMetadata(AnnotationMetadata importMetadata) { //获取@EnableTransactionMangement的属性值赋值给enableTx this.enableTx = AnnotationAttributes.fromMap( importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); //如果为null,抛出异常 if (this.enableTx == null) { throw new IllegalArgumentException( "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); } }
ImportAware这个类型的作用是获取标注在实现了该接口的配置类上的所有注解的元数据,包括注解的属性,值,类型等信息。同样实现了Aware接口,但是这个和BeanFactoryAware、ResourceLoaderAware等不同的是,这个接口必须是由配置类【即是标注了@Configuration注解】实现,并且需要结合@Import注解使用才能生效,否则不能生效,如下的使用方式将会生效。
那么对于AbstractTransactionManagementConfiguration配置类可以看到这个配置类主要做了两件事:
AbstractTransactionManagementConfiguration
中的一个重要实现,其实就是将@EnableTransactionMangement
的属性值赋值给enableTx- 从
ProxyTransactionManagementConfiguration
的源码可以知道,其实就是向容器中注入了三个Bean,分别是org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor
、org.springframework.transaction.interceptor.TransactionAttributeSource
、org.springframework.transaction.interceptor.TransactionInterceptor
那么看下来,TransactionManagementConfigurationSelector类的目的主要是向Spring容器注册了两个bean:
- AutoProxyRegistrar:往容器内注册InfrastructureAdvisorAutoProxyCreator,基于Advisor创建bean代理对象,让bean拥有事务增强的能力。
- ProxyTransactionManagementConfiguration:往容器内注册BeanFactoryTransactionAttributeSourceAdvisor以及依赖的bean,织入该Advisor的bean将拥有事务增强的能力。
我们画个图来理解一下:
到这里我们事务相关的加载就结束了,接下来我们就看代理的创建过程。
3 代理创建判断
我们在加载的过程中看到,会注册事务的通知器:
我们不会细说什么时候判断需不需要代理,代理的过程哈,这些我在AOP的分析里都说过了哈,我们就直接去看通知器里的切点,看看是要给哪些类进行增强判断的:
// ### BeanFactoryTransactionAttributeSourceAdvisor // 就是我们的事务通知器的切点 也就是用于判断要给哪些类进行事务代理的 private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { @Override @Nullable protected TransactionAttributeSource getTransactionAttributeSource() { return transactionAttributeSource; } }; @Override public Pointcut getPointcut() { return this.pointcut; }
3.1 TransactionAttributeSourcePointcut#matches 事务切点
@Override public boolean matches(Method method, Class<?> targetClass) { // 获取属性的类,通过否存在事物属性判断当前加载的类是否需要创建代理 TransactionAttributeSource tas = getTransactionAttributeSource(); return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); }
对于 TransactionAttributeSource 把它理解成一个事务注解解析器,用于解析类或者方法上的注解,并解析事务相关的属性出来的,getTransactionAttributeSource()就是获取的我们通知器上的 transactionAttributeSource,类型是:AnnotationTransactionAttributeSource,我画个图理一下:
3.2 AnnotationTransactionAttributeSource # getTransactionAttribute
AnnotationTransactionAttributeSource又是继承的AbstractFallbackTransactionAttributeSource,getTransactionAttribute 实际调用的是 AbstractFallbackTransactionAttributeSource父类的方法,我们来看看:
// ###AbstractFallbackTransactionAttributeSource public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) { if (method.getDeclaringClass() == Object.class) { return null; } // First, see if we have a cached value. Object cacheKey = getCacheKey(method, targetClass); TransactionAttribute cached = this.attributeCache.get(cacheKey); if (cached != null) { // Value will either be canonical value indicating there is no transaction attribute, // or an actual transaction attribute. if (cached == NULL_TRANSACTION_ATTRIBUTE) { return null; } else { return cached; } } else { // We need to work it out. // 根据 method、targetClass 推算事务属性,TransactionAttribute TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); // 将取出的值存入缓存中,下次再取就不需要解析了,直接取值即可 Put it in the cache. if (txAttr == null) { this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); } else { // 获取方法的名称,全类名+方法名的形式 String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); if (txAttr instanceof DefaultTransactionAttribute) { DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr; dta.setDescriptor(methodIdentification); dta.resolveAttributeStrings(this.embeddedValueResolver); } if (logger.isTraceEnabled()) { logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); } this.attributeCache.put(cacheKey, txAttr); } return txAttr; } }
核心的方法就是:computeTransactionAttribute,分析当前方法或者类的注解信息:
@Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // Don't allow non-public methods, as configured. 方法必须是public的 if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // 如果method方法是在接口中定义的方法,那么获取接口实现类的方法。 // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // 方式1: 从目标类的方法上找 Transaction注解 First try is the method in the target class. TransactionAttribute txAttr = findTransactionAttribute(specificMethod); if (txAttr != null) { return txAttr; } // 方式2: 从目标类上找 Transaction注解 Second try is the transaction attribute on the target class. txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } if (specificMethod != method) { // 方式3:接口的方法上找 Transaction注解 Fallback is to look at the original method. txAttr = findTransactionAttribute(method); if (txAttr != null) { return txAttr; } // 方式4:接口的类上找 Transaction注解 Last fallback is the class of the original method. txAttr = findTransactionAttribute(method.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } } return null; }
而 findTransactionAttribute 实际调用的就是 AnnotationTransactionAttributeSource类了,我们看下构造器:
可以看到创建的时候,会放入内置的解析器,我们继续看 findTransactionAttribute :
@Override @Nullable protected TransactionAttribute findTransactionAttribute(Class<?> clazz) { return determineTransactionAttribute(clazz); } @Override @Nullable protected TransactionAttribute findTransactionAttribute(Method method) { return determineTransactionAttribute(method); } protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) { // 遍历每个解析器进行解析 for (TransactionAnnotationParser parser : this.annotationParsers) { TransactionAttribute attr = parser.parseTransactionAnnotation(element); if (attr != null) { return attr; } } return null; }
可以看到最后都是调用的每个解析器的 parser.parseTransactionAnnotation,那我们跟进去看下:
// ### SpringTransactionAnnotationParser public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { // 获取 Transactional 注解 AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( element, Transactional.class, false, false); // 如果属性值不为空 if (attributes != null) { // 从 @Transactional 注解上获取事务属性值,并包装成 TransactionAttribute 返回 return parseTransactionAnnotation(attributes); } else { return null; } } public TransactionAttribute parseTransactionAnnotation(Transactional ann) { return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false)); } // 解析注解的属性值,将其封装在TransactionAttribute中 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()); String timeoutString = attributes.getString("timeoutString"); Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0, "Specify 'timeout' or 'timeoutString', not both"); rbta.setTimeoutString(timeoutString); rbta.setReadOnly(attributes.getBoolean("readOnly")); rbta.setQualifier(attributes.getString("value")); rbta.setLabels(Arrays.asList(attributes.getStringArray("label"))); // 封装回滚规则 List<RollbackRuleAttribute> rollbackRules = new ArrayList<>(); for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); } for (String rbRule : attributes.getStringArray("rollbackForClassName")) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); } for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); } for (String rbRule : attributes.getStringArray("noRollbackForClassName")) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); } rbta.setRollbackRules(rollbackRules); return rbta; }
好了,到这里我们的事务筛选给哪些类或者方法创建代理到这里就结束了,方法或者类上的@Transcational注解进行判断是否要创建代理,方法的话必须是public的哈。
4 小结
本节我们主要看了事务的加载过程以及通知器里切点的判断是否需要事务代理等,下节我们看具体的增强过程哈,如何控制事务传播以及隔离级别的哈,有理解不对的地方欢迎指正哈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了