从源码角度深刻理解Spring AOP

今天总结的是Spring 的AOP原理。本文不讲使用,只讲原理!这里贴笔者收藏夹里两篇很不错的关于AOP的文章:
1、springboot aop的execution 表达式详解
2、【漫画】AOP 面试造火箭事件始末
好了,Talk is cheap. Show you the code。

AOP的入口在哪

我们回忆一下,我们写切面时,要把它定义为spring的bean。基于这个切面bean,做了代理增强。透过这句话你想到了什么?如果你熟悉Spring Ioc的话,bean 意味着往ioc容器靠,对bean做处理意味着往后置处理器靠。那么你一定会想到Spring的后置处理器BeanPostProcessor,它可以在bean的初始化前后进行处理。这就是AOP的初始入口。

BeanPostProcessor是属于Ioc范畴,但是接下来分析AOP原理,必须得基于它开始。所以笔者尽量从简讲解,将篇幅尽量留给AOP。

BeanPostProcessor后置处理器的调用发生在spring ioc容器完成Bean实例对象的创建和属性的依赖注入之后。那什么时候触发Bean实例对象的创建和属性的依赖注入之后呢?有两种情况:

  • 应用程序第一次向容器索取Bean时通过getBean()方法完成的。
  • 当你使用@Lazy(value = false)或者不使用该注解声明Bean时 容器将会在初始化时对所配置的Bean进行预实例化

我们看一下源码

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
// 省略部分代码
Object exposedObject = bean;
		try {
		    // 对bean属性进行依赖注入
			populateBean(beanName, mbd, instanceWrapper);
			// Bean实例的依赖注入完成后,开始对Bean实例进行初始化
			// 为Bean实例对象应用BeanPostProcessor后置处理器
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
// 省略部分代码
}

initializeBean方法主要就是遍历所有注册的后置处理器,回调后置处理器的before与after方法,对Bean实例初始化前后做一些处理。源码就不贴啦~

增强策略选择

我们知道了AOP的入口,那么接下来就是要知道他是具体用什么进行增强的。我们看一下后置处理器的实现类,找一下。就是这家伙AbstractAutoProxyCreator
在这里插入图片描述
我们思考一下,如果是你来设计,你要做增强,会选择BeanPostProcessor的postProcessBeforeInitialization还是postProcessAfterInitialization进行中增强呢?什么是增强?肯定是在目标类完整的情况下增强才有意义,所以目标锁定postProcessAfterInitialization方法。我们看一下postProcessAfterInitialization。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
}

有一个很核心方法wrapIfNecessary,我们跟进去,着重分析一下。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		// 判断是否应该处理这个bean
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		// 判断是否是一些InfrastructureClass或者是否应该跳过这个bean
		// 所谓InfrastructureClass就是指Advice、PointCut、Advisor等接口的实现类
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// 获取这个bean的通知
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			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;
	}

createProxy方法里最终调用的是proxyFactory.getProxy(getProxyClassLoader())

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {
   // 省略部分代码 
   return proxyFactory.getProxy(getProxyClassLoader());
}

我们都知道AOP的代理有两种方式:JDK以及CGLib。那么选哪一种呢?这里用的是工厂模式。
方法调用链路:getProxy->createAopProxy->getAopProxyFactory->createAopProxy
发现最后的createAopProxy走的是DefaultAopProxyFactory 的实现。里面做了JDK与CGLib的策略选择。
我们都知道当目标类是接口,则走JDK动态代理,其他的可以选择走CGLib动态代理。看到源码其实还有一种情况也是会走JDK动态代理–符合Proxy.isProxyClass(targetClass):
当且仅当使用getProxyClass方法或newProxyInstance方法动态将指定的类生成为代理类时

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (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);
		}
	}

代理对象的生成

我们分别看一下JDK与CGLib两种方式的代理对象的生成方式
JDK: 用的JDK 的Proxy。底层原理是反射技术

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}
}

CGLib: 用的Enhancer 。底层原理是ASM字节码增强技术

class CglibAopProxy implements AopProxy, Serializable {
public Object getProxy(@Nullable ClassLoader classLoader) {
		    // .....省略部分代码
			// Configure CGLIB Enhancer...
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
			return createProxyClassAndInstance(enhancer, callbacks);
		}
		// 省略部分代码
	}
}

