Spring源码-AOP具体源码

news2024/10/6 5:39:08

1.类ProxyFactory

核心方法:getProxy

1.DefaultAopProxyFactory#createAopProxy

判断使用JDK还是CGLIB动态代理的代码如下:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 如果ProxyFactory的isOptimize为true,Spring认为cglib比jdk动态代理要快
    // 或者isProxyTargetClass为true,
    // 或者被代理对象没有实现接口,
    // 或者只实现了SpringProxy这个接口
    // 那么则利用Cglib进行动态代理,但如果被代理类是接口,或者被代理类已经是进行过JDK动态代理而生成的代理类了则只能进行JDK动态代理

    // 其他情况都会进行JDK动态代理,比如被代理类实现了除SpringProxy接口之外的其他接口

    // 是不是在GraalVM虚拟机上运行
    if (!NativeDetector.inNativeImage() &&
        (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {

        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                                         "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//被代理的类 是接口
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

判断该使用哪种技术生成代理对象

config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config),判断是否优化、是否代理目标类、是否没有接口。满足这个条件,返回CGLIB代理对象;不满足,就返回JDK代理对象。

同时还会判断,判断:targetClass.isInterface() || Proxy.isProxyClass(targetClass),如果被代理的类是接口,那就返回JDK代理对象;如果类是被JDK代理的类,那继续使用JDK代理对象。

(isOptimize()表示Spring启动优化,那就使用CGLIB;isProxyTargetClass 表示代理的是类,JDK只能代理接口;hasNoUserSuppliedProxyInterfaces表示用户有没有addInterface)

2.getProxy

核心:通过不同的技术,产生代理对象(有两种实现方法,CGLIB和JDK动态代理)

2.Jdk生成的代理对象的执行流程

核心方法:JdkDynamicAopProxy#invoke

1.targetSource = this.advised.targetSource

拿到被代理对象

2.如果接口中的是equals方法或者hasCode方法,那它们不用走代理

3.if (this.advised.exposeProxy),true-->把代理对象放到ThredLocal中

如果ProxyFactory的exposeProxy为true,则将代理对象设置到currentProxy这个ThreadLocal中

4.target = targetSource.getTarget();Class<?> targetClass

拿到被代理对象和代理类

5.chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(2.1详细讲

代理对象在执行某个方法时,根据方法筛选出匹配的 Advisor ,并适配成 Interceptor生成chain链。(生成MethodIntercept链)

6.如果chain.isEmpty()(没有代理逻辑),则调用invokeJoinpointUsingReflection(直接执行 被代理对象的方法)

7.如果有代理逻辑,invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain),将chain 组合成invocation (2.2详解ReflectiveMethodInvocation)

retVal = invocation.proceed();// 执行MethodIntercept逻辑

@Override
	@Nullable
	public Object proceed() throws Throwable {

		// We start with an index of -1 and increment early.
		// currentInterceptorIndex初始值为-1,每调用一个interceptor就会加1
		// 当调用完了 最后一个interceptor后 就会执行被代理方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		// currentInterceptorIndex初始值为-1
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

		// 当前interceptor是InterceptorAndDynamicMethodMatcher,则先进行匹配,匹配成功后再调用该interceptor
		// 如果没有匹配则递归调用proceed()方法,调用下一个interceptor
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			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 {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				// 不匹配则执行下一个MethodInterceptor
				return proceed();
			}
		}
		else {

			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			// 直接调用MethodInterceptor,传入this,在内部会再次调用proceed()方法进行递归
			// 比如MethodBeforeAdviceInterceptor
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

invoke->proceed->invoke->proceed,链路调用实现AOP逻辑

8.结束

2.1getInterceptorsAndDynamicInterceptionAdvice的筛选逻辑

代理对象在执行某个方法时,根据方法筛选出匹配的 Advisor ,并适配成 Interceptor,生成chain链。(生成MethodIntercept链)

核心:所有Advice都会封装成Advisor,最终都会转化为MethodInterceptor

1.advisors = config.getAdvisors()

从ProxyFactory中拿到所设置的Advice(config就是ProxyFactory)

2.遍历每一个Advisor,筛选出符合条件的Advisor

3.先看class是否匹配class匹配通过,再看方法是否匹配(现在只拿MethodMatcher中的matches进行匹配)

4.如果都匹配,执行interceptors = registry.getInterceptors,将Advisor封装成为MethodInterceptor

在这里使用到设配器模式,将advisor中的advice适配成MethodInterceptor(适配的代码如下)

@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<>(3);
    Advice advice = advisor.getAdvice();// 取出Advice
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }
    // 将Advice适配成MethodInterceptor
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    return interceptors.toArray(new MethodInterceptor[0]);
}

三个适配器

  • MethodBeforeAdviceAdapter():before
  • AfterReturningAdviceAdapter(): AfterRurening
  • ThrowsAdviceAdapter(): Throws

注意:afterThrowing限制方法的参数是1个或者4个。部分核心代码如下:

class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof AfterReturningAdvice);
    }

    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
        return new AfterReturningAdviceInterceptor(advice);
    }

}
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {

	private final AfterReturningAdvice advice;


	/**
	 * Create a new AfterReturningAdviceInterceptor for the given advice.
	 * @param advice the AfterReturningAdvice to wrap
	 */
	public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}


	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		Object retVal = mi.proceed();
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		return retVal;
	}

}

