Spring源码分析のAOP

文章目录

  • 前言
  • 一、wrapIfNecessary
    • 1.1、getAdvicesAndAdvisorsForBean
      • 1.1.1、findCandidateAdvisors
      • 1.1.2、findAdvisorsThatCanApply
    • 1.2、createProxy
  • 二、invoke
    • 2.1、getInterceptorsAndDynamicInterceptionAdvice
      • 2.1.1、getInterceptors
    • 2.2、proceed
      • 2.2.1、invoke
  • 三、@AspectJ模式下注解的解析
  • 总结


前言

  在Spring中,AOP通常是通过动态代理实现的,通过运行时增强的机制,以实现在目标代码执行前后的统一的逻辑。而Spring将两大动态代理,封装成为了ProxyFactory,在ProxyFactory中,会自动进行动态代理方式的选择:

@Component
public class UserService {public void originalMethod() {System.out.println("UserService originalMethod");}
}

  同时在ProxyFactory中,允许在目标对象上添加切面(advisors 或 advices),自定义切面的逻辑:

/*** 方法执行前后执行*/
public class AroundAdvice implements MethodInterceptor {/*** Implement this method to perform extra treatments before and* after the invocation. Polite implementations would certainly* like to invoke {@link Joinpoint#proceed()}.** @param invocation the method invocation joinpoint* @return the result of the call to {@link Joinpoint#proceed()};* might be intercepted by the interceptor* @throws Throwable if the interceptors or the target object*                   throws an exception*/@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("方法执行前");invocation.proceed();System.out.println("方法执行后");return null;}
}

  其中ThrowsAdvice 较为特殊,接口中没有具体的方法,但是子类在定义具体的实现时,方法的名称,签名必须要遵循特定的格式:
在这里插入图片描述

/*** 抛出异常后执行*/
public class AfterThrowingAdvice implements ThrowsAdvice {public void afterThrowing(Method method, Object[] args, Object target, Exception ex){System.out.println("AfterThrowingAdvice");}
}

  在ProxyFactory中添加切面,其中Advisor是Advice + Pointcut组合,等于切入点+切面,指定在哪些方法上应用 Advice:

public class ProxyFactoryDemo {public static void main(String[] args) {UserService userService = new UserService();ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(userService);proxyFactory.addAdvice(new AroundAdvice());proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {return new StaticMethodMatcherPointcut() {@Overridepublic boolean matches(Method method, Class<?> targetClass) {return method.getName().equals("originalMethod");}};}@Overridepublic Advice getAdvice() {return new BeforeAdvice();}@Overridepublic boolean isPerInstance() {return false;}});UserService proxy = (UserService) proxyFactory.getProxy();proxy.originalMethod();}
}

  而在Spring的底层,如果通过AOP配置类的方式进行:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.ragdollcat.aop.aspect")
public class AppConfig {
}
@Aspect
@Component
public class MyAspect {@Before("execution(public void org.ragdollcat.aop.aspect.UserService.originalMethod())")public void before(JoinPoint joinPoint) {System.out.println("before method");}
}
@Component
public class UserService {public void originalMethod() {System.out.println("UserService originalMethod");}
}
public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserService bean = (UserService) context.getBean("userService");bean.originalMethod();}
}

  会在refresh的invokeBeanFactoryPostProcessors这一步,解析AppConfig 配置类,扫描到了@EnableAspectJAutoProxy注解,就会向
beanDefinitionMap中存放一个AnnotationAwareAspectJAutoProxyCreator类型的bean后处理器。
在这里插入图片描述在这里插入图片描述  最终在bean生命周期的初始化后这一步
在这里插入图片描述  执行AbstractAutoProxyCreatorpostProcessAfterInitialization方法:
在这里插入图片描述
在这里插入图片描述