切面是如何织入的

在JDK动态代理,核心在InvocationHandler的invoke方法上,生成的代理对象的方法调用都会委派到invoke方法。我们着重分析一下Spring AOP是如何织入的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
		   // 目标对象未实现eaquals()方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				return equals(args[0]);
			}
			// 目标对象未实现hashCode()方法
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			// 直接反射调用Advised接口或者其父接口中定义的方法,不应用通知
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// 获得目标对象的类
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 获取可以运用到此方法上的拦截器列表
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		    // 如果没有可以运用到此方法上的拦截器,则直接反射调用Method.invoke(target, args)
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// 创建ReflectiveMethodInvocation
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 通过拦截器列表执行joinpoint.
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// 这个AopContext内部维护了threadlocal实例.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

在CGLib动态代理中,核心是方法拦截器MethodInterceptor的intercept的方法上。织入就是发生在这里,思路跟jdk的基本一致

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// 获得目标对象的类
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// 如果没有可以应用到此方法上的拦截器,则直接反射调用methodProxy.invoke(target, argsToUse)
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
					retVal = methodProxy.invoke(target, argsToUse);
				}
				else {
					// 创建CglibMethodInvocation
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null && !targetSource.isStatic()) {
					targetSource.releaseTarget(target);
				}
				if (setProxyContext) {
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

主要思路:先获取应用到此方法上拦截器列表(Interceptor Chain)。如果有拦截器,则应用拦截器,并执行连接点(joinPoint);如果没有拦截器列表,则直接反射执行连接点。这里关键是拦截器链是如何获取的,以及它又是如何执行的
另外,AopContext熟不熟悉?当在调用同类方法,增强失效的时候,基本就会请这家伙出山解决。
我们复习一下为何会失效?因为增强方法调用同类方法其实就是this.xxx()。this指代的是目标类而不是代理类。要想调用同类方法也起效果,思路只有一种,就是要走代理类。具体解决方法有以下两种:

  • 强制拿出代理类来执行:就是AopContext这家伙
  • 显示触发代理类执行:注入自身实例显示调用

给个小作业:在早版本的Dubbo中有这么一个问题,就是@Transactional与@Service(dubbo的)共用时,会导致注册中心注册的服务实例时是个代理对象!导致调用方调用异常。读者可以思考这个问题。

凡是基于AOP的设计,都会有笔者说的这个问题,像@Transactional等。使用过程中切记!其实类似的还有个synchronized,同步方法嵌套调用,同步失效。但是原理就截然不同。后面我们在并发编程专栏着重讲解,我们共同期待~

拦截器链如何获取与执行

通过上面的代码我们可以知道拦截器链是通过AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice获取的

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
		MethodCacheKey cacheKey = new MethodCacheKey(method);
		List<Object> cached = this.methodCache.get(cacheKey);
		if (cached == null) {
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}

可以看到,获取拦截器其实是由AdvisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice()方法完成的,且获取到的结果会被缓存起来。接下来我们分析这个方法实现。

主要思路:从提供的配置实例config中获取Advisor列表,遍历处理这些Advisor。如果是IntroductionAdvisor,则判断此Advisor能否运用到目标类targetClass上。如果是PointcutAdvisor,则判断此Advisor能否运用到目标方法Method上。将满足条件的Advisor通过AdvisorAdaptor转化成拦截器列表返回

public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {
		// 这里实际上注册了一些列AdvisorAdaptor,用于将Advisor转换成MethodInterceptor
		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
		// 获得Advisor列表
		Advisor[] advisors = config.getAdvisors();
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		Boolean hasIntroductions = null;
			
		for (Advisor advisor : advisors) {
			if (advisor instanceof PointcutAdvisor) {
				// Add it conditionally.
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					// 检查当前Advisor的切入点是否可以匹配当前方法
					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 {
						match = mm.matches(method, actualClass);
					}
					if (match) {
						// 如果匹配将Advisor转换成MethodInterceptor
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						if (mm.isRuntime()) {
							for (MethodInterceptor interceptor : interceptors) {
								interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
							}
						}
						else {
							interceptorList.addAll(Arrays.asList(interceptors));
						}
					}
				}
			}
			else if (advisor instanceof IntroductionAdvisor) {
				IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
				if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
					Interceptor[] interceptors = registry.getInterceptors(advisor);
					interceptorList.addAll(Arrays.asList(interceptors));
				}
			}
			else {
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}

		return interceptorList;
	}
}