4.1.判断isRuntime(),如果runtime为true,再包装成InterceptorAndDynamicMethodMatcher(runtime为true->进行入参匹配),还需要看MethodMatcher中的matches是否可以匹配,如果匹配,才可以执行代理逻辑。

if (match) {
    // 如果匹配则将Advisor封装成为Interceptor,当前Advisor中的Advice可能即是MethodBeforeAdvice,也是ThrowsAdvice
    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
    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.2 ReflectiveMethodInvocation的执行逻辑

1.invocation.proceed

执行 MethodIntercept逻辑

2.invokeJoinpoint

当Interceptor执行完,就执行被代理的方法

3.满足方法的匹配(runtime为true),根据方法参数匹配

如果匹配,调用interceptor.invoke,执行被包装的代理逻辑

如果不匹配,则执行下一个MethodInterceptor,调用proceed()

3.SpringAOP的实现逻辑

核心注解:@EnableAspectJAutoProxy-->@Import(AspectJAutoProxyRegistrar.class)-->registerBeanDefinitions--> registerAspectJAnnotationAutoProxyCreatorIfNecessary-->forceAutoProxyCreatorToUseClassProxying

解释:注册一个 AnnotationAwareAspectJAutoProxyCreator 类型的Bean, beanName为AUTO_PROXY_CREATOR_BEAN_NAME(AnnotationAwareAspectJAutoProxyCreator就是BeanPostProcessor)

forceAutoProxyCreatorToUseClassProxying是将注解中的属性注册到BeanDefinition中,包含proxyTargetClass和exposeProxy。

3.1AOP代理对象的产生时机

当生成Bean的时候,调用doCreateBean-->initializeBean-->applyBeanPostProcessorsAfterInitialization(进入初始化后逻辑),

进行如下判断:拿到所有的BeanPostProcessor,包括AnnotationAwareAspectJAutoProxyCreator,如果存在这个BeanPostProcessor,则走代理逻辑,生成代理对象。

进入postProcessAfterInitialization生成代理对象的逻辑(类AbstractAutoProxyCreator)

(注意:AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator)

AbstractAutoProxyCreator#wrapIfNecessary的代码如下:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }

    // advisedBeans表示已经判断过了的bean,false表示此bean不需要进行Aop
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }

    // 当前正在创建的Bean不用进行AOP,比如切面Bean
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    // 判断当前bean是否存在匹配的advice,如果存在则要生成一个代理对象
    // 此处根据类以及类中的方法去匹配到Interceptor(也就是Advice),然后生成代理对象,代理对象在执行的时候,还会根据当前执行的方法去匹配
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        // advisedBeans记录了某个Bean已经进行过AOP了
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

1.调用wrapIfNecessary

2.判断FALSE.equals(this.advisedBeans.get(cacheKey))

表示已经判断过了的bean,false表示此bean不需要进行AOP

3.判断isInfrastructureClass

表示当前正在创建的Bean不用进行AOP,比如切面Bean、Advice、Pointcut、Advisor、AopInfrastructureBean,同时调用advisedBeans.put(cacheKey, Boolean.FALSE):把不进行AOP的bean放到缓存

4.判断shouldSkip(模版方法)

留给子类扩展,子类可以定义哪些Bean不需要AOP

5.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean

找到所有bean中的Advisor,内容很多,在3.2。

6.如果specificInterceptors!=null,调用

createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)),生成代理对象,调用addAdvisors-->setTargetSource-->proxyFactory.getProxy(classLoader)

在执行的时候还会对Advisor筛选,执行符合不同方法的Advisor(对于一个bean,并不是所有的Adivisor都生效

3.2getAdvicesAndAdvisorsForBean的实现逻辑

判断某个bean是否需要进行AOP,匹配到bean的所有Advisor(第一次匹配)。bean在执行方法的时候还会进行一次匹配,细粒度的匹配(pointcut匹配)(第二次匹配)。(一共两次)

getAdvicesAndAdvisorsForBean(类AbstractAdvisorAutoProxyCreator)-->findEligibleAdvisors(找到合格的Advisors)

1.调用findCandidateAdvisors,找到所有的Advisor

1.1findAdvisorBeans

先找到 所有Advisor类型 的Bean对象.findCandidateAdvisors

具体是:从beanFactory拿到所有Advisor的beanName,找到Advisor的bean添加到list中,然后return

BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors:本方法会被多次调用,因为一个Bean在判断要不要进行AOP时,都会调用这个方法

获取切面类中没有加@Pointcut的方法,进行遍历生成Advisor

ReflectiveAspectJAdvisorFactory#getAdvisorMethods:

private List<Method> getAdvisorMethods(Class<?> aspectClass) {
    List<Method> methods = new ArrayList<>();
    // 拿到切面类中所有没有加@Pointcut的方法
    ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);
    // 对方法进行排序,按注解和方法名字进行排序
    if (methods.size() > 1) {
        methods.sort(adviceMethodComparator);
    }
    return methods;
}

1.2advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()

从所有切面中解析,得到Advisor对象,也就是解析加了@Aspect注解的bean

具体是:找出来Factory中所有bean,然后判断有无@Aspect注解,拿到加了@Aspect注解的bean,factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName),把这个bean封装成BeanFactoryAspectInstanceFactory对象,利用BeanFactoryAspectInstanceFactory来解析Aspect类。

调用getAdvisors,拿到Bean的类型和name,getAdvisorMethods(获取切面类中所有没有加Pointcut注解的方法),methods.sort,先根据Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class的顺序排序,然后根据方法的名字排序。

调用getAdvisor,将方法生成Advisor(拿到所有带Pointcut内容的Advisor),他会调用expressionPointcut = getPointcut,拿到当前方法所对应的Pointcut对象,但是注意:如果当前方法上是这么写的@After("pointcut()"),那么此时得到的Pointcut并没有去解析pointcut()得到对应的表达式,然后调用InstantiationModelAwarePointcutAdvisorImpl,拿到所有带Pointcut内容的Advisor,之后核心调用getAdvice(解析method上面的注解),根据不同的注解生成Advice对象。

2.调用eligibleAdvisors = findAdvisorsThatCanApply,进行筛选,将找到的所有Advisor和userService的方法进行匹配,找到符合userServicce方法的Advisor(利用方法过滤器)

3.sortAdvisors,对Advisor进行排序,按 Ordered接口、@Order 注解进行排序

3.3 AOP的执行逻辑

Advisor在执行的时候,就会进到这个方法getAdvice(类InstantiationModelAwarePointcutAdvisorImpl

getAdvice-->instantiateAdvice-->advice =getAdvice(this.aspectJAdviceMethod,...)

1.getAspectClass

拿到切面类

2.aspectJAnnotation =findAspectJAnnotationOnMethod

拿到当前candidateAdviceMethod方法上的注解信息

3.根据不同的注解生成不同的Advice,然后执行。代码如下:

@Override
@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
                        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    // 拿到当前candidateAdviceMethod方法上的注解信息
    AspectJAnnotation<?> aspectJAnnotation =
    AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    // If we get here, we know we have an AspectJ method.
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                                     "Offending method '" + candidateAdviceMethod + "' in class [" +
                                     candidateAspectClass.getName() + "]");
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Found AspectJ method: " + candidateAdviceMethod);
    }

    AbstractAspectJAdvice springAdvice;

    // 按不同的注解类型得到不同的Advice
    switch (aspectJAnnotation.getAnnotationType()) {
        case AtPointcut:
            if (logger.isDebugEnabled()) {
                logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
            }
            return null;
        case AtAround:
            // @Around
            springAdvice = new AspectJAroundAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
        default:
            throw new UnsupportedOperationException(
                "Unsupported advice type on method: " + candidateAdviceMethod);
    }

    // Now to configure the advice...
    springAdvice.setAspectName(aspectName);
   springAdvice.setDeclarationOrder(declarationOrder);
   String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
   if (argNames != null) {
      springAdvice.setArgumentNamesFromStringArray(argNames);
   }
   springAdvice.calculateArgumentBindings();

   return springAdvice;
}

