spring 事务源码阅读

news2024/12/26 2:18:28

开启事务

使用@EnableTransactionManagement注解开启事务

该注解会引入TransactionManagementConfigurationSelector类,然后该类导入两个类AutoProxyRegistrar和ProxyTransactionManagementConfiguration。

1、添加bean后置处理器

AutoProxyRegistrar类的作用是注册InfrastructureAdvisorAutoProxyCreator类,InfrastructureAdvisorAutoProxyCreator是Spring中实现AOP代理的关键组件,它会扫描所有的Advisor(通知器),并将其与BeanFactory中的Bean进行匹配,以决定是否需要为该Bean创建AOP代理。

InfrastructureAdvisorAutoProxyCreator 继承AbstractAdvisorAutoProxyCreator 继承AbstractAutoProxyCreator,实现SmartInstantiationAwareBeanPostProcessor和BeanFactoryAware接口。

是一个bean后置处理器

public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
   @Nullable
   private ConfigurableListableBeanFactory beanFactory;
   @Override
   protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      super.initBeanFactory(beanFactory);
      this.beanFactory = beanFactory;
   }
   @Override
   protected boolean isEligibleAdvisorBean(String beanName) {
      return (this.beanFactory != null && this.beanFactory.containsBeanDefinition(beanName) &&
            this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);
   }

}

代理的创建在上一篇AOP源码阅读中有说过就是在后置处理器的after方法寻找合适的advisor进行创建代理。上面的源码看到InfrastructureAdvisorAutoProxyCreator只是重写了initBeanFactory方法用来获取beanfacotry和isEligibleAdvisorBean方法用来判断是否是合适的bean。

2、引入Advisor

TransactionManagementConfigurationSelector引入的另一个类是ProxyTransactionManagementConfiguration,该类代码也不多,如下

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

   @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;
   }

}

ProxyTransactionManagementConfiguration类主要引入BeanFactoryTransactionAttributeSourceAdvisor通知类。并为其TransactionAttributeSource和TransactionInterceptor两个属性赋值。该类实现了Advisor接口,是一个PointcutAdvisor类型的Advisor,用来匹配@Transaction注解。具体如何匹配下面结合源码说。

代理创建

1、获取所有Advisor

在AbstractAutoProxyCreator的后置出来器afater方法里还是会调用wrapIfNecessary(bean, beanName, cacheKey)来判断当前bean是否需要包装代理。然后会调用getAdvicesAndAdvisorsForBean(bean.getClass(), beanName)方法尝试获取适用于该bean的advice。这个方法在AbstractAdvisorAutoProxyCreator 中实现

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

protected Object[] getAdvicesAndAdvisorsForBean(
      Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
   //获取符合条件的advisor
   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //获取候选的所有advisor
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
      //获取适用于当前bean的advisor
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

findCandidateAdvisors()方法还是在该类中实现,使用了一个BeanFactoryAdvisorRetrievalHelper工具类进行获取Advisor

AbstractAdvisorAutoProxyCreator#findCandidateAdvisors

protected List<Advisor> findCandidateAdvisors() {
     return this.advisorRetrievalHelper.findAdvisorBeans();
}

BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans

public List<Advisor> findAdvisorBeans() {
   String[] advisorNames = this.cachedAdvisorBeanNames;
   if (advisorNames == null) {
      //从beanfacotry总获取所有的Advisor
      advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this.beanFactory, Advisor.class, true, false);
      this.cachedAdvisorBeanNames = advisorNames;
   }
   if (advisorNames.length == 0) {
      return new ArrayList<>();
   }

   List<Advisor> advisors = new ArrayList<>();
   for (String name : advisorNames) {
      if (isEligibleBean(name)) {//调用isEligibleBean判断bean是否满足基本条件
         advisors.add(this.beanFactory.getBean(name, Advisor.class));
      }
   }
   return advisors;
}

