AOP原理
AOP相关的概念
1)Aspect:切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面。
2)Join point:连接点,也就是可以进行横向切入的位置。
3)Advice:通知,切面在某个连接点执行的操作(分为:Before advice,After returning advice,After throwing advice,After (finally) advice,Around advice)。
4)Pointcut:切点,符合切点表达式的连接点,也就是真正被切入的地方。
1) JDK动态代理
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP。我们看个例子:
被代理对象实现的接口,只有接口中的方法才能够被代理:
public interface UserService { public void addUser(User user); public User getUser(int id); }
被代理对象:
public class UserServiceImpl implements UserService { public void addUser(User user) { System.out.println("add user"); } public User getUser(int id) { User user = new User(); System.out.println("getUser"); return user; } }
代理中间类:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyUtil implements InvocationHandler {
//被代理的对象 private Object target; public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do method before"); Object result = method.invoke(target, args); System.out.println("do method after"); return result; } ProxyUtil(Object target){ this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } }
测试:
import java.lang.reflect.Proxy; import User; public class ProxyTest { public static void main(String[] args){
// 被代理的对象 Object proxyedObject = new UserServiceImpl(); ProxyUtil proxyUtils = new ProxyUtil(proxyedObject); //生成代理对象,对被代理对象的这些接口进行代理:UserServiceImpl.class.getInterfaces() UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.getUser(1); proxyObject.addUser(new User()); } }
结果:
do method before
getUser
do method after
do method before
add user
do method after
该方式有一个要求,被代理的对象必须实现接口,而且只有接口中的方法才能被代理。
2)CGLIB(code generate libary)
package aop; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGProxy implements MethodInterceptor{ private Object target; // 被代理对象 public CGProxy(Object target){ this.target = target; } public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable { System.out.println("do method before"); Object result = proxy.invokeSuper(arg0, arg2); System.out.println("do method after"); return result; } public Object getProxyObject() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 设置父类 // 设置回调
// 在调用父类方法时,回调 this.intercept() enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } }
测试:
public class CGProxyTest { public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的对象 CGProxy cgProxy = new CGProxy(proxyedObject); UserService proxyObject = (UserService) cgProxy.getProxyObject(); proxyObject.getUser(1); proxyObject.addUser(new User()); } }
结果:
do method before
getUser
do method after
do method before
add user
do method after
spring实现AOP的相关源码:
@SuppressWarnings("serial") 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()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
}
如果被代理对象实现了接口,那么就使用JDK的动态代理技术,反之则使用CGLIB来实现AOP,所以Spring默认是使用JDK的动态代理技术实现AOP的。
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @Override public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }
}
JdkDynamicAopProxy中invoke方法核心代码如下:
// Get the interception chain for this method.得到目标方法的拦截器链 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) {
// 如果chain是空的,直接调用target对象的method // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else {
// 进行拦截器链的调用 // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); }
顺着this.advised.getInterceptorsAndDynamicInterceptionAdvice看看拦截器链的产生:
/** * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects * for the given method, based on this configuration. * @param method the proxied method * @param targetClass the target class * @return List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) */ public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, 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; }
this.advisorChainFactory其实是DefaultAdvisorChainFactory。
/** * A simple but definitive way of working out an advice chain for a Method, * given an {@link Advised} object. Always rebuilds each advice chain; * caching can be provided by subclasses. * * @author Juergen Hoeller * @author Rod Johnson * @author Adrian Colyer * @since 2.0.3 */ @SuppressWarnings("serial") public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable { @Override public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class<?> targetClass) { // This is somewhat tricky... We have to process introductions first, // but we need to preserve order in the ultimate list. List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); boolean hasIntroductions = hasMatchingIntroductions(config, actualClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor); MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) { 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)); } } } } 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; } /** * Determine whether the Advisors contain matching introductions. */ private static boolean hasMatchingIntroductions(Advised config, Class<?> actualClass) { for (int i = 0; i < config.getAdvisors().length; i++) { Advisor advisor = config.getAdvisors()[i]; if (advisor instanceof IntroductionAdvisor) { IntroductionAdvisor ia = (IntroductionAdvisor) advisor; if (ia.getClassFilter().matches(actualClass)) { return true; } } } return false; } }
List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, Class<?> targetClass)
这里的config就是ProxyFactoryBean的实例。(config传入了个AdvisedSupport实例,ProxyFactoryBean继承了AdvisedSupport)
继续查看ProxyFactoryBean的代码。
ProxyFactoryBean的getObject方法调用时,会对adviceChain进行初始化。
/** * Return a proxy. Invoked when clients obtain beans from this factory bean. * Create an instance of the AOP proxy to be returned by this factory. * The instance will be cached for a singleton, and create on each call to * {@code getObject()} for a proxy. * @return a fresh AOP proxy reflecting the current state of this factory */ @Override public Object getObject() throws BeansException { initializeAdvisorChain(); if (isSingleton()) { return getSingletonInstance(); } else { if (this.targetName == null) { logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } }
initializeAdvisorChain的内部实现:
因为ProxyFactoryBean实现了BeanFactoryAware接口,所以可以拿到当前的容器实例beanFactory。
通过调用this.beanFactory.get(beanName)方法将各个advice bean拿出来,然后加入到list中。
initializeAdvisorChain的实现如下。
/** * Create the advisor (interceptor) chain. Advisors that are sourced * from a BeanFactory will be refreshed each time a new prototype instance * is added. Interceptors added programmatically through the factory API * are unaffected by such changes. */ private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { if (this.advisorChainInitialized) { return; } if (!ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); } // Globals can't be last unless we specified a targetSource using the property... if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { throw new AopConfigException("Target required after globals"); } // Materialize interceptor chain from bean names. for (String name : this.interceptorNames) { if (logger.isTraceEnabled()) { logger.trace("Configuring advisor or advice '" + name + "'"); } if (name.endsWith(GLOBAL_SUFFIX)) { if (!(this.beanFactory instanceof ListableBeanFactory)) { throw new AopConfigException( "Can only use global advisors or interceptors with a ListableBeanFactory"); } addGlobalAdvisor((ListableBeanFactory) this.beanFactory, name.substring(0, name.length() - GLOBAL_SUFFIX.length())); } else { // If we get here, we need to add a named interceptor. // We must check if it's a singleton or prototype. Object advice; if (this.singleton || this.beanFactory.isSingleton(name)) { // Add the real Advisor/Advice to the chain. advice = this.beanFactory.getBean(name); } else { // It's a prototype Advice or Advisor: replace with a prototype. // Avoid unnecessary creation of prototype bean just for advisor chain initialization. advice = new PrototypePlaceholderAdvisor(name); } addAdvisorOnChainCreation(advice, name); } } } this.advisorChainInitialized = true; }
2)拦截器链是如何链式调用的
接着看JdkDynamicAopProxy中invoke方法调用拦截器链代码,ReflectiveMethodInvocation.proceed执行拦截器链的调用:
/** * Spring's implementation of the AOP Alliance * {@link org.aopalliance.intercept.MethodInvocation} interface, * implementing the extended * {@link org.springframework.aop.ProxyMethodInvocation} interface. * * <p>Invokes the target object using reflection. Subclasses can override the * {@link #invokeJoinpoint()} method to change this behavior, so this is also * a useful base class for more specialized MethodInvocation implementations. * * <p>It is possible to clone an invocation, to invoke {@link #proceed()} * repeatedly (once per clone), using the {@link #invocableClone()} method. * It is also possible to attach custom attributes to the invocation, * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods. * * <p><b>NOTE:</b> This class is considered internal and should not be * directly accessed. The sole reason for it being public is compatibility * with existing framework integrations (e.g. Pitchfork). For any other * purposes, use the {@link ProxyMethodInvocation} interface instead. * * @author Rod Johnson * @author Juergen Hoeller * @author Adrian Colyer * @see #invokeJoinpoint * @see #proceed * @see #invocableClone * @see #setUserAttribute * @see #getUserAttribute */
//ReflectiveMethodInvocation实现了接口ProxyMethodInvocation,而ProxyMethodInvocation是MethodInvocation的子接口。
//所以ReflectiveMethodInvocation实现了MethodInvocation接口。
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable { @Override public Object proceed() throws Throwable { // We start with an index of -1 and increment early. 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; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
}
MethodInterceptor代码:
public interface MethodInterceptor extends Interceptor { /** * 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 */ Object invoke(MethodInvocation invocation) throws Throwable; }
spring提供的MethodBeforeAdviceInterceptor:
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { private MethodBeforeAdvice advice; /** * Create a new MethodBeforeAdviceInterceptor for the given advice. * @param advice the MethodBeforeAdvice to wrap */ 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(); } }
Spring AOP的配置
Spring中AOP的配置一般有两种方法,一种是使用 aop:config 标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。
1)基于aop:config的AOP配置
下面是一个典型的事务AOP的配置:
<tx:advice id="transactionAdvice" transaction-manager="transactionManager"?> <tx:attributes > <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.zby.service..*Impl.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /> </aop:config>
2) 基于注解和@Aspect风格的AOP配置
<!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" />
扫描切面:
<context:component-scan base-package="com.zby.aop" />
启用@AspectJ风格的注解:
<aop:aspectj-autoproxy proxy-target-class="true"/>
proxy-target-class="true" 这个最好不要随便使用,它是指定只能使用CGLIB代理。
切面失效
因为Spring AOP是基于动态代理对象的,那么如果target中的方法不是被代理对象调用的,那么就不会织入切面代码。
失效示例:
切面:
@Aspect @Order(100) public class MethodMonitorAspect { Logger logger = LoggerFactory.getLogger(UMPAspect.class); /** * 环绕增强切面 * * @param pjp * @return */ @Around(value = "@annotation(methodMonitor)") public Object intercept(ProceedingJoinPoint pjp, MethodMonitor methodMonitor) throws Throwable { logger.info("do method before"); obj = pjp.proceed(); logger.info("do method after"); return obj; } }
@Service("userService") public class UserServiceImpl implements UserService{
public void a() { b(); }
@MethodMonitor public void b() {
System.out.println("do b"); } }
虽然b()方法被调用了,但是因为不是代理对象调用的,所以切面并没有执行。这就是Spring aop的缺陷。解决方法如下:
首先:将 <aop:aspectj-autoproxy /> 改为:
<aop:aspectj-autoproxy expose-proxy="true"/>
修改失效示例:
@Service("userService") public class UserServiceImpl implements UserService{ public void a() { ((UserService)AopContext.currentProxy()).b(); } @methodMonitor public void b() {
System.out.println("do b"); } }
结果:
do method before
do b
do method after
((UserService)AopContext.currentProxy())先获得当前的代理对象,然后在调用b() 方法就可以了。expose-proxy="true" 表示将当前代理对象暴露出去,不然 AopContext.currentProxy()返回的是null。
切面顺序
项目中经常会使用多个切面,监控、权限校验、日志打印、参数校验,使用@Order注解配置顺序,越小优先级越高,越先执行。
final方法想代理怎么解决?
使用字节码增强技术,重写类。例如:Javassist、bytebuddy、ASM工具包。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构