一、wrapIfNecessary

  最终通过AbstractAutoProxyCreatorpostProcessAfterInitialization方法,会进入wrapIfNecessary方法,在AbstractAutoProxyCreator中,有两个重要的属性:

  • targetSourcedBeans是一个set集合,用于存储不需要代理的 Bean 名称。
  • advisedBeans 是一个Map,记录了哪些bean需要代理,哪些不需要代理,value存放了布尔值,表示该bean是否应该被代理。

  这两个属性都起到缓存的作用。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 如果 beanName 非空且 targetSourcedBeans 集合中包含该 beanName,则直接返回 bean,不进行代理if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean;}// 如果 cacheKey 对应的 bean 在 advisedBeans 中已明确标记为不需要代理,则直接返回 beanif (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean;}// 如果该 bean 是基础设施类(比如加上了@Component注解的自定义advice类)或者应跳过代理(shouldSkip 方法返回 true),// 则将其在 advisedBeans 中标记为不需要代理,并直接返回 beanif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 1.1、getAdvicesAndAdvisorsForBean 获取该 bean 适用的增强(Advice)或拦截器(Advisor)Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 如果返回的拦截器数组不是 DO_NOT_PROXY(表示需要代理),则创建代理对象if (specificInterceptors != DO_NOT_PROXY) { // 在 advisedBeans 记录该 bean 需要代理this.advisedBeans.put(cacheKey, Boolean.TRUE);// 1.2、createProxy 创建代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 进行缓存,方便后续使用this.proxyTypes.put(cacheKey, proxy.getClass());// 返回代理对象return proxy;}// 如果不需要代理,则记录该 bean 不需要代理,并返回原始 beanthis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

1.1、getAdvicesAndAdvisorsForBean

  getAdvicesAndAdvisorsForBean最终会进入findEligibleAdvisors方法,在该方法中,做了四件事,主要是用于判断,当前的bean是否需要aop

  • 获取所有候选的 Advisor(增强逻辑),即当前 Spring 容器中所有可用的通知器。
  • 从所有候选 Advisor(candidateAdvisors)中筛选出适用于当前 beanClass 的 Advisor。
  • 对筛选出的 Advisor 进行扩展处理。
  • 如果有可用的 Advisor,则进行排序。
    在这里插入图片描述

1.1.1、findCandidateAdvisors

  在findCandidateAdvisors中,也做了两件事,分别是调用父类findCandidateAdvisors()方法,获取 Spring 机制自动发现的 Advisor,以及调用 buildAspectJAdvisors() 解析所有 @Aspect 类。
在这里插入图片描述  如果我们像前言中那样手动注册了Advisor,则会走父类的逻辑,找到所有类型为Advisor的自定义切面。
在这里插入图片描述  否则使用了 @Aspect 注解,就会走后续的逻辑,先是从容器中获取所有的bean,然后判断哪些bean上加入了@Aspect注解,在getAdvisors方法中真正进行解析:
在这里插入图片描述  ReflectiveAspectJAdvisorFactorygetAdvisors,会找到标注了@Aspect类中,没有加@PointCut的方法,然后循环这些方法,解析成切面:
在这里插入图片描述  最终包装成InstantiationModelAwarePointcutAdvisorImpl对象返回:
在这里插入图片描述  InstantiationModelAwarePointcutAdvisorImplAdvisor的子类
在这里插入图片描述

1.1.2、findAdvisorsThatCanApply

  这一段代码的重点在于canApply方法,会判断是否有匹配的切点。
在这里插入图片描述