这里看到Advisor的获取是直接从beanFacotry中查找Advisor.class类型的beanDef,这样就能找到我们最开始通过import导入的BeanFactoryTransactionAttributeSourceAdvisor。这和上一篇说的AOP的Aspect方式有一些区别(扫描所有的bean,反射解析@Aspect注解)。

2、寻找匹配的Advisor

找到所有的Advisor后下一步就是逐个判断是否适用于当前bean

protected List<Advisor> findAdvisorsThatCanApply(
      List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

   ProxyCreationContext.setCurrentProxiedBeanName(beanName);
   try {
      return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
   }
   finally {
      ProxyCreationContext.setCurrentProxiedBeanName(null);
   }
}

AopUtils#findAdvisorsThatCanApply

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
   if (candidateAdvisors.isEmpty()) {
      return candidateAdvisors;
   }
   List<Advisor> eligibleAdvisors = new ArrayList<>();
   //这里不是IntroductionAdvisor类型的不会进入循环
   for (Advisor candidate : candidateAdvisors) {
      if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
         eligibleAdvisors.add(candidate);
      }
   }
   boolean hasIntroductions = !eligibleAdvisors.isEmpty();
   for (Advisor candidate : candidateAdvisors) {
      if (candidate instanceof IntroductionAdvisor) {
         // already processed
         continue;
      }
      //进入canApply方法判断
      if (canApply(candidate, clazz, hasIntroductions)) {
         eligibleAdvisors.add(candidate);
      }
   }
   return eligibleAdvisors;
}

AopUtils#canApply

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
   if (advisor instanceof IntroductionAdvisor) {
      return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
   }
   else if (advisor instanceof PointcutAdvisor) {//走这里
      PointcutAdvisor pca = (PointcutAdvisor) advisor;
      return canApply(pca.getPointcut(), targetClass, hasIntroductions);
   }
   else {
      // It doesn't have a pointcut so we assume it applies.
      return true;
   }
}

最开始说过我们引入的advisor是一个PointcutAdvisor类型。所以会进入第二个if分支。拿出pointcut进行匹配。这里的advisor是BeanFactoryTransactionAttributeSourceAdvisor实例,器pointcut是TransactionAttributeSourcePointcut类型实例。下一个重载canApply方法主要匹配逻辑是下面代码

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
   //...
   //这里返回的matcher还是TransactionAttributeSourcePointcut本身
   MethodMatcher methodMatcher = pc.getMethodMatcher();
   for (Class<?> clazz : classes) {
     //拿出class所有方法与matcher进行匹配
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
      for (Method method : methods) {
         if (introductionAwareMethodMatcher != null ?
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
               methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}

主要的匹配逻辑是methodMatcher.matches,也就是

TransactionAttributeSourcePointcut#matches

public boolean matches(Method method, Class<?> targetClass) {
   //获取TransactionAttributeSource,这是最开始在引入Advisor设置的
   TransactionAttributeSource tas = getTransactionAttributeSource();
   return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

这里TransactionAttributeSource是最开始引入由ProxyTransactionManagementConfiguration类内部设置的,其实例类型是AnnotationTransactionAttributeSource ,该类继承 AbstractFallbackTransactionAttributeSource。getTransactionAttribute()方法在其父类中

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   if (method.getDeclaringClass() == Object.class) {//Object类方法跳过
      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 {
      // 解析当前方法事务属性
      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);
         }

         this.attributeCache.put(cacheKey, txAttr);
      }
      return txAttr;
   }
}

解析实际方法是computeTransactionAttribute()

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   //step1 判断方法必须是public方法
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }

   // 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);

   //step2 解析方法事务属性
   TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
   if (txAttr != null) {
      return txAttr;
   }

   // 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) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
         return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
         return txAttr;
      }
   }

   return null;
}

step2处findTransactionAttribute()方法解析事务属性,该方法是AnnotationTransactionAttributeSource类实现,其调用determineTransactionAttribute(method)方法。