示例,如果使用After注解,After类的代码如下:核心是invoke方法

public class AspectJAfterAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice, Serializable {

    public AspectJAfterAdvice(
        Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

        super(aspectJBeforeAdviceMethod, pointcut, aif);
    }


    @Override
    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }
        finally {
            invokeAdviceMethod(getJoinPointMatch(), null, null);
        }
    }

    @Override
    public boolean isBeforeAdvice() {
        return false;
    }

    @Override
    public boolean isAfterAdvice() {
        return true;
    }

}

补充:@Around中使用ProceedJoinPoint,其他的注解都是使用JoinPoint。

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

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

相关文章

北京市大兴区启动乐享生活 寻味大兴 美食嘉年华 系列促销费活动

北京市大兴区启动乐享生活 寻味大兴 系列促销费活动 区商务局副局长 兰莉 致开幕辞 区餐饮行业协会会长 董志明 介绍活动内容 2024年9月30日&#xff0c;由大兴区商务局主办、大兴区餐饮行业协会承办&#xff0c;并得到高米店街道和大兴绿地缤纷城大力支持的“乐享生活 寻味大…

快速排序的非递归实现:借助栈实现、借助队列实现

目录 用栈实现快速排序 1.用栈实现非递归快速排序的思路步骤 1.1.思路步骤 2.用栈实现非递归快速排序的代码 3.用栈实现非递归快速排序的整个工程 3.1.QuickSortNonR.h 3.2.QuickSortNonR.c 3.3.Stack.h 3.4.Stack.c 用队列实现非递归快速排序 1.用队列实现非递归快…

进程概念 | 进程状态 | 进程优先级

进程的基本概念 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等。核心观点&#xff1a;担当分配系统资源&#xff08;cpu时间、内存&#xff09;的实体。 资源占用 它占用系统资源向CPU时间&#xff0c;内存等不同进程的资源是相互隔离的&#xff0c;确…

Linux中swap分区

swap 分区的创建方法演示&#xff1a; 方法1&#xff1a;fdisk &#xff08;首先查看 fdisk /dev/sda 中 swapID。由于 Linux 的 fdisk 默认会将分区的 ID 设置为 Linux 的文件系统&#xff0c;所以需要设置一下 systemID&#xff09; 解决方法 重启或者执行命令 第二个交换分…

主机可以ping通linux虚拟机但linux虚拟机无法ping通主机的解决办法

出现这个问题一般是由于物理主机的防火墙挡住了ping包&#xff0c;可以试试看把主机的防火墙关闭看看 如果可以ping通的话那么试试看添加规则将虚拟机ip添加进去 高级设置&#xff0c;入站规则&#xff0c;新建规则 下列ip地址添加虚拟机的ip地址&#xff08;使用桥接网络&…

修改Anaconda虚拟环境默认安装路径(Linux系统)

文章目录 修改Anaconda虚拟环境默认安装路径(Linux系统)1.方法一&#xff1a;使用--prefix参数2.方法二&#xff1a;配置conda环境的默认安装位置 修改Anaconda虚拟环境默认安装路径(Linux系统) 1.方法一&#xff1a;使用--prefix参数 在创建虚拟环境时&#xff0c;使用--pre…

Vue3 中Ref的最佳实践

在vue3中如果我们需要获取一个响应式的变量&#xff0c;可以使用ref来定义一个变量。 const name ref( "" );name.value "test" 定义好后&#xff0c;就可以实现修改状态&#xff0c;更新UI的效果了。 在这个基础上&#xff0c;本文主要讨论跨组件时如何…

基于STM32的智能风扇控制系统设计