// 该方法用于判断一个给定的 Pointcut 是否适用于目标类
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {// 确保 Pointcut 对象不为 null,若为 null 则抛出异常Assert.notNull(pc, "Pointcut must not be null");// 如果目标类不匹配 Pointcut 的类过滤器,则直接返回 falseif (!pc.getClassFilter().matches(targetClass)) {return false;}// 获取与 Pointcut 相关联的方法匹配器MethodMatcher methodMatcher = pc.getMethodMatcher();// 如果方法匹配器是 "TRUE",表示所有方法都可以匹配,直接返回 trueif (methodMatcher == MethodMatcher.TRUE) {return true;}// 如果方法匹配器是 IntroductionAwareMethodMatcher 类型,则强制转换为该类型IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}// 使用 LinkedHashSet 存储目标类和它的接口,避免重复Set<Class<?>> classes = new LinkedHashSet<>();// 如果目标类不是代理类,则添加目标类的用户类(去除代理的类)if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}// 将目标类的所有接口也加入到类集合中classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));// 遍历类集合中的每一个类,检查该类中每个方法是否匹配 Pointcutfor (Class<?> clazz : classes) {// 获取该类的所有的方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);// 遍历方法,检查每个方法是否匹配 Pointcut 的方法匹配器for (Method method : methods) {// 如果使用的是 IntroductionAwareMethodMatcher,则使用其匹配方法,否则使用普通的 methodMatcher 匹配if (introductionAwareMethodMatcher != null ? introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) : methodMatcher.matches(method, targetClass)) {// 如果匹配成功,返回 truereturn true;}}}// 如果没有方法匹配,则返回 falsereturn false;
}

1.2、createProxy

  createProxy 方法的作用是创建代理对象,底层使用的也是proxyFactory:
在这里插入图片描述  最终调用的也是proxyFactorygetProxy方法,在该方法中,主要做了两件事:

  • 代理模式的选择
  • 根据选择的代理模式,创建代理对象

在这里插入图片描述

  • 如果当前的jvm是GraalVM,就直接用jdk动态代理。
  • 如果当前的jvm不是GraalVM,并且下面三个条件满足其一,就会再次进行判断:启用优化(cglib的性能某些情况要优于jdk动态代理)或isProxyTargetClass为true或没有为目标类提供用户自定义的代理接口。
    • 如果目标类是接口或目标类是代理类或目标类是 Lambda 类,还是会使用jdk动态代理。
    • 否则就会走cglib动态代理

在这里插入图片描述  我的案例中,使用的是jdk动态代理,在进行了代理的选择后
在这里插入图片描述  就会根据jdk和cglib不同的方式去创建代理对象:
在这里插入图片描述  以jdk动态代理为例:
在这里插入图片描述

二、invoke

  invoke方法是在调用代理类目标方法时执行的逻辑。
  在invoke方法中首先会进行判断,如果当前方法是equals或者hashcode,就不会进行代理,进到invoke是以被代理类中单个方法的维度。
在这里插入图片描述  并且@EnableAspectJAutoProxyexposeProxy属性为true时(默认为false),就会把当前的代理放入ThreadLocal中。(解决事务自调用失效)
在这里插入图片描述  然后会获取方法的拦截器链:
在这里插入图片描述  最后会进行判断,如果拦截器链为空,说明没有适合该方法的切面,则不需要AOP,直接调用目标方法,否则会按照顺序调用拦截器链:
在这里插入图片描述

2.1、getInterceptorsAndDynamicInterceptionAdvice

  获取方法的拦截器链,最终会调用DefaultAdvisorChainFactorygetInterceptorsAndDynamicInterceptionAdvice方法,这一步会拿到所有的advisor
在这里插入图片描述  如果定义的是advice,也会被封装成advisor:
在这里插入图片描述  接着会去遍历所有的advisor,关键性的代码:

if (advisor instanceof PointcutAdvisor) {// Add it conditionally.PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;//先匹配类,可以重写Pointcut接口的getClassFilter方法,自定义匹配的逻辑 比如根据类名匹配if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {//再匹配方法,可以重写Pointcut接口的getMethodMatcher方法,自定义匹配的逻辑 MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();boolean match;if (mm instanceof IntroductionAwareMethodMatcher) {if (hasIntroductions == null) {hasIntroductions = hasMatchingIntroductions(advisors, actualClass);}match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);}else {//matches如果没有自定义,默认是truematch = mm.matches(method, actualClass);}if (match) {//2.1.1、getInterceptors	将所有的advisor转换为MethodInterceptor对象MethodInterceptor[] interceptors = registry.getInterceptors(advisor);//如果方法匹配器的.isRuntime属性设置成了true,就会对所有匹配的方法,再进行参数的筛选。if (mm.isRuntime()) {// Creating a new object instance in the getInterceptors() method// isn't a problem as we normally cache created chains.for (MethodInterceptor interceptor : interceptors) {interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));}}else {interceptorList.addAll(Arrays.asList(interceptors));}}}
}

2.1.1、getInterceptors

  getInterceptors是将advisor封装成MethodInterceptor的逻辑,在这一段代码中,运用了适配器模式

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {List<MethodInterceptor> interceptors = new ArrayList<>(3);Advice advice = advisor.getAdvice();//如果advice 的类型本来就属于 MethodInterceptorif (advice instanceof MethodInterceptor) {//直接添加进集合interceptors.add((MethodInterceptor) advice);}//遍历所有的适配器for (AdvisorAdapter adapter : this.adapters) {//如果某一个适配器和当前的advice类型匹配if (adapter.supportsAdvice(advice)) {//就由该适配器进行类型转换interceptors.add(adapter.getInterceptor(advisor));}}if (interceptors.isEmpty()) {throw new UnknownAdviceTypeException(advisor.getAdvice());}return interceptors.toArray(new MethodInterceptor[0]);
}

  在构造DefaultAdvisorAdapterRegistry时,默认添加了三个适配器:
在这里插入图片描述  适配器类
在这里插入图片描述  某一个具体子类,实现了适配器类:
在这里插入图片描述

2.2、proceed

  proceed是执行拦截器链以及目标方法的逻辑,有两个实现,目前主要看ReflectiveMethodInvocation的实现:
在这里插入图片描述
  在该类中有一个重要的属性,初始值是-1,会根据该属性的值去判断是否应该执行目标方法:
在这里插入图片描述  proceed方法中,主要对于InterceptorAndDynamicMethodMatcher 类型的拦截器进行了判断,如果拦截器是该类型,说明需要对于方法的参数也进行匹配。

public Object proceed() throws Throwable { // proceed 方法用于执行拦截器链中的下一个拦截器,或最终执行目标方法// 当拦截器链中的所有拦截器都执行完毕时(即当前索引等于最后一个拦截器的索引),执行目标方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {// 通过反射调用目标方法,相当于 method.invoke(target, args);return invokeJoinpoint();}// 从拦截器链中取出下一个拦截器(或拦截器 + 动态方法匹配器)Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);// 判断当前拦截器是否是 InterceptorAndDynamicMethodMatcher 类型//InterceptorAndDynamicMethodMatcher 类型的拦截器,支持对于方法的参数进行匹配if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// 强制类型转换,将拦截器对象转换为 InterceptorAndDynamicMethodMatcherInterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;// 确定目标类,如果 targetClass 为空,则使用 method 所在的类Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());// 使用动态方法匹配器判断当前方法是否符合匹配规则if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {// 如果方法匹配,则调用对应的拦截器return dm.interceptor.invoke(this);} else {// 如果方法不匹配,则跳过该拦截器,继续执行下一个拦截器return proceed();}} else {// 如果拦截器不是 InterceptorAndDynamicMethodMatcher 类型,则直接执行其 invoke 方法return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}
}

  在invoke方法中,实际调用的就是各自切面的逻辑,假设我的切面类如下:

@Aspect
@Component
public class MyAspect {@Before("execution(public void org.ragdollcat.aop.aspect.UserService.originalMethod())")public void before(JoinPoint joinPoint) {System.out.println("before method");}@Around("execution(public void org.ragdollcat.aop.aspect.UserService.originalMethod())")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around - before method");Object proceed = pjp.proceed();System.out.println("around - after method");return proceed;}@After("execution(public void org.ragdollcat.aop.aspect.UserService.originalMethod())")public void after() {System.out.println("after method");}
}

  那么执行切面的顺序是:
在这里插入图片描述

2.2.1、invoke

  这里的invoke最终调用的是各个切面的invoke方法
  基于基于 Spring AOP代理方式的拦截器:

  • AfterReturningAdviceInterceptor:在目标方法正常返回后执行的拦截器(不包括异常情况),需要实现AfterReturningAdvice接口。相当于注解模式下的@AfterReturning 和@After注解
  • MethodBeforeAdviceInterceptor:在目标方法执行前执行的拦截器,需要实现MethodBeforeAdvice接口。相当于注解模式下的@Before注解
  • ThrowsAdviceInterceptor:在目标方法抛出异常后执行的拦截器。需要实现ThrowsAdvice接口。相当于注解模式下@AfterThrowing注解
  • MethodInterceptor:目标方法执行前、执行后,相当于注解模式下的@Around注解

  是基于 AspectJ 注解方式的拦截器:

  • AspectJAfterAdvice:在目标方法执行后触发,不管方法是否抛出异常,对应@After注解。
  • AspectJMethodBeforeAdvice:在目标方法执行前触发,对应@Before 注解
  • AspectJAroundAdvice:在目标方法执行前、执行后、异常时都可以进行拦截,相当于 @Before + @AfterReturning + @AfterThrowing 组合,对应@Around注解。
  • AspectJAfterThrowingAdvice:异常通知拦截器,在目标方法抛出异常后触发。对应@AfterThrowing注解。
  • AspectJAfterReturningAdvice:在目标方法正常返回后执行的拦截器(不包括异常情况),对应@AfterReturning注解。

  可以简单的看一下其中的invoke逻辑:
在这里插入图片描述前置增强,先执行前置增强逻辑,然后继续调用链

在这里插入图片描述后置增强,先执行调用链,最终再执行后置增强逻辑

三、@AspectJ模式下注解的解析

  在@AspectJ模式下,对于注解的解析,最终会调用到ReflectiveAspectJAdvisorFactorygetAdvice方法,其中有对于每个注解的解析逻辑:
在这里插入图片描述  最终也是将其包装成不同的切面,然后会统一再次封装成InstantiationModelAwarePointcutAdvisorImpl对象,作为该对象的instantiatedAdvice属性。
在这里插入图片描述  并且在2.1.1、getInterceptors中,会对其进行二次适配,目的是将不同类型的 Advice 转换为 MethodInterceptor
在这里插入图片描述
在这里插入图片描述

Advice 类型作用适配的 AdvisorAdapter转换后的 MethodInterceptor
@Before方法执行前MethodBeforeAdviceAdapterMethodBeforeAdviceInterceptor
@AfterReturning方法正常返回后AfterReturningAdviceAdapterAfterReturningAdviceInterceptor
@AfterThrowing方法抛出异常后ThrowsAdviceAdapterThrowsAdviceInterceptor
@After方法执行完成(无论是否异常)AspectJAfterAdvice直接实现了 MethodInterceptor
@Around方法执行前后AspectJAroundAdvice直接实现了 MethodInterceptor

总结

  Spring的AOP,分为了两部分:
  第一部分是@EnableAspectJAutoProxy向Spring容器中添加的AnnotationAwareAspectJAutoProxyCreator后置处理器的调用(在bean生命周期的初始化后调用),主要完成了:

  • 获取并解析切面
    1. 获取所有候选的 Advisor(增强逻辑),即当前 Spring 容器中所有可用的通知器。
      • 对于Spring AOP API注册的切面的适配。
      • 对于AspectJ 注解方式的解析。
    2. 从所有候选 Advisor(candidateAdvisors)中筛选出适用于当前 beanClass 的 Advisor。
    3. 对筛选出的 Advisor 进行扩展处理。
    4. 如果有可用的 Advisor,则进行排序。
  • 构建代理
    1. 选择代理模式
    2. 创建代理对象

  第二部分是执行目标方法,代理类的invoke方法的调用:

  1. 获取到与方法匹配的拦截器链,这里会再次进行切面匹配:
    • 匹配类
    • 匹配方法
    • 特殊情况匹配方法的参数
  2. 目标方法的调用(递归)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LINUX网络基础 [一] - 初识网络,理解网络协议

目录 前言 一. 计算机网络背景 1.1 发展历程 1.1.1 独立模式 1.1.2 网络互联 1.1.3 局域网LAN 1.1.4 广域网WAN 1.2 总结 二. "协议" 2.1 什么是协议 2.2 网络协议的理解 2.3 网络协议的分层结构 三. OSI七层模型&#xff08;理论标准&#xff09; …

React学习笔记10

一、Redux与React-提交action传参 需求&#xff1a;组件中有两个按钮&#xff0c;点击add to 10和add to 20将count的值修改到对应的数字&#xff0c;目标count值是在组件中传递过去的&#xff0c;需要提交action的时候传递参数 实现思路&#xff1a;在reducers的同步修改方法中…

Docker概念与架构

文章目录 概念docker与虚拟机的差异docker的作用docker容器虚拟化 与 传统虚拟机比较 Docker 架构 概念 Docker 是一个开源的应用容器引擎。诞生于 2013 年初&#xff0c;基于 Go 语言实现。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xf…

HarmonyOS Next~应用开发入门:从架构认知到HelloWorld实战

HarmonyOS应用开发入门&#xff1a;从架构认知到HelloWorld实战 一、HarmonyOS架构解析 1.1 分布式能力三要素 &#xff08;1&#xff09;软总线&#xff08;SoftBus&#xff09; HarmonyOS的核心神经中枢&#xff0c;通过统一的分布式通信协议实现设备间的自动发现和组网。…

相控阵扫盲

下图展示天线增益 在仰角为0度的情况下随着方位角的变化而变化。需要注意到的是在天线视轴方向上的高增益主瓣上还有几个低增益旁瓣 阵列因子乘以新的阵元方向图会形成指向性更强的波速

[QT]开发全解析:从概念到实战

文章目录 Qt 框架入门与应用开发指南一、Qt 框架概述1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt 的应用场景1.7 Qt 的成功案例 二、Qt 的开发工具概述Qt CreatorVisual StudioEclipse 三、认识 Qt Creator3.1 Qt Creator 概览3.2 使用 Qt C…

LeetCode 718.最长重复子数组(动态规划,Python)

给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1a; 输…

从厨电模范到数字先锋,看永洪科技如何助力方太集团开启数字新征程

在数字化洪流席卷全球的宏大背景下&#xff0c;企业转型升级的紧迫性与重要性日益凸显&#xff0c;成为驱动行业进步的关键引擎。在这一波澜壮阔的转型浪潮中&#xff0c;方太集团——厨电领域的璀璨明珠&#xff0c;以其前瞻性的战略视野和不懈的创新精神&#xff0c;携手数据…

C++11中atomic

C11中atomic 在C中&#xff0c;std::atomic 是一个非常重要的工具&#xff0c;主要用于实现线程安全的操作。它属于C11标准引入的 <atomic> 头文件的一部分&#xff0c;用于处理多线程环境下的原子操作。以下是 std::atomic 的主要作用和特点&#xff1a; 1. 保证操作的…

尚庭公寓项目记录

数据库准备 保留图像时&#xff0c;保存图像地址就可以数据表不是越多越好&#xff0c;可以用中间表来实现俩个表之间的联立这样方便查数据但是却带来性能问题而减少表的jion但是提高性能&#xff0c;以冗余来换去性能采用MySQL&#xff0c;InnoDB存储引擎物理删除和逻辑删除逻…

unity6 打包webgl注意事项

webgl使用资源需要异步加载 使用localization插件时要注意&#xff0c;webgl不支持WaitForCompletion&#xff0c;LocalizationSettings.InitializationOperation和LocalizationSettings.StringDatabase.GetTable都不能用 web里想要看到具体的报错信息调试开启这两个&#xf…

wxWidgets GUI 跨平台 入门学习笔记

准备 参考 https://wiki.wxwidgets.org/Microsoft_Visual_C_NuGethttps://wiki.wxwidgets.org/Tools#Rapid_Application_Development_.2F_GUI_Buildershttps://docs.wxwidgets.org/3.2/https://docs.wxwidgets.org/latest/overview_helloworld.htmlhttps://wizardforcel.gitb…

C++20 中使用括号进行聚合初始化:新特性与实践指南

文章目录 1. 聚合初始化简介2. C20 中的括号聚合初始化2.1 指定初始化器&#xff08;Designated Initializers&#xff09;2.2 嵌套聚合初始化 3. 使用括号初始化数组4. 注意事项5. 实际应用场景6. 总结 在 C20 中&#xff0c;聚合初始化&#xff08;Aggregate Initialization&…

TomcatServlet

https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架构图&#xff0c;与 jre&#xff0c;应用程序之前的关系 安装使用 tomcat 10 开始&#xff0c;api 从 javax.* 转为使用 jakarta.*&#xff0c;需要至少使用 jdk 11 cmd 中默认 gbk 编码&#xff0c;解决控制…

android接入rocketmq

一 前言 RocketMQ 作为一个功能强大的消息队列系统&#xff0c;不仅支持基本的消息发布与订阅&#xff0c;还提供了顺序消息、延时消息、事务消息等高级功能&#xff0c;适应了复杂的分布式系统需求。其高可用性架构、多副本机制、完善的运维管理工具&#xff0c;以及安全控制…

有关Java中的集合(1):List<T>和Set<T>

学习目标 核心掌握List集合了解Set集合 1.List<T> ● java.util.List。有序列表。 ● List集合元素的特点&#xff1a;有序表示存取有序&#xff08;因为有索引&#xff09;而且可以重复 ● List常用实现类&#xff1a; ArrayList、LinkedList、Vector等 1.1 常用方法…

DeepSeek+Graphrag检索增强

用于增强的文章为一篇机器学习的文章&#xff0c;以及本人自己的论文 对于此感兴趣的可私聊我&#xff0c;过多细节不便展示 实现方法 图构建 数据收集&#xff1a;收集与检索相关的各种数据&#xff0c;如文本、图像、元数据等。实体识别和关系抽取&#xff1a;从收集的数据…

利用opencv_python(pdf2image、poppler)将pdf每页转为图片

1、安装依赖pdf2image pip install pdf2image 运行.py报错&#xff0c;因为缺少了poppler支持。 2、安装pdf2image的依赖poppler 以上命令直接报错。 改为手工下载&#xff1a; github: Releases oschwartz10612/poppler-windows GitHub 百度网盘&#xff1a; 百度网盘…

C# Unity 面向对象补全计划 之 [反射]自动处理带有自定义[特性]的类

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 有一些插件就是利用本篇的方法做"自动"处理的 目录 1.情景: 2.介绍与举例: 自定义特性API与使用 反射搜索自定义API 3.优化 4.处理带有自定义特性的类…

AI-Deepseek + PPT

01--Deepseek提问 首先去Deepseek问一个问题&#xff1a; Deepseek的回答&#xff1a; 在汽车CAN总线通信中&#xff0c;DBC文件里的信号处理&#xff08;如初始值、系数、偏移&#xff09;主要是为了 将原始二进制数据转换为实际物理值&#xff0c;确保不同电子控制单元&…