AnnotationTransactionAttributeSource#determineTransactionAttribute

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
   for (TransactionAnnotationParser parser : this.annotationParsers) {
      TransactionAttribute attr = parser.parseTransactionAnnotation(element);
      if (attr != null) {
         return attr;
      }
   }
   return null;
}

这里拿出所有的annotationParsers进行解析方法是否定义事务。annotationParsers是在AnnotationTransactionAttributeSource的构造方法初始化,这里有两个SpringTransactionAnnotationParser和JtaTransactionAnnotationParser。我们主要看SpringTransactionAnnotationParser。

SpringTransactionAnnotationParser#parseTransactionAnnotation

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
   AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
         element, Transactional.class, false, false);
   if (attributes != null) {
      return parseTransactionAnnotation(attributes);
   }
   else {
      return null;
   }
}

终于看到熟悉的Transactional注解了,也不往方法里面看了。这里就是解析@Transactional注解配置的事务属性。

3、事务管理

代理创建和AOP过程一致,这里还是以JDK代理类为例。代理对象是JdkDynamicAopProxy。原方法的执行会首先进入JdkDynamicAopProxy.invoke方法。

这里还是从invoke的获取拦截器链开始

List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

最后会调到

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
   List<MethodInterceptor> interceptors = new ArrayList<>(3);
   Advice advice = advisor.getAdvice();
   if (advice instanceof MethodInterceptor) {//会走这里
      interceptors.add((MethodInterceptor) advice);
   }
   for (AdvisorAdapter adapter : this.adapters) {//这是Aspect的情况
      if (adapter.supportsAdvice(advice)) {
         interceptors.add(adapter.getInterceptor(advisor));
      }
   }
   if (interceptors.isEmpty()) {
      throw new UnknownAdviceTypeException(advisor.getAdvice());
   }
   return interceptors.toArray(new MethodInterceptor[0]);
}

这里要回到开始ProxyTransactionManagementConfiguration引入Advisor引入的地方

public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
      TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

   BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
   advisor.setTransactionAttributeSource(transactionAttributeSource);
   //设置的advisor是TransactionInterceptor类型
   advisor.setAdvice(transactionInterceptor);
   if (this.enableTx != null) {
      advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
   }
   return advisor;
}

所以最后会调用TransactionInterceptor的invoke方法

TransactionInterceptor#invoke

public Object invoke(MethodInvocation invocation) throws Throwable {
   // Work out the target class: may be {@code null}.
   // The TransactionAttributeSource should be passed the target class
   // as well as the method, which may be from an interface.
   Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

   // Adapt to TransactionAspectSupport's invokeWithinTransaction...
   return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
      @Override
      @Nullable
      public Object proceedWithInvocation() throws Throwable {
         return invocation.proceed();
      }
      @Override
      public Object getTarget() {
         return invocation.getThis();
      }
      @Override
      public Object[] getArguments() {
         return invocation.getArguments();
      }
   });
}

最后事务的控制在invokeWithinTransaction方法完成。

总结

开启事务为我们引入了两个重要的

1、BeanFactoryTransactionAttributeSourceAdvisor事务Advisor。设置两个属性TransactionInterceptor用来代理管理事务,TransactionAttributeSource用来匹配事务方法。

1、InfrastructureAdvisorAutoProxyCreator是bean后置处理器,用来判断当前bean是否需要代理。会拿出第一步引入的事务Advisor,与当前bean的所有方法进行匹配看是否有@Transaction注解。有就创建代理,最后使用BeanFactoryTransactionAttributeSourceAdvisor中设置的TransactionInterceptor拦截器进行事务管理。

整理流程
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1066646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

883. 高斯消元解线性方程组

883. 高斯消元解线性方程组 - AcWing题库 输入一个包含 n 个方程 n 个未知数的线性方程组。 方程组中的系数为实数。 求解这个方程组。 下图为一个包含 m 个方程 n 个未知数的线性方程组示例&#xff1a; 输入格式 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含 n1…