引言 本项目将基于STM32微控制器设计一个智能风扇控制系统&#xff0c;通过温度传感器实时检测环境温度&#xff0c;并根据预设的温度范围自动调节风扇的转速。该系统展示了STM32的PWM输出、传感器接口以及自动控制应用的实现。 环境准备 1. 硬件设备 STM32F103C8T6 开发板…

Python 语言学习——应用1.1 数字图像处理(第一节,颜色)

目录 1.基础知识 2.实战演示 1.基础知识&#xff1a; 1.图像的表示. 函数表示&#xff1a;图像是二维信号&#xff0c;定义为二维函数f(x,y)&#xff0c;其中&#xff0c;x、y是空间坐标&#xff0c;f(x,y)是点(x,y)的幅值。拓展看&#xff0c;视频&#xff0c;又称动态图像…

SOMEIP_ETS_166: SD_TestFieldUINT8

测试目的&#xff1a; 验证DUT能够通过Getter和Setter方法正确地发送和接收TestFieldUINT8字段的值。 描述 本测试用例旨在确保DUT的ETS能够响应Tester的请求&#xff0c;正确地使用Getter方法获取TestFieldUINT8的值&#xff0c;以及使用Setter方法设置新的值。 测试拓扑&…

QGIS中怎么加载数据(如矢量shp与栅格数据)

最近有不少初学者来问我qgis里怎么加载数据 这个与arcgis中的操作其实也是类似的&#xff0c;也是通过软件的里面&#xff0b;号就行了 下面是我对这个问题的解决思路&#xff1a; 一种是直接把图层文件拖进去&#xff0c;但是这种方法很有局限性&#xff0c;下面我还说明一…

JavaWeb的小结02

第2章-第2节 一、知识点 HttpServletRequest请求对象、HttpServletResponse响应对象、响应内容类型Content-Type、请求转发、重定向、ServletContext对象。 二、目标 深刻理解HttpServletRequest对象的作用。 深刻理解HttpServletResponse对象的作用。 掌握HttpServletRequ…

什么是请求转发?

请求转发 解释 请求转发,将前端发送的请求转发到别的资源 别的资源是指: servlet,页面 即: 请求转发,可以将请求转发值另外一个servlet;也可以是将请求转发至页面 1、 请求转发演示 1.1 请求转发跳转页面 实战: 之前注册练习,修改: 实现注册完跳转到登录页面 1.2 请求转发…

【Matlab案例】imageJ + matlab 实现物体轨迹追踪及路径彩色上色

我们经常看到一些文献中对细胞或者粒子的运动轨迹进行上色&#xff0c;不同的颜色对应着不同的时间。一纯色的轨迹实现起来很方便&#xff0c;彩色的轨迹如何实现呢&#xff1f;本文使用imageJ获取轨迹数据&#xff0c;使用matlab对轨迹进行上色。结果如下&#xff1a; 1. im…

Java | Leetcode Java题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution {public boolean circularArrayLoop(int[] nums) {int n nums.length;for (int i 0; i < n; i) {if (nums[i] 0) {continue;}int slow i, fast next(nums, i);// 判断非零且方向相同while (nums[slow] * nums[fast]…

Python爬虫(二)--http基本原理(Python Crawler (2) Basic Principles of HTTP)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

TriLite完成A轮扩展融资:加速AR微型投影仪技术创新与市场拓展

近日,全球领先的AR微型投影仪开发商TriLite宣布成功完成A轮扩展融资,将A轮融资总额提升至超过2000万欧元。这一轮融资不仅彰显了资本市场对TriLite技术实力和市场潜力的高度认可,更为其后续在AR微型投影仪领域的技术研发、产品迭代以及市场拓展提供了坚实的资金保障。以下是…

力扣刷题 | 两数之和

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 给定一个整数数组 nums 和…

C++ | Leetcode C++题解之第456题132模式

题目&#xff1a; 题解&#xff1a; class Solution { public:bool find132pattern(vector<int>& nums) {int n nums.size();vector<int> candidate_i {nums[0]};vector<int> candidate_j {nums[0]};for (int k 1; k < n; k) {auto it_i upper_…

Leetcode—416. 分割等和子集【中等】

2024每日刷题&#xff08;172&#xff09; Leetcode—416. 分割等和子集 C实现代码 class Solution { public:bool canPartition(vector<int>& nums) {int sum accumulate(nums.begin(), nums.end(), 0);if(sum % 2) {return false;}int m nums.size();int subSu…