这个方法执行完成后,Advised中配置的能够运用到连接点(JointPoint)或者目标对象(TargetObject)的Advisor全部转化成MethodInterceptor。接下来看看拦截器链是怎么起作用
JDK:

if (chain.isEmpty()) {
	Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
	retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}else {
	MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
	retVal = invocation.proceed();
}

CGLib:

if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
	retVal = methodProxy.invoke(target, argsToUse);
}else {
    // CglibMethodInvocation继承ReflectiveMethodInvocation
	retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}

从源码里可以看出,如果得到拦截器链为空,jdk的处理方式是直接反射调用目标方法,cglib是用methodProxy.invoke,复杂点,否则创建MethodInvocation,调用其proceed()方法,触发拦截器链的执行

public Object proceed() throws Throwable {
		// 如果拦截器执行完了,则执行连接点.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		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 {
				// 动态匹配失败,略过当前拦截器,递归调用下一个拦截器
				return proceed();
			}
		}
		else {
			// 执行当前拦截器
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

在CGLib中有这么一个细的知识点:MethodProxy#invokeSuper和invoke的区别cglib动态代理拦截器中使用MethodProxy#invokeSuper和invoke的区别

至此,拦截器链就完美地形成了。

触发通知大揭秘

我们通过前文分析已经知道AOP的增强具体体现在拦截器链上,拦截器链一个很核心就是AdvisorAdaptor适配器将Advisor转化成连接器列表。这个适配器到底有什么魔力呢?我们探一下究竟

public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {
		// 省略部分代码
		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
		// 省略部分代码
		MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
		// 省略部分代码
}

GlobalAdvisorAdapterRegistry 类负责拦截器的适配和注册过程。起到了适配器和单例模式的作用

public final class GlobalAdvisorAdapterRegistry {

	private GlobalAdvisorAdapterRegistry() {
	}

	private static AdvisorAdapterRegistry instance = new DefaultAdvisorAdapterRegistry();

	public static AdvisorAdapterRegistry getInstance() {
		return instance;
	}

	static void reset() {
		instance = new DefaultAdvisorAdapterRegistry();
	}
}

DefaultAdvisorAdapterRegistry类用来完成各种通知的适配和注册过程。类中设置了一系列适配器,正是这些适配器的实现,为Spring AOP提供了织入能力。

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

	private final List<AdvisorAdapter> adapters = new ArrayList<>(3);

	// 一系列熟知的适配器
	public DefaultAdvisorAdapterRegistry() {
		registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
		registerAdvisorAdapter(new AfterReturningAdviceAdapter());
		registerAdvisorAdapter(new ThrowsAdviceAdapter());
	}
	
	@Override
	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) {
			if (adapter.supportsAdvice(advice)) {
				interceptors.add(adapter.getInterceptor(advisor));
			}
		}
		if (interceptors.isEmpty()) {
			throw new UnknownAdviceTypeException(advisor.getAdvice());
		}
		return interceptors.toArray(new MethodInterceptor[0]);
	}

	@Override
	public void registerAdvisorAdapter(AdvisorAdapter adapter) {
		this.adapters.add(adapter);
	}
}

我们以MethodBeforeAdviceAdapter为例子看一下具体实现

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

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

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

}

Spring AOP为了实现Advice的织入,设计了特定的拦截器对这些功能进行封装。我们看看MethodBeforeAdviceInterceptor类是如何完成封装的。

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

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

可以看到invoke()方法中首先触发了Advice的before方法回调,然后才执行proceed()方法。其他的MethodInterceptor(spring的)就不再一一赘述了,读者们可以源码看一下,比较简单。

至此,我们知道了对目标对象的增强是通过拦截器实现的。

本文就到此结束了,我们下文见,谢谢
github: honey开源系列组件作者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值