千万不要支付赎金!解密.halo勒索病毒的秘诀在这里

导言&#xff1a; .halo 勒索病毒等勒索病毒已经成为网络犯罪分子的利器&#xff0c;威胁着很多企业的数据安全。本文91数据恢复将为您介绍 .halo 勒索病毒的新面貌&#xff0c;以及一些创新的方法&#xff0c;如何保护和恢复被 .halo 勒索病毒加密的数据文件&#xff0c;并提供…

【计算机视觉|人脸建模】学习从图像中回归3D面部形状和表情而无需3D监督

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Learning to Regress 3D Face Shape and Expression from an Image without 3D Supervision 链接&#xff1a;[1905.06817] Learning to Regress 3D Face Shape and Expression from an I…

6-10 单链表分段逆转 分数 15

void K_Reverse( List L, int K ) { //此题已经默认size > K 因为当size < K时 反转后将不再符合链表的定义//求出表中元素个数int size 0;for (List cur L->Next; cur ! NULL; cur cur->Next)size; List prv, cur, next, first, head L;//共需要反转 si…

功率放大器可以放大电压吗为什么

功率放大器是一种电子设备&#xff0c;用于将输入信号的功率放大到更高的水平。而电压是功率的一个组成部分&#xff0c;所以功率放大器可以放大电压。 在功率放大器中&#xff0c;输入信号经过放大器的放大阶段后&#xff0c;会得到一个更强的输出信号。这个放大过程是通过增加…

HTML5开发实例-3D全景(ThreeJs全景Demo) 详解(图)

前言 在现在市面上很多全景H5的环境下,要实现全景的方式有很多,可以用css3直接构建也可以用基于threeJs的库来实现,还有很多别的制作全景的软件使用 本教学适用于未开发过3D全景的工程狮 如果觉得内容太无聊可以直接跳到最后 下载代码 理论 整个3D全景所用的相关理论就…

CSS布局 | flex布局

flex布局 flex是CSS3中新增的布局手段&#xff0c;优势是适用于不同屏幕尺寸和设备&#xff0c;当布局涉及到不定宽度&#xff0c;分布对⻬的场景时&#xff0c;我们可以优先考虑弹性盒布局。 任何一个容器都可以指定为Flex布局&#xff0c;容器设为 Flex 布局以后&#xff0…

电驱2035目标及新材料研究应用进展-2023

今天同大家一起了解DOE电驱2035目标&#xff08;成本、功率密度、电压、峰值功率&#xff09;&#xff0c;及当前研发项目中关于电驱电机的新材料研究进展与应用。 2030-2035电驱系统目标 峰值功率和功率密度按每5年50%的速度提升&#xff0c;电压平台800V&#xff0c;增加峰值…

数据被加密?.locked1勒索病毒的简单解决方法

引言&#xff1a; 在数字化的今天&#xff0c;数据宛如生命的一部分&#xff0c;而 .locked1 勒索病毒这种威胁正在如影随形地威胁着我们的数字宝库。本文将为您生动地介绍 .locked1 勒索病毒&#xff0c;以及如何摆脱它的束缚&#xff0c;解锁被其加密的数据&#xff0c;同时提…

Unity Golang项目教程-创建项目

安装Unity Unity的安装比较简单。这里我不做详细介绍&#xff0c;提供一些安装教程链接&#xff0c;如果还有困难下面我提供联系方式可以私信我。 安装教程参考 创建工程如下图所示&#xff1a; 等待项目创建完成即可。 如有问题可以Q联系我&#xff0c; 873149745

CCF中国开源大会专访 | 刘旭东:着眼“开源联合”,实现“聚力共赢”

受访嘉宾 | 刘旭东 记者 | 朱珂欣 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 2023 CCF 中国开源大会&#xff08; CCF ChinaOSC &#xff09;拟于 2023 年 10 月 21 日至 22 日在湖南省长沙市北辰国际会议中心召开。 作为第二届 CCF 中国开源大会&a…

计算CPK及规范限合格率,并绘制概率密度曲线

CPK&#xff08;过程能力指数&#xff09;是一个用于衡量一个过程的稳定性和一致性的统计指标&#xff0c;特别用于制造业和质量管理中。它衡量了一个过程的变异性与规范界限的关系&#xff0c;帮助确定过程是否能够产生合格的产品或服务。 正态分布假设&#xff1a;CPK的计算…

Kfka监控工具--Kafka-eagle安装

1、开启Kafka JMX端口 JMX 是一个为应用程序植入管理功能的框架 在启动Kafka脚本之前&#xff0c;添加&#xff1a; export JMX_PORT9988 nohup bin/kafka-server-start.sh comfig/server.properties 2、安装jdk配置好JAVA_HOME 3、将kafka_eagle 上传并解压 tar -zxvf …

想做扫码看图效果,你需要学会这一招

现在扫描二维码来查看图片是常见的一种方式&#xff0c;比如资料图片、个人照片、pdf图片、表情包等等都可以通过扫码的方式来展现。而且生成图片二维码的方法也非常的简单&#xff0c;只需要使用二维码在线生成器&#xff0c;就可以使用浏览器来快速完成制作&#xff0c;对于还…

VS2022+qt5.15.2+cmake3.23.2配置VTK9.1.0版本

VS2022qt5.15.2cmake3.23.2VTK9.1.0 尝试了好多次&#xff0c;终于成了~ 软件安装 先把需要的软件都安装好&#xff01; VS2022安装教程: https://blog.csdn.net/qq_44005305/article/details/132295064 qt5.15.2安装教程&#xff1a;https://blog.csdn.net/Qi_1337/article…

python—如何提取word中指定内容

假设有一个Word&#xff0c;该Word中存在 “联系人” 关键字&#xff0c;如何将该Word中的联系人所对应的内容提取出来呢&#xff1f; 该Word内容如下所示&#xff1a; 要在给定的Word文档中提取出与"联系人"关键字对应的内容&#xff0c;可以使用Python的py…

qt开发从入门到实战2

以下是本人学习笔记 原视频&#xff1a;最新QT从入门到实战完整版|传智教育 qt开发从入门到实战1 练习示例 设计一个按钮&#xff0c;点击时弹出新窗口&#xff0c;再次点击时新窗口关闭 // exerciseQWidget* second_window new QWidget();QPushButton* btn3 new QPushBu…

兽药经营小程序微信商城的作用是什么

无论家宠还是畜牧养殖&#xff0c;生病杀虫总是不可少的&#xff0c;尤其对铲屎官们来说&#xff0c;宠物的健康状况很重要&#xff0c;以此花费百元千元也并不觉心疼&#xff0c;兽药的需求度也是非常高&#xff0c;那么对相关从业商家来说&#xff0c;遇到的难题有哪些&#…

淘宝大数据揭秘:购物狂欢节背后的秘密

淘宝详情接口是淘宝开放平台提供的一种API接口&#xff0c;主要用于获取淘宝商品详情信息。通过该接口&#xff0c;开发者可以在自己的网站或应用程序中快速获取淘宝商品的详细信息&#xff0c;包括价格、图片、商品描述等。 要使用淘宝详情接口&#xff0c;首先需要在淘宝开放…

[NISACTF 2022]babyserialize - 反序列化+waf绕过【*】

[NISACTF 2022]babyserialize 一、解题过程二、思考总结&#xff08;一&#xff09;、关于题目的小细节&#xff08;二&#xff09;、关于弱类型比较技巧 一、解题过程 题目代码&#xff1a; <?php include "waf.php"; class NISA{public $fun"show_me_fl…