Spring-AOP核心源码、原理详解前篇
本文主要分4部分
- Aop原理介绍
- 介绍aop相关的一些类
- 通过源码详解aop代理的创建过程
- 通过源码详解aop代理的调用过程
- Aop代理一些特性的使用案例
Spring AOP原理
原理比较简单,主要就是使用jdk动态代理和cglib代理来创建代理对象,通过代理对象来访问目标对象,而代理对象中融入了增强的代码,最终起到对目标对象增强的效果。
aop相关的一些类
- 连接点(JoinPoint)相关类
- 通知(Advice)相关的类
- 切入点(Pointcut)相关的类
- 切面(Advisor)相关的类
连接点(JoinPoint)相关类
JoinPoint接口
这个接口表示一个通用的运行时连接点(在AOP术语中)
import java.lang.reflect.AccessibleObject;public interface Joinpoint {/*** 转到拦截器链中的下一个拦截器* @return* @throws Throwable*/Object proceed() throws Throwable;/*** 返回保存当前连接点静态部分【的对象】,这里一般指被代理的目标对象* @return*/Object getThis();/*** 返回此静态连接点 一般就为当前的Method(至少目前的唯一实现是MethodInvocation,所以连接点得静态部分肯定就是本方法)* @return*/AccessibleObject getStaticPart();
}
几个重要的子接口和实现类,如下:
Invocation接口
此接口表示程序中的调用,调用是一个连接点,可以被拦截器拦截。
package org.aopalliance.intercept;public interface Invocation extends Joinpoint {/*** 将参数作为数组对象获取,可以更改此数组中的元素值以更改参数。* 通常用来获取调用目标方法的参数*/Object[] getArguments();
}
MethodInvocation接口
用来表示连接点中方法的调用,可以获取调用过程中的目标方法。
package org.aopalliance.intercept;
import java.lang.reflect.Method;/*** 方法调用的描述,在方法调用时提供给拦截器。* 方法调用是一个连接点,可以被方法拦截器拦截。*/
public interface MethodInvocation extends Invocation {/*** 返回正在被调用得方法~~~ 返回的是当前Method对象。* 此时,效果同父类的AccessibleObject getStaticPart() 这个方法*/Method getMethod();
}
ProxyMethodInvocation接口
表示代理方法的调用
public interface ProxyMethodInvocation extends MethodInvocation {/*** 获取被调用的代理对象* @return*/Object getProxy();/*** 克隆一个方法调用器MethodInvocation* @return*/MethodInvocation invocableClone();/*** 克隆一个方法调用器MethodInvocation,并为方法调用器指定参数* @param arguments* @return*/MethodInvocation invocableClone(Object... arguments);/*** 设置要用于此链中任何通知的后续调用的参数* @param arguments*/void setArguments(Object... arguments);/*** 添加一些扩展用户属性,这些属性不在AOP框架内使用。它们只是作为调用对象的一部分保留,用于* 特殊的拦截器* @param key* @param value*/void setUserAttribute(String key, @Nullable Object value);/*** 根据key获取对应的用户属性* @param key* @return*/@NullableObject getUserAttribute(String key);
}
通俗点理解:连接点表示方法的调用过程,内部包含了方法调用过程中的所有信息,比如被调用的方法、目标、代理对象、执行拦截器链等信息。
上面定义都是一些接口,最终有2个实现。
ReflectiveMethodInvocation
当代理对象是采用jdk动态代理创建的,通过代理对象来访问目标对象的方法的时,最终过程是由ReflectiveMethodInvocation来处理的,内部会通过递归调用方法拦截器,最终会调用到目标方法。
CglibMethodInvocation
功能和上面的类似,当代理对象是采用cglib创建的,通过代理对象来访问目标对象的方法的时,最终过程是由CglibMethodInvocation来处理的,内部会通过递归调用方法拦截器,最终会调用到目标方法。
通知相关的类
通知用来定义需要增强的逻辑。
Advice接口
通知的底层接口,只是一个标记作用
package org.aopalliance.aop;public interface Advice {
}
BeforeAdvice接口
方法前置通知,内部空的,也就是方法前置增强
package org.springframework.aop;
import org.aopalliance.aop.Advice;public interface BeforeAdvice extends Advice {
}
Interceptor接口
此接口表示通用拦截器
package org.aopalliance.intercept;
public interface Interceptor extends Advice {
}
MethodInterceptor接口
方法拦截器,所有的通知均需要转换为MethodInterceptor类型的,最终多个MethodInterceptor组成一个方法拦截器连
package org.aopalliance.intercept;@FunctionalInterface
public interface MethodInterceptor extends Interceptor {/*** 拦截目标方法的执行,可以在这个方法内部实现需要增强的逻辑,以及主动调用目标方法* @param methodInvocation* @return* @throws Throwable*/Object invoke(MethodInvocation methodInvocation) throws Throwable;
}
AfterAdvice接口
后置通知的公共标记接口,也就是后置增强接口
package org.springframework.aop;
import org.aopalliance.aop.Advice;public interface AfterAdvice extends Advice {
}
MethodBeforeAdvice接口
方法执行前通知,需要在目标方法执行前执行一些逻辑的,可以通过这个实现。
通俗点说:需要在目标方法执行之前增强一些逻辑,可以通过这个接口来实现。before方法:在调用给定方法之前回调。
package org.springframework.aop;import java.lang.reflect.Method;
import org.springframework.lang.Nullable;public interface MethodBeforeAdvice extends BeforeAdvice {/*** 调用目标方法之前会先调用这个before方法* @param method 需要执行的目标方法* @param args 目标方法的参数* @param target 目标对象* @throws Throwable*/void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
如同
public Object invoke(){// 调用MethodBeforeAdvice#before方法return 调用目标方法;
}
AfterReturningAdvice接口
方法执行后通知,需要在目标方法执行之后执行增强一些逻辑的,可以通过这个实现。
不过需要注意一点:目标方法正常执行后,才会回调这个接口,当目标方法有异常,那么这通知会被跳过。
package org.springframework.aop;
public interface AfterReturningAdvice extends AfterAdvice {/*** 目标方法执行之后会回调这个方法* method:需要执行的目标方法* args:目标方法的参数* target:目标对象*/void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
ThrowsAdvice接口
异常增强标记接口
package org.springframework.aop;public interface ThrowsAdvice extends AfterAdvice {
}
此接口上没有任何方法,因为方法由反射调用,实现类必须实现以下形式的方法,前3个参数是可选的,最后一个参数为需要匹配的异常的类型。
void afterThrowing([Method, args, target], ThrowableSubclass);
通知包装器
负责将各种非MethodInterceptor类型的通知(Advice)包装为MethodInterceptor类型。
刚才有说过:Aop中所有的Advice最终都会转换为MethodInterceptor类型的,组成一个方法调用链,然后执行。
3个包装器类
- MethodBeforeAdviceInterceptor
- AfterReturningAdviceInterceptor
- ThrowsAdviceInterceptor
MethodBeforeAdviceInterceptor类
这个类实现了 MethodInterceptor 接口,负责将 MethodBeforeAdvice 方法前置通知包装为MethodInterceptor 类型,创建这个类型的对象的时候需要传递一个MethodBeforeAdvice 类型的参数,重点是 invoke 方法。
package org.springframework.aop.framework.adapter;import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.util.Assert;public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {private final MethodBeforeAdvice advice;public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}public Object invoke(MethodInvocation mi) throws Throwable {//负责调用前置通知的方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());//继续执行方法调用链return mi.proceed();}
}
AfterReturningAdviceInterceptor类
这个类实现了 MethodInterceptor 接口,负责将 AfterReturningAdvice 方法后置通知包装为MethodInterceptor 类型,创建这个类型的对象的时候需要传递一个AfterReturningAdvice 类型的参数,重点是 invoke 方法
package org.springframework.aop.framework.adapter;import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.util.Assert;public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {private final AfterReturningAdvice advice;public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}public Object invoke(MethodInvocation mi) throws Throwable {//先执行方法调用链,可以获取目标方法的执行结果Object retVal = mi.proceed();//执行后置通知this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());//返回结果return retVal;}
}
ThrowsAdviceInterceptor类
这个类实现了 MethodInterceptor 接口,负责将 ThrowsAdvice 异常通知包装为 MethodInterceptor类型,创建这个类型的对象的时候需要传递一个 Object 类型的参数,通常这个参数是 ThrowsAdvice类型的,重点是 invoke 方法
package org.springframework.aop.framework.adapter;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterAdvice;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {private static final String AFTER_THROWING = "afterThrowing";private static final Log logger = LogFactory.getLog(org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.class);private final Object throwsAdvice;private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();public ThrowsAdviceInterceptor(Object throwsAdvice) {Assert.notNull(throwsAdvice, "Advice must not be null");this.throwsAdvice = throwsAdvice;//获取异常通知中定义的所有方法(public、默认的、protected、private)Method[] methods = throwsAdvice.getClass().getMethods();int length = methods.length;//轮询methodsfor (Method method : methods) {//方法名称为afterThrowing && 方法参数为1或者4if (method.getName().equals(AFTER_THROWING) && (method.getParameterCount() == 1 || method.getParameterCount() == 4)) {//获取方法的最后一个参数类型Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];//判断方法参数类型是不是Throwable类型的if (Throwable.class.isAssignableFrom(throwableParam)) {//缓存异常处理方法到map中(异常类型->异常处理方法)this.exceptionHandlerMap.put(throwableParam, method);}}}//如果exceptionHandlerMap是空,抛出异常,所以最少要有一个异常处理方法if (this.exceptionHandlerMap.isEmpty()) {throw new IllegalArgumentException("At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");}}/*** @return 获取异常通知中自定义的处理异常方法的数量*/public int getHandlerMethodCount() {return this.exceptionHandlerMap.size();}public Object invoke(MethodInvocation mi) throws Throwable {try {//调用通知链return mi.proceed();} catch (Throwable e) {//获取异常通知中自定义的处理异常的方法Method handlerMethod = this.getExceptionHandler(e);//当处理的方法不为空if (handlerMethod != null) {this.invokeHandlerMethod(mi, e, handlerMethod);}//继续向外抛出异常throw e; //@1}}/*** @param exception 获取throwsAdvice中处理exception参数指定的异常的方法* @return*/@Nullableprivate Method getExceptionHandler(Throwable exception) {//获取异常类型Class<?> exceptionClass = exception.getClass();//从缓存中获取异常类型对应的方法Method handler = this.exceptionHandlerMap.get(exceptionClass);//来一个循环,查询处理方法,循环条件:方法为空 && 异常类型!=Throwablewhile (handler == null && exceptionClass != Throwable.class) {//获取异常的父类型exceptionClass = exceptionClass.getSuperclass();//从缓存中查找异常对应的处理方法handler = this.exceptionHandlerMap.get(exceptionClass);}//将查找结果返回return handler;}/*** 通过反射调用异常通知中的异常方法* @param mi* @param ex* @param method* @throws Throwable*/private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {//构建方法请求参数Object[] handlerArgs;//若只有1个参数,参数为:异常对象if (method.getParameterCount() == 1) {handlerArgs = new Object[]{ex};} else {//4个参数(方法、方法请求参数、目标对象、异常对象)handlerArgs = new Object[]{mi.getMethod(), mi.getArguments(), mi.getThis(), ex};}try {//通过反射调用异常通知中的方法method.invoke(this.throwsAdvice, handlerArgs);} catch (InvocationTargetException tx) {throw tx.getTargetException();}}
}
从上面可以看出,异常通知,自定义处理异常的方法有几个特点
- 方法名称必须为 afterThrowing
- 方法参数必须1个或4个,最后一个参数是 Throwable 类型或其子类型
- 可以在异常处理中记录一些异常信息,这个还是比较有用的,但是注意一点目标方法抛出的异常最后还是会向外继续抛出 @1
切入点(PointCut)相关类
通知(Advice)用来指定需要增强的逻辑,但是哪些类的哪些方法中需要使用这些通知呢?这个就是通过切入点来配置的。
public interface Pointcut {//匹配所有对象的 Pointcut,内部的2个过滤器默认都会返回truePointcut TRUE = TruePointcut.INSTANCE;/*** 类过滤器, 可以知道哪些类需要拦截* @return*/ClassFilter getClassFilter();/*** 方法匹配器, 可以知道哪些方法需要拦截* @return*/MethodMatcher getMethodMatcher();
}
ClassFilter接口
类过滤器
@FunctionalInterface
public interface ClassFilter {ClassFilter TRUE = TrueClassFilter.INSTANCE;boolean matches(Class<?> var1);
}
MethodMatcher接口
方法过滤器
package org.springframework.aop;
import java.lang.reflect.Method;public interface MethodMatcher {//匹配所有方法,这个内部的2个matches方法任何时候都返回trueorg.springframework.aop.MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;/*** 执行静态检查给定方法是否匹配* @param method 目标方法* @param targetClass 目标对象类型* @return 是否通过*/boolean matches(Method method, Class<?> targetClass);/**** @return 是否是动态匹配,即是否每次执行目标方法的时候都去验证一下*/boolean isRuntime();/*** 动态匹配验证的方法,比第一个matches方法多了一个参数args,这个参数是调用目标方法传入的参数* @param method 目标方法* @param targetClass 目标对象类型* @param args 方法参数* @return 是否通过*/boolean matches(Method method, Class<?> targetClass, Object... args);
}
其实从字面意思我们都可以看出来,isRuntime这个标记是判断要不要根据方法参数动态的拦击。所以这个是第二层判断而已。
顾问(Advisor) 也就是切面
通知定义了需要做什么,切入点定义了在哪些类的哪些方法中执行通知,那么需要将他们2个组合起来才有效啊。
顾问(Advisor)就是做这个事情的。
在spring aop中,你可以将advisor理解为切面,切面中通常有2个关键信息:
- 需要增强的目标方法列表,这个通过切入点(Pointcut)来指定
- 需要在目标方法中增强的逻辑,这个通过(Advice)通知来指定
package org.springframework.aop;import org.aopalliance.aop.Advice;/*** 包含AOP通知(在joinpoint处执行的操作)和确定通知适用性的过滤器(如切入点[PointCut])的基* 本接口。*/
public interface Advisor {Advice EMPTY_ADVICE = new Advice() {};/*** * @return 返回引用的通知*/Advice getAdvice();boolean isPerInstance();
}
上面这个接口通常不会直接使用,这个接口有2个子接口,通常我们会和这2个子接口来打交道,下面看一下这2个子接口
PointcutAdvisor接口
通过名字就能看出来,这个和Pointcut有关,内部有个方法用来获取 Pointcut ,AOP使用到的大部分Advisor都属于这种类型的。
在目标方法中实现各种增强功能基本上都是通过PointcutAdvisor来实现的。
package org.springframework.aop;public interface PointcutAdvisor extends Advisor {/*** * @return 获取顾问中使用的切入点*/Pointcut getPointcut();
}
DefaultPointcutAdvisor类
PointcutAdvisor的默认实现,这是最常用的Advisor实现,它可以用于任何Pointcut和Advice类型,代码相当简单,里面定义了2个属性:pointcut和advisor,由使用者指定。
IntroductionAdvisor接口
这个接口,估计大家比较陌生,干什么的呢?
一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。可以通过IntroductionAdvisor给目标类引入更多接口的功能,这个功能是不是非常牛逼。
下面开始2个重点工作。
- 通过源码介绍aop中代理创建过程
- 通过源码介绍代理方法的调用执行过程
创建代理3大步骤
- 创建代理所需参数配置
- 根据代理参数获取AopProxy对象
- 通过AopProxy获取代理对象
创建代理所需参数配置
创建代理所需参数配置主要是通过 AdvisedSupport 这个类来做的,看一下类图,下面一个个来介绍。
TargetClassAware接口
比较简单的一个接口,定义了一个方法,用来获取目标对象类型。
所谓目标对象:就是被代理对象。
package org.springframework.aop;
public interface TargetClassAware {@NullableClass<?> getTargetClass();
}
ProxyConfig类
这个类比较关键了,代理配置类,内部包含了创建代理时需要配置的各种参数。
package org.springframework.aop.framework;import java.io.Serializable;
import org.springframework.util.Assert;/*** 对外提供统一的代理参数配置类,以确保所有代理创建程序具有一致的属性*/
public class ProxyConfig implements Serializable {private static final long serialVersionUID = -8409359707199703185L;//标记是否直接对目标类进行代理,而不是通过接口产生代理private boolean proxyTargetClass = false;/*** 标记是否对代理进行优化。启动优化通常意味着在代理对象被创建后,增强的修改将不会生效,因此* 默认值为false,* todo 如果exposeProxy设置为true,即使optimize为true也会被忽略。*/private boolean optimize = false;//标记是否需要阻止通过该配置创建的代理对象转换为Advised类型,默认值为false,表示代理对象//可以被转换为Advised类型boolean opaque = false;/*** 标记代理对象是否应该被aop框架通过AopContext以ThreadLocal的形式暴露出去。* 当一个代理对象需要调用它自己的另外一个代理方法时,这个属性将非常有用。默认是是false,以* 避免不必要的拦截。*/boolean exposeProxy = false;/*** 标记该配置是否需要被冻结,如果被冻结,将不可以修改增强的配置。* 当我们不希望调用方修改转换成Advised对象之后的代理对象时,这个配置将非常有用。*/private boolean frozen = false;//...省略了属性的get set方法}
Advised接口
这个接口中定义了操作Aop代理配置的各种方法(比如指定被代理的目标对象、添加通知、添加顾问等等)。
所有由spring aop创建的代理对象默认都会实现这个接口。
package org.springframework.aop.framework;import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.TargetClassAware;
import org.springframework.aop.TargetSource;public interface Advised extends TargetClassAware {/**** @return 返回配置是否已冻结,被冻结之后,无法修改已创建好的代理对象中的通知*/boolean isFrozen();/*** 是否对目标类直接创建代理,而不是对接口创建代理,通俗点讲:* @return 如果是通过cglib创建代理,此方法返回true,否则返回false*/boolean isProxyTargetClass();/**** @return 获取配置中需要代理的接口列表*/Class<?>[] getProxiedInterfaces();/**** @param intf* @return 判断某个接口是否被代理*/boolean isInterfaceProxied(Class<?> intf);/*** 设置被代理的目标源,创建代理的时候,通常需要传入被代理的对象,最终被代理的对象会被包装为* TargetSource类型的* @param targetSource*/void setTargetSource(TargetSource targetSource);TargetSource getTargetSource();void setExposeProxy(boolean var1);/*** 设置是否需要将代理暴露在ThreadLocal中,这样可以在线程中获取到被代理对象,这个配置挺有* 用的,稍后会举例说明使用场景* @return isExposeProxy*/boolean isExposeProxy();/*** 设置此代理配置是否经过预筛选,以便它只包含适用的顾问(匹配此代理的目标类)。* 默认设置是“假”。如果已经对advisor进行了预先筛选,则将其设置为“true”* 这意味着在为代理调用构建实际的advisor链时可以跳过ClassFilter检查。* @param preFiltered*/void setPreFiltered(boolean preFiltered);boolean isPreFiltered();//返回代理配置中干掉所有Advisor列表Advisor[] getAdvisors();//添加一个Advisorvoid addAdvisor(Advisor advisor) throws AopConfigException;//指定的位置添加一个Advisorvoid addAdvisor(int index, Advisor advisor) throws AopConfigException;//移除一个Advisorboolean removeAdvisor(Advisor advisor);//移除指定位置的Advisorvoid removeAdvisor(int index) throws AopConfigException;//查找某个Advisor的位置int indexOf(Advisor advisor);//对advisor列表中的a替换为bboolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;//添加一个通知void addAdvice(Advice advice) throws AopConfigException;//向指定的位置添加一个通知void addAdvice(int index, Advice advice) throws AopConfigException;//移除一个通知boolean removeAdvice(Advice advice);//获取通知的位置int indexOf(Advice advice);//将代理配置转换为字符串,这个方便排错和调试使用的String toProxyConfigString();
}
AdvisedSupport类
这个类是个重点,AOP代理配置管理器的基类,继承了 ProxyConfig 并且实现了 Advised 接口,创建 aop代理之前,所有需要配置的信息都是通过这个类来操作的。
比如:设置是否为目标类创建代理、设置目标对象、配置通知列表等等
import lombok.Getter;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AdvisorChainFactory;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/***** 上面几个类有几个结论,这里说一下。* 1. 配置中添加的Advice对象最终都会被转换为DefaultPointcutAdvisor对象,此时* DefaultPointcutAdvisor未指定pointcut,大家可以去看一下DefaultPointcutAdvisor中pointcut有* 个默认值,默认会匹配任意类的任意方法。* 2. 当配置被冻结的时候,即frozen为true的时,此时配置中的Advisor列表是不允许修改的。* 3. 上面的 getInterceptorsAndDynamicInterceptionAdvice 方法,通过代理调用目标方法的时* 候,最后需要通过方法和目标类的类型,从当前配置中会获取匹配的方法拦截器列表,获取方法拦* 截器列表是由 AdvisorChainFactory 负责的。* getInterceptorsAndDynamicInterceptionAdvice 会在调用代理的方法时会执行,稍后在执行* 阶段会详解。* 4. 目标方法和其关联的方法拦截器列表会被缓存在 methodCache 中,当顾问列表有变化的时候,* methodCache 缓存会被清除。* @description: 自己的代理类** @author: stone* @date: Created by 2021/6/3 16:55* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.aop.principle*/
public class AdvisedSupport extends ProxyConfig implements Advised {public static final TargetSource EMPTY_TARGET_SOURCE =EmptyTargetSource.INSTANCE;public TargetSource targetSource = EMPTY_TARGET_SOURCE;/*** 建议器是否已经针对特定的目标类进行筛选*/private boolean preFiltered = false;/*** 调用链工厂,用来获取目标方法的调用链*/@Getterpublic AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();/*** 方法调用链缓存:以方法为键,以顾问链表为值的缓存。*/private transient Map<MethodCacheKey, List<Object>> methodCache;//代理对象需要实现的接口列表。保存在列表中以保持注册的顺序,以创建具有指定接口顺序的JDK代理。private List<Class<?>> interfaces = new ArrayList<>();//配置的顾问列表。所有添加的Advise对象都会被包装为Advisor对象private List<Advisor> advisors = new ArrayList<>();//数组更新了对advisor列表的更改,这更容易在内部操作。private Advisor[] advisorArray = new Advisor[0];//无参构造方法public AdvisedSupport() {this.methodCache = new ConcurrentHashMap<>(32);}//有参构造方法,参数为:代理需要实现的接口列表public AdvisedSupport(Class<?>... interfaces) {this();this.setInterfaces(interfaces);}//设置目标对象public void setTarget(Object target) {this.setTargetSource(new SingletonTargetSource(target));}//todo 此方法先忽略,用来为目标类引入接口的private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {advisor.validateInterfaces();// If the advisor passed validation, we can make the change.Class<?>[] ifcs = advisor.getInterfaces();for (Class<?> ifc : ifcs) {addInterface(ifc);}}//指定的位置添加顾问private void addAdvisorInternal(int pos, Advisor advisor) throwsAopConfigException {Assert.notNull(advisor, "Advisor must not be null");if (isFrozen()) {throw new AopConfigException("Cannot add advisor: Configuration is frozen.");}if (pos > this.advisors.size()) {throw new IllegalArgumentException("Illegal position " + pos + " in advisor list with size " +this.advisors.size());}this.advisors.add(pos, advisor);updateAdvisorArray();adviceChanged();}//将advisorArray和advisors保持一致protected final void updateAdvisorArray() {this.advisorArray = this.advisors.toArray(new Advisor[0]);}//获取顾问列表protected final List<Advisor> getAdvisorsInternal() {return this.advisors;}//设置被代理的目标类public void setTargetClass(Class<?> targetClass) {this.targetSource = EmptyTargetSource.forClass(targetClass);}//获取被代理的目标类型@Overridepublic Class<?> getTargetClass() {return this.targetSource.getTargetClass();}//设置代理对象需要实现的接口public void setInterfaces(Class<?>... interfaces) {Assert.notNull(interfaces, "Interfaces must not be null");this.interfaces.clear();for (Class<?> ifc : interfaces) {addInterface(ifc);}}/*** 为代理对象添加需要实现的接口** @param intf*/public void addInterface(Class<?> intf) {Assert.notNull(intf, "Interface must not be null");if (!intf.isInterface()) {throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");} else {if (!this.interfaces.contains(intf)) {this.interfaces.add(intf);this.adviceChanged();}}}/*** 通知更改时调用,会清空当前方法调用链缓存*/protected void adviceChanged() {this.methodCache.clear();}/*** * 设置顾问链工厂,当调用目标方法的时候,需要获取这个方法上匹配的Advisor列表,* 获取目标方法上匹配的Advisor列表的功能就是AdvisorChainFactory来负责的*/public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) {Assert.notNull(advisorChainFactory, "advisorChainFactory is must not null");this.advisorChainFactory = advisorChainFactory;}//移除代理对象需要实现的接口public boolean removeInterface(Class<?> intf) {return this.interfaces.remove(intf);}/*** 获取代理对象需要实现的接口列表** @return*/@Overridepublic Class<?>[] getProxiedInterfaces() {return ClassUtils.toClassArray(this.interfaces);}/*** 判断代理对象是否需要实现某个接口** @param aClass* @return*/@Overridepublic boolean isInterfaceProxied(Class<?> aClass) {for (Class<?> proxyIntf : this.interfaces) {if (aClass.isAssignableFrom(proxyIntf)) {return true;}}return false;}@Overridepublic void setTargetSource(@Nullable TargetSource targetSource) {this.targetSource = (targetSource == null ? EMPTY_TARGET_SOURCE : targetSource);}//获取被代理的目标源@Overridepublic TargetSource getTargetSource() {return this.targetSource;}/*** todo* 设置此代理配置是否经过预筛选,这个什么意思呢:通过目标方法调用代理的时候,* 需要通过匹配的方式获取这个方法上的调用链列表,查找过程需要2个步骤:* 第一步:类是否匹配,第二步:方法是否匹配,当这个属性为true的时候,会直接跳过第一步,这个* 懂了不*/@Overridepublic void setPreFiltered(boolean preFiltered) {this.preFiltered = preFiltered;}// 返回preFiltered@Overridepublic boolean isPreFiltered() {return this.preFiltered;}/*** 获取所有的顾问列表** @return*/@Overridepublic Advisor[] getAdvisors() {return this.advisorArray;}/*** 添加顾问** @param advisor* @throws AopConfigException*/@Overridepublic void addAdvisor(Advisor advisor) throws AopConfigException {int pos = this.advisors.size();this.addAdvisor(pos, advisor);}/*** 指定的位置添加顾问** @param pos* @param advisor* @throws AopConfigException*/@Overridepublic void addAdvisor(int pos, Advisor advisor) throws AopConfigException {// todo 这块先忽略,以后讲解if (advisor instanceof IntroductionAdvisor) {validateIntroductionAdvisor((IntroductionAdvisor) advisor);}addAdvisorInternal(pos, advisor);}@Overridepublic boolean removeAdvisor(Advisor advisor) {int index = indexOf(advisor);if (index == -1) {return false;}removeAdvisor(index);return true;}@Overridepublic void removeAdvisor(int index) throws AopConfigException {//当配置如果是冻结状态,是不允许对顾问进行修改的,否则会抛出异常if (isFrozen()) {throw new AopConfigException("Cannot remove Advisor: Configuration is frozen.");}if (index < 0 || index > this.advisors.size() - 1) {throw new AopConfigException("Advisor index " + index + " is out of bounds:" +"This configuration only has " + this.advisors.size() + " advisors. ");}//移除advisors中的顾问Advisor advisor = this.advisors.remove(index);if (advisor instanceof IntroductionAdvisor) {IntroductionAdvisor ia = (IntroductionAdvisor) advisor;// We need to remove introduction interfaces.for (Class<?> ifc : ia.getInterfaces()) {removeInterface(ifc);}}//更新advisorArrayupdateAdvisorArray();//通知已改变,内部会清除方法调用链缓存信息。adviceChanged();}@Overridepublic int indexOf(Advisor advisor) {Assert.notNull(advisor, "Advisor must not be null");return this.advisors.indexOf(advisor);}@Overridepublic boolean replaceAdvisor(Advisor replaceAdvisor, Advisor targetAdvisor) throws AopConfigException {Assert.notNull(replaceAdvisor, "Advisor a must not be null");Assert.notNull(targetAdvisor, "Advisor b must not be null");int index = indexOf(replaceAdvisor);if (index == -1) {return false;}removeAdvisor(index);addAdvisor(index, targetAdvisor);return true;}/*** 添加通知* @param advice* @throws AopConfigException*/@Overridepublic void addAdvice(Advice advice) throws AopConfigException {int pos = this.advisors.size();this.addAdvice(pos, advice);}/*** 添加指定位置的通知* @param index* @param advice* @throws AopConfigException*/@Overridepublic void addAdvice(int index, Advice advice) throws AopConfigException {//此处会将advice通知包装为DefaultPointcutAdvisor类型的AdvisoraddAdvisor(index, new DefaultPointcutAdvisor(advice));}/*** 移除通知* @param advice* @return*/@Overridepublic boolean removeAdvice(Advice advice) {int index = indexOf(advice);if (index == -1) {return false;}removeAdvisor(index);return true;}/**** @param advice* @return*/@Overridepublic int indexOf(Advice advice) {Assert.notNull(advice, "Advice must not be null");for (int i = 0; i < this.advisors.size(); i++) {Advisor advisor = this.advisors.get(i);if (advisor.getAdvice() == advice) {return i;}}return -1;}//是否包含某个通知public boolean adviceIncluded(@Nullable Advice advice) {if (advice != null) {for (Advisor advisor : this.advisors) {if (advisor.getAdvice() == advice) {return true;}}}return false;}/*** 获取某种类型的通知数量* @param adviceClass* @return*/public int countAdvicesOfType(Class<?> adviceClass) {int count = 0;if (adviceClass == null) {return count;}long count1 = this.advisors.stream().filter(e -> adviceClass.isInstance(e.getAdvice())).count();count = (int) count1;return count;}/*** 基于当前配置,获取给定方法的方法调用链列表(即* org.aopalliance.intercept.MethodInterceptor对象列表)** @param method* @param targetClass* @return*/public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {MethodCacheKey methodCacheKey = new MethodCacheKey(method);
// List<Object> objects = this.methodCache.get(methodCacheKey);
// if (objects == null) {
// objects = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
// this, method, targetClass);
// this.methodCache.put(methodCacheKey, objects);
// }return this.methodCache.computeIfAbsent(methodCacheKey, m -> this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass));}@Overridepublic String toProxyConfigString() {return this.toString();}/*** 将other中的配置信息复制到当前对象中* @param other* @param targetSource* @param advisors*/protected void copyConfigurationFrom(MyAdvisedSupport other, TargetSourcetargetSource, List<Advisor> advisors) {copyFrom(other);this.targetSource = targetSource;this.advisorChainFactory = other.advisorChainFactory;this.interfaces = new ArrayList<>(other.interfaces);for (Advisor advisor : advisors) {if (advisor instanceof IntroductionAdvisor) {validateIntroductionAdvisor((IntroductionAdvisor) advisor);}Assert.notNull(advisor, "Advisor must not be null");this.advisors.add(advisor);}updateAdvisorArray();adviceChanged();}//构建此AdvisedSupport的仅配置副本,替换TargetSource。/*** 构建此AdvisedSupport的仅配置副本,替换TargetSource。* @return*/MyAdvisedSupport getConfigurationOnlyCopy() {MyAdvisedSupport copy = new MyAdvisedSupport();copy.copyFrom(this);copy.targetSource = EmptyTargetSource.forClass(getTargetClass(),getTargetSource().isStatic());copy.advisorChainFactory = this.advisorChainFactory;copy.interfaces = this.interfaces;copy.advisors = this.advisors;copy.updateAdvisorArray();return copy;}private static final class MethodCacheKey implements Comparable<MethodCacheKey> {private final Method method;private final int hashCode;public MethodCacheKey(Method method) {this.method = method;this.hashCode = method.hashCode();}public boolean equals(@Nullable Object other) {return this == other || other instanceof MyAdvisedSupport.MethodCacheKey && this.method == ((MyAdvisedSupport.MethodCacheKey) other).method;}public int hashCode() {return this.hashCode;}public String toString() {return this.method.toString();}public int compareTo(MethodCacheKey other) {int result = this.method.getName().compareTo(other.method.getName());if (result == 0) {result = this.method.toString().compareTo(other.method.toString());}return result;}}}
上面几个类有几个结论,这里说一下。
- 配置中添加的Advice对象最终都会被转换为DefaultPointcutAdvisor对象,此时
DefaultPointcutAdvisor未指定pointcut,大家可以去看一下DefaultPointcutAdvisor中pointcut有个默认值,默认会匹配任意类的任意方法。 - 当配置被冻结的时候,即frozen为true的时,此时配置中的Advisor列表是不允许修改的。
- 上面的 getInterceptorsAndDynamicInterceptionAdvice 方法,通过代理调用目标方法的时候,最后需要通过方法和目标类的类型,从当前配置中会获取匹配的方法拦截器列表,获取方法拦截器列表是由 AdvisorChainFactory 负责的。
getInterceptorsAndDynamicInterceptionAdvice 会在调用代理的方法时会执行,稍后在执行阶段会详解。 - 目标方法和其关联的方法拦截器列表会被缓存在 methodCache 中,当顾问列表有变化的时候,methodCache 缓存会被清除。
配置阶段完成之后,下面进入AopProxy获取阶段。
此阶段会根据AdvisedSupport中配置信息,判断具体是采用cglib的方式还是采用jdk动态代理的方式获取代理对象,先看一下涉及到的一些类。
AopProxy接口
package org.springframework.aop.framework;import org.springframework.lang.Nullable;public interface AopProxy {/*** * @return 创建一个新的代理对象*/Object getProxy();/*** * @param classLoader* @return 创建一个新的代理对象*/Object getProxy(@Nullable ClassLoader classLoader);
}
AopProxy的2个实现类,实现了上面定义的2个方法,稍后在代理的创建阶段详细介绍。
AopProxyFactory接口
通过名称就可以看出来,是一个工厂,负责创建AopProxy,使用的是简单工厂模式。
接口中定义了一个方法,会根据Aop的配置信息AdvisedSupport来获取AopProxy对象,主要是判断采用cglib的方式还是采用jdk动态代理的方式。
package org.springframework.aop.framework;
public interface AopProxyFactory {/*** 根据aop配置信息获取AopProxy对象*/AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
DefaultAopProxyFactory类
AopProxyFactory接口的默认实现,代码比较简单,我们来细看一下
/*** 默认AopProxyFactory实现,创建CGLIB代理或JDK动态代理。* 对于给定的AdvisedSupport实例,以下条件为真,则创建一个CGLIB代理:* optimize = true* proxyTargetClass = true* 未指定代理接口代理创建阶段到目前为止我们已经根据aop配置信息得到了AopProxy对象了,下面就可以调用AopProxy.getProxy方法获取代理对象了。AopProxy.createAopProxy方法返回的结果有2种情况JdkDynamicAopProxy:以jdk动态代理的方式创建代理ObjenesisCglibAopProxy:以cglib的方式创建动态代理项目详解这2个类的源码 。* 通常,指定proxyTargetClass来强制执行CGLIB代理,或者指定一个或多个接口来使用JDK动态代理。*/
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {public DefaultAopProxyFactory() {}public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {// optimize==false && proxyTargetClass 为false && 配置中有需要代理的接口if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {return new JdkDynamicAopProxy(config);} else {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.");} else {/*** 如果被代理的类为接口 或者 被代理的类是jdk动态代理创建代理类,则采用* JdkDynamicAopProxy的方式,否则采用cglib代理的方式*/return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));}}}/*** * @param config* @return 确定所提供的AdvisedSupport是否只指定了SpringProxy接口(或者根本没有指定代理接口)*/private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {Class<?>[] ifcs = config.getProxiedInterfaces();return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);}
}
代理创建阶段
到目前为止我们已经根据aop配置信息得到了AopProxy对象了,下面就可以调用AopProxy.getProxy方
法获取代理对象了。
AopProxy.createAopProxy方法返回的结果有2种情况
- JdkDynamicAopProxy:以jdk动态代理的方式创建代理
- ObjenesisCglibAopProxy:以cglib的方式创建动态代理
篇幅较长,下回分解吧,88
相关文章:

Spring-AOP核心源码、原理详解前篇
本文主要分4部分 Aop原理介绍介绍aop相关的一些类通过源码详解aop代理的创建过程通过源码详解aop代理的调用过程Aop代理一些特性的使用案例 Spring AOP原理 原理比较简单,主要就是使用jdk动态代理和cglib代理来创建代理对象,通过代理对象来访问目标对象…...
Reflection反射——Class类
概述 在Java中,除了int等基本类型外,Java的其他类型全部都是class(包括interface)。例如: String、Object、Runnable、Exception…… Java反射机制是Java语言的一个重要特性。在学习Java反射机制前,需要了…...
王朝兴替的因果
天道好轮 回,苍天饶过谁。王朝兴亡,天道无情。 而其因果循环,天道之森严,让人敬畏。 王朝创业帝王造下什么业,后世子孙在兴替之时,往往要承担何种果 报。 中国几千年的王朝史,因 果循环&…...

损坏SD数据恢复的8种有效方法
SD卡被用于许多不同的产品来存储重要数据,如图片和重要的商业文件。如果您的SD卡坏了,您需要SD数据恢复来获取您的信息。通过从损坏的SD卡中取回数据,您可以确保重要文件不会永远丢失,这对于工作或个人原因是非常重要的。 有许多…...

好评如潮的年度黑马韩剧,惊喜从一上线就开始
韩剧一直以来都以细腻的情感和紧凑的剧情打动观众,而最近播出的一部作品更是掀起了不小的风波-《法官大人》。孙贤周与金明民两大演技派领衔主演,凭借他们的深沉演技和复杂的角色关系,让这部剧集迅速成为热议焦点。故事围绕着一起交通事故展开…...

超好用的PC端语音转文字工具CapsWriter-Offline结合内网穿透实现远程使用
文章目录 前言1. 软件与模型下载2. 本地使用测试3. 异地远程使用3.1 内网穿透工具下载安装3.2 配置公网地址3.3 修改config文件3.4 异地远程访问服务端 4. 配置固定公网地址4.1 修改config文件 5. 固定tcp公网地址远程访问服务端 前言 本文主要介绍如何在Windows系统电脑端使用…...

1、https的全过程
目录 一、概述二、SSL过程如何获取会话秘钥1、首先认识几个概念:2、没有CA机构的SSL过程:3、没有CA机构下的安全问题4、有CA机构下的SSL过程 一、概述 https是非对称加密和对称加密的过程,首先建立https链接需要经过两轮握手: T…...

抢鲜体验 PolarDB PG 15 开源版
unsetunsetPolarDB 商业版unsetunset 8 月,PolarDB PostgreSQL 版兼容 PostgreSQL 15 版本(商业版)正式发布上线。 当前版本主要增强优化了以下方面: 改进排序功能:改进内存和磁盘排序算法。 增强SQL功能:支…...
UEFI——使用标准C库
一、C标准库 C标准库是ANSL C标准为C语言定义的标准库。C标准库包含15个头文件:assert.h ctype.h error.h float.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stddef.h stdio.h stdlib.h string.h time.h。标准库函数与C语言的紧密结合给我们开发程序带…...

[全网首发]怎么让国行版iPhone使用苹果Apple Intelligence
全文共分为两个部分:第一让苹果手机接入AI,第二是让苹果手机接入ChatGPT 4o功能。 一、国行版iPhone开通 Apple Intelligence教程 打破限制:让国行版苹果手机也能接入AI 此次发布会上,虽然国行 iPhone16 系列不支持 GPT-4o&…...

C语言-综合案例:通讯录
传送门:C语言-第九章-加餐:文件位置指示器与二进制读写 目录 第一节:思路整理 第二节:代码编写 2-1.通讯录初始化 2-2.功能选择 2-3.增加 和 扩容 2-4.查看 2-5.查找 2-6.删除 2-7.修改 2-8.退出 第三节:测试 下期…...
XWiki中添加 html 二次编辑失效
如果直接在 XWiki 中添加 html, 例如 修改颜色, 新窗口打开主页面等功能, 首次保存是生效的. 如果再次编辑, 则失效, 原因是被转换成了 Markdown 的代码, 而 Markdown 不支持. 解决这个问题可以使用 HTML 宏. 在 XWiki 中使用 Markdown 1.2 语法时,默认 Markdown …...

外贸|基于Java+vue的智慧外贸平台系统(源码+数据库+文档)
外贸|智慧外贸平台|外贸服务系统 目录 基于Javavue的智慧外贸平台系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师&…...

Elasticsearch:无状态世界中的数据安全
作者:来自 Elastic Henning Andersen 在最近的博客文章中,我们宣布了支持 Elastic Cloud Serverless 产品的无状态架构。通过将持久性保证和复制卸载到对象存储(例如 Amazon S3),我们获得了许多优势和简化。 从历史上…...

动手学习RAG:迟交互模型colbert微调实践 bge-m3
动手学习RAG: 向量模型动手学习RAG: BGE向量模型微调实践]()动手学习RAG: BCEmbedding 向量模型 微调实践]()BCE ranking 微调实践]()GTE向量与排序模型 微调实践]()模型微调中的模型序列长度]()相似度与温度系数 本文我们来进行ColBERT模型的实践,按惯例ÿ…...

springboot 整合quartz定时任务
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pom的配置1.加注解 二、使用方法1.工程图2.创建工具类 三、controller 实现 前言 提示:这里可以添加本文要记录的大概内容: 提示&a…...

erlang学习: Mnesia Erlang数据库3
Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…...

善于善行——贵金属回收
在当今社会,贵金属回收已成为一项日益重要的产业。随 着科技的不断进步和人们对资源可持续利用的认识逐渐提高,贵金属回收的现状也备受关注。 目前,贵金属回收市场呈现出蓬勃发展的态势。一方面,贵金属如金、银、铂、钯等在众多领…...

用CSS 方式设置 table 样式
在现代Web开发中,使用CSS来设置table的样式是一种常见且强大的方法,它能让你的表格数据既美观又易于阅读。下面我将通过一个示例来展示如何使用现代CSS技巧来美化表格。 效果图 HTML 结构 首先,我们定义一个基本的HTML表格结构:…...
Elasticsearch7.x 集群迁移文档
一、集群样例信息 集群名称:escluster-ali-test 1、源集群:(source_cluster) 节点IP节点名称节点角色是否为master节点10.200.112.149es2.gj1.china-job.cndata,master是10.200.112.151es1.gj1.china-job.cndata,master否10.200.112.153es…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...