如何完整追踪并复现 Spring AOP 源码学习全过程?

摘要:Spring AOP 各阶段的流程图 简而言之, 可以分为四部步走 一是 在 spring aop 最初的入口是在哪? 二是 spring 如何解析配置, 如何封装关于AOP概念配置对象的? 三是 spring 如何根据配置对象创建代理对象
Spring AOP 各阶段的流程图 简而言之, 可以分为四部步走 一是 在 spring aop 最初的入口是在哪? 二是 spring 如何解析配置, 如何封装关于AOP概念配置对象的? 三是 spring 如何根据配置对象创建代理对象? 四是 调用代理对象方法的过程, spring 是如何拦截的? 一、Spring AOP 的入口 AOP 的入口点 (XML) 在XML中配置 <aop: ../> 启用Aop标签, 在解析XML自定义标签时, 会拿到 AopNamespaceHandler 命名空间处理器: public class AopNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // In 2.0 XSD as well as in 2.5+ XSDs registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace in 2.5+ registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); } registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());其内部的注册的 ConfigBeanDefinitionParser 做了两件事 注册一个名称为org.springframework.aop.config.internalAutoProxyCreator 的Bean Definition(对AOP处理); 对应的类, 根据情况有以下三个可能: org.springframework.aop.config.AopConfigUtils#APC_PRIORITY_LIST, 系统会按优先级注册以下三个自动代理创建器之一: AnnotationAwareAspectJAutoProxyCreator (最高优先级) 当项目中存在AspectJ依赖时启用 支持@Aspect注解的切面、@Before、@After等通知注解 继承自 AspectJAwareAdvisorAutoProxyCreator 能够识别并处理基于注解的切面定义 AspectJAwareAdvisorAutoProxyCreator (第二优先级) 当项目中存在AspectJ依赖但不需要注解支持时启用 主要处理XML配置的切面定义 继承自 InfrastructureAdvisorAutoProxyCreator 提供对AspectJ切点表达式的解析能力 InfrastructureAdvisorAutoProxyCreator (最低优先级) 当项目中不存在AspectJ依赖时启用 只处理基础的advisor,不支持切面编程 是最基础的自动代理创建器 适用于简单的拦截器场景 注册过程通过AopConfigUtils.registerAutoProxyCreatorIfNecessary方法实现,该方法会根据classpath中是否存在AspectJ相关类来决定注册哪个具体的实现类。 这里对应的是 AspectJAwareAdvisorAutoProxyCreator AOP 的入口点 (注解) AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(AopConfiguration.class); BicycleService bean = acac.getBean(BicycleService.class); bean.doProduct("黑色"); 关键是在 AnnotationConfigApplicationContext 构造 reader(负责解析注解的) 成员变量时 org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext() /** * Create a new AnnotationConfigApplicationContext that needs to be populated * through {@link #register} calls and then manually {@linkplain #refresh refreshed}. */ public AnnotationConfigApplicationContext() { StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create"); /** * 构造负责解析注解的对象 * 1. 创建了一个标准环境对象 * {@link AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(BeanDefinitionRegistry)} * 2. 注册了 internalConfigurationAnnotationProcessor (重点) * {@link org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.core.env.Environment)} * * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)} * */ this.reader = new AnnotatedBeanDefinitionReader(this); createAnnotatedBeanDefReader.end(); this.scanner = new ClassPathBeanDefinitionScanner(this); } 创建了一个标准环境对象 注册了internalConfigurationAnnotationProcessor 注册 BeanDefinition 名称是 org.springframework.context.annotation.internalConfigurationAnnotationProcessor对应的 class 是 ConfigurationClassPostProcessor.class其实现了 BeanDefinitionRegistryPostProcesso r接口,它是对注解支持核心Bean 具体需要的BeanDefinition是一样的, 无非就是换成注解的形式配置和解析 二、AOP 相关 BeanDefinition 的解析过程 (XML) https://img2024.cnblogs.com/blog/3723410/202603/3723410-20260307173232904-136419763.jpg org.springframework.aop.config.ConfigBeanDefinitionParser 解析 aop:config 标签的子元素 (pointcut, advisor, aspect) 每一个通知(Advice) 都会封装为一个 AspectJPointcutAdvisor 的 BeanDefinition 将其注册到 BeanFactory org.springframework.aop.config.ConfigBeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) @Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef); /** * 1. 注册一个名称为`org.springframework.aop.config.internalAutoProxyCreator` 对AOP处理的Bean Definition; 它是实现 InstantiationAwareBeanPostProcessor 接口的 * 名称是: org.springframework.aop.config.internalAutoProxyCreator * 对应的类, 根据情况有以下三个可能: org.springframework.aop.config.AopConfigUtils#APC_PRIORITY_LIST * InfrastructureAdvisorAutoProxyCreator.class * AspectJAwareAdvisorAutoProxyCreator.class * AnnotationAwareAspectJAutoProxyCreator.class * */ configureAutoProxyCreator(parserContext, element); /** * 2. 解析 <aop:config> 标签的子元素 (pointcut, advisor, aspect) * 解析 <aspect ...>: * 每一个通知(Advice) 都会封装为一个 AspectJPointcutAdvisor 的BeanDefinition 然后将其注册到 BeanFactory * * AspectJPointcutAdvisor 的包含情况 * AspectJPointcutAdvisor 包括: AspectJXXXAdvice(有五种通知) * 而 AspectJXXXAdvice 则包括: 三个关键属性 * 1. java.lang.reflect.Method(通知切面的方法) * 2. org.springframework.aop.aspectj.AspectJExpressionPointcut(切入点表达式) * 3. org.springframework.aop.aspectj.AspectInstanceFactory (切面实例工厂) */ List<Element> childElts = DomUtils.getChildElements(element); for (Element elt: childElts) { String localName = parserContext.getDelegate().getLocalName(elt); switch (localName) { /** * 解析 pointcut/切入点 //筛选连接点, 即: 哪些方法需要被代理 */ case POINTCUT -> parsePointcut(elt, parserContext); /** * 解析 advisor/通知/建议/增强处理 //即: 增强功能这一部分代码 */ case ADVISOR -> parseAdvisor(elt, parserContext); /** * 解析 aspect/切面 //切面是**通知(写日志)** 和**切入点(要被代理的方法)** 的结合, 即:**修改后的整个方法, (有正常需要调用的代码,又有写日志的)** * */ case ASPECT -> parseAspect(elt, parserContext); } } parserContext.popAndRegisterContainingComponent(); return null; } 三、AOP 代理对象 的创建过程 https://img2024.cnblogs.com/blog/3723410/202603/3723410-20260307173316974-1553110752.jpg AOP 处理入口点 (BPP) ApplicationContext在容器创建bean流程中 {@link org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, RootBeanDefinition, Object[])} 会调用接口方法 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 如果其该返回不为null, 则直接使用这个bean实例返回, 不走正常实例bean流程. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition) protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { // 如果是合成的(mbd是AOP的时候为true) 并且实现 InstantiationAwareBeanPostProcessor 接口 if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { Class<?> targetType = determineTargetType(beanName, mbd); if (targetType != null) { /** * 调用 InstantiationAwareBeanPostProcessor * #postProcessBeforeInstantiation * #postProcessAfterInitialization */ bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); if (bean != null) { bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } } mbd.beforeInstantiationResolved = (bean != null); } return bean; } AspectJAwareAdvisorAutoProxyCreator 创建代理对象 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary /** * Wrap the given bean if necessary, i.e. if it is eligible for being proxied. * @param bean the raw bean instance * @param beanName the name of the bean * @param cacheKey the cache key for metadata access * @return a proxy wrapping the bean, or the raw bean instance as-is */ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } /** * 拿到所有匹配织入当前bean的 所有通知器(Advisor) * 做了三件事, 见: {@link org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean(java.lang.Class, java.lang.String, org.springframework.aop.TargetSource)} * 1. 往返回 `AspectJXXXAdvice`列表数组`0`索引 插入一个{@link org.springframework.aop.interceptor.ExposeInvocationInterceptor} 实例 * 方便传递参数用的 * * 2. 怎么匹配(Advisor)? * Advisor中的 `AspectJExpressionPointcut` 是实现 {@link ClassFilter} 和 {@link org.springframework.aop.MethodMatcher} 接口 * 一个进行类匹配, 一个进行方法匹配. * * 3. 排序, 基于 `有向无环` 图进行排序; 可能匹配到多个切面(aspect) * */ // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { /** *{@link org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#advisedBeans} * 这个变量缓存所有处理过的 bean名称, value 为 boolean值, 如果为false 则不处理 */ 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; } org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#buildProxy private Object buildProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) { if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) { AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass); } /** * * 1. 创建 代理工厂 * {@link org.springframework.aop.framework.ProxyFactory} */ ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (proxyFactory.isProxyTargetClass()) { // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios) if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) { // Must allow for introductions; can't just set interfaces to the proxy's interfaces only. for (Class<?> ifc : beanClass.getInterfaces()) { proxyFactory.addInterface(ifc); } } } else { // No proxyTargetClass flag enforced, let's apply our default checks... if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { // 尝试获取所有接口 evaluateProxyInterfaces(beanClass, proxyFactory); } } /** * 所有匹配的 Advisor */ Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); /** * 被代理的对象 */ proxyFactory.setTargetSource(targetSource); /** * 2.定制化 ProxyFactory (预留扩展的) */ customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } // Use original ClassLoader if bean class not locally loaded in overriding class loader ClassLoader classLoader = getProxyClassLoader(); if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) { classLoader = smartClassLoader.getOriginalClassLoader(); } /** * 3.根据 classOnly 决定返回的是: 代理对象的class, 还是 代理对象的实例 */ return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader)); } AOP 的两种织入原理 1.静态AOP (AspectJ) 静态AOP:在编译期,切面直接以字节码的形式编译到目标字节码文件中。 AspectJ 属于静态AOP,是在编译时进行增强,会在编译的时候将AOP逻辑织入到代码中,需要专有的编译器和织入器。 优点:被织入的类性能不受影响。 缺点:不够灵活 javassist [[Java 字节码指令]] 2.动态AOP JDK 动态代理 [[10.结构型 - 代理模式 (Proxy Pattern)]] 在正常开发中, 基于 依赖倒置原则(Dependence Inversion Principle), 通常需要接口解决 上层与类的耦合问题; 即A类调用B类, 为了解决A依赖与B, 抽取B做成C接口. 变成A依赖以C接口; 在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。 Java从1.3引入动态代理。实现原理是为被代理的业务接口生成代理类,将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑。 主要实现方式依赖 java.lang.reflect 包下的InvocationHandler和Proxy类。 优点:Java标准库原生支持,使用简单,无需引用额外的包。相对于静态AOP更灵活。 缺点:带代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。 JDK 动态代理必须需要接口, 它的原理是动态的生成class, 类似实现原始类的接口 /** * JDK 动态代理实现 */ public static final void JDK_PROXY(){ /** * 目标类 */ ProductServiceInter carService = new CarService(); // 拦截回调处理器 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result ; System.out.println("增强处理 前"); //调用方法 result = method.invoke(carService, args); System.out.println("增强处理 后"); return result; } }; ProductServiceInter proxyInstance = (ProductServiceInter) Proxy.newProxyInstance(CarService.class.getClassLoader() , CarService.class.getInterfaces(), handler); proxyInstance.doProduct("白色"); } CGLIB 动态代理 CGLIB(Code Generation Library)是一个开源项目,是一个强大的, 高性能, 高质量的Code生成类库, 它可以在运行期扩展Java类与实现Java接口, 其实就是cglib可以在运行时动态生成字节码; 使用CGLib实现动态代理, 完全不受代理类必须实现接口的限制, 而且CGLib底层采用ASM字节码生成框架, 使用字节码技术生成代理类, 比使用Java反射效率要高; 唯一需要注意的是, CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类; 它的原理是: 动态的创建一份class(代理类) 继承/实现被代理的 类/接口 net.sf.cglib.proxy.Enhancer – 主要的增强类 net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类 它是Callback接口的子接口, 需要用户实现 net.sf.cglib.proxy.MethodProxyJDK的java.lang.reflect.Method类的代理类 可以方便的实现对源对象方法的调用,如使用: Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象, 也不会出现死循环的问题; net.sf.cglib.proxy.MethodInterceptor 接口是最通用的回调(callback)类型 它经常被基于代理的AOP用来实现拦截(intercept)方法的调用; 这个接口只定义了一个方法,public Object intercept(Object object, java.lang.reflect.Method method,Object[] args, MethodProxy proxy) throws Throwable; 第一个参数是代理对像, 第二和第三个参数分别是拦截的方法和方法的参数; 原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用, 或者使用 net.sf.cglib.proxy.MethodProxy对象调用; sf.cglib.proxy.MethodProxy通常被首选使用, 因为它更快; 一个动态代理例子 /** * CGLIB 动态代理实现 */ public static final void CJLIB_PROXY(){ Enhancer enhancer = new Enhancer(); /** * 设置父类 */ enhancer.setSuperclass(CarService.class); // 拦截回调处理器 MethodInterceptor handler = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object result ; System.out.println("增强处理 前"); //调用父对象 方法 result = proxy.invokeSuper(obj, args); System.out.println("增强处理 后"); return result; } }; // 设置 拦截回调处理器; 它可以是: * @see MethodInterceptor * @see NoOp * @see LazyLoader * @see Dispatcher * @see org.springframework.cglib.proxy.InvocationHandler * @see FixedValue enhancer.setCallback(handler); CarService proxyInstance = (CarService) enhancer.create(); proxyInstance.doProduct("粉色"); } 四、AOP 代理对象的方法调用过程 https://img2024.cnblogs.com/blog/3723410/202602/3723410-20260224191135364-2132190076.png ExposeInvocationInterceptor 的作用 把它放在第0位优先调用它, 把 AOP 调用的核心上下文(MethodInvocation) 存入 ThreadLocal<MethodInvocation> 中 org.springframework.aop.interceptor.ExposeInvocationInterceptor public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable { //全局单例实例(Spring AOP 所有代理都会默认添加这个拦截器) /** Singleton instance of this class. */ public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor(); /** * Singleton advisor for this class. Use in preference to INSTANCE when using * Spring AOP, as it prevents the need to create a new Advisor to wrap the instance. */ public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) { @Override public String toString() { return ExposeInvocationInterceptor.class.getName() +".ADVISOR"; } }; // 存储当前线程的 MethodInvocation 上下文 private static final ThreadLocal<MethodInvocation> invocation = new NamedThreadLocal<>("Current AOP method invocation"); /** * Ensures that only the canonical instance can be created. */ private ExposeInvocationInterceptor() { } @Override @Nullable public Object invoke(MethodInvocation mi) throws Throwable { MethodInvocation oldInvocation = invocation.get(); //存储在ThreadLocal的上下文关键信息 mi.getMethod();// 当前调用的方法 mi.getArguments();// 方法参数 mi.getThis(); // 目标对象(原 Bean) invocation.set(mi); try { return mi.proceed(); } finally { invocation.set(oldInvocation); } } ... 源码调试主入口 主入口构成一个责任链的形式 org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept @Override @Nullable public Object proceed() throws Throwable { /** * 这里 循环责任链 的调用 */ // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } /** * 这个`this.interceptorsAndDynamicMethodMatchers` 数组 就是通知(advised)列表 AspectJXXXAdvice (这里再封装为 XXXAdviceInterceptor) * 第0个是此前插入的 `ExposeInvocationInterceptor` 它哈都没干, 直接 proceed(); 回调回来; * * 这里累加 ++this.currentInterceptorIndex, 每次回调回来, 拿到下一个 `XXXAdviceInterceptor` 调用 */ Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); if (dm.matcher().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. return proceed(); } } else { /** * 将 `this` 传递进去, 各 XXXAdviceInterceptor 将使用这个参数, 回调回来 */ // 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); } } Around 通知 org.springframework.aop.aspectj.AspectJAroundAdvice#invoke @Override @Nullable public Object invoke(MethodInvocation mi) throws Throwable { if (!(mi instanceof ProxyMethodInvocation pmi)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } /** * 封装一个 ProceedingJoinPoint 参数, 这个就是给 环绕通知方法 接受的参数 */ ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi); JoinPointMatch jpm = getJoinPointMatch(pmi); // 环绕通知内部, 调用 `joinPoint.proceed();` 其实还是会回去责任链那里的, 并不是真正的调用原始方法 return invokeAdviceMethod(pjp, jpm, null, null); } After 通知 没啥重点 org.springframework.aop.aspectj.AspectJAfterReturningAdvice#afterReturning @Override public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable { if (shouldInvokeOnReturnValueOf(method, returnValue)) { invokeAdviceMethod(getJoinPointMatch(), returnValue, null); } } Throwing 通知 org.springframework.aop.aspectj.AspectJAfterThrowingAdvice#invoke @Override @Nullable public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } catch (Throwable ex) { if (shouldInvokeOnThrowing(ex)) { invokeAdviceMethod(getJoinPointMatch(), null, ex); } throw ex; } } 四、SpringAOP 的使用 一个案例 An AOP Example - https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/example.html 例 有一个 ProductServiceInter 接口 public interface ProductServiceInter { void doProduct(String color); } 有两个实现 public class CarService implements ProductServiceInter { public void doProduct(String color){ System.out.println("生产一辆 " + color + " 颜色的汽车"); } } public class BicycleService implements ProductServiceInter { public void doProduct(String color){ System.out.println("生产一辆 " + color + " 颜色的自行车"); } } 业务需求 调用 BicycleService#doProduct 方法前, 需要记录日志 调用 CarService#doProduct 方法前, 同样也需要记录日志 解决方式(过程式) ProductServiceInter carService = new CarService(); ProductServiceInter bicycleService = new BicycleService(); //....写日志 carService.doProduct("红色"); //....写日志 bicycleService.doProduct("黑色"); 问题 有几点不合理的地方: 写日志部分代码重复 大量的 service 都有此需要,又有少量的 service 没这个需要, 不好修改 AOP 概念术语 AOP Concepts - https://docs.spring.io/spring-framework/reference/core/aop/introduction-defn.html Advice /通知/建议/增强处理/... //即: 增强功能这一部分代码 joinpoint /连接点 //和方法有关的前前后后(抛出异常), 都是连接点; Pointcut /切入点 //筛选连接点, 即: 哪些方法需要被代理 Aspect /切面 //切面是通知(写日志) 和切入点(要被代理的方法) 的结合, 即:修改后的整个方法 //(有正常需要调用的(doProduct)代码,又有写日志的) target /目标/目标对象 //即: Service 它们都没有 写日志的功能 weaving /织入 //把切面应用到目标对象来创建新的代理对象的过程; AOP代理类 /AOP修改后的类 基于XML 的配置方式 Schema-based AOP Support public class AdviceService { public void before(){ System.out.println("前置通知(Before),它在执行方法之前调用"); } public void after(){ System.out.println("后置通知(After returning advice),它在执行匹配pointcut的方法之后调用"); } public void after_throwing(Exception ex){ System.out.println("异常通知(After throwing advice),它在执行匹配pointcut的方法时出现异常调用"); System.out.println("异常信息:"+ ex.getMessage() ); } public Object around(ProceedingJoinPoint mi){ System.out.println("环绕通知(Around advice)之前"); Object result = null; try { result = mi.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("环绕通知(Around advice)之后"); return result; } } <!-- 三个普通的bean --> <bean id="bicycleService" class="org.yang.learn.spring.aop.service.BicycleService"></bean> <bean id="carService" class="org.yang.learn.spring.aop.service.CarService"></bean> <bean id="adviceService" class="org.yang.learn.spring.aop.aspect.AdviceService"></bean> <!-- 需要引入aop 命名空间 xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd --> <aop:config> <!-- 把容器中的普通bean 变成一个切面bean --> <!-- 方式一: 切点, 切面 散装形式的定义 为了方便使用可以先定义 pointcut (切入点) <aop:pointcut expression="execution(* com.yang.service.Test*_Service.*(..))" id="bbb"/> <aop:aspect ref="myAdvice"> <aop:before method="log" pointcut-ref="bbb" /> </aop:aspect> --> <!-- 把容器中的普通bean 变成一个切面bean --> <!-- 方式二: 切点, 切面 集成形式的定义--> <aop:aspect ref="adviceService" id="beforeExample"> <!-- Before Advice: https://docs.spring.io/spring-framework/reference/core/aop/schema.html#aop-schema-advice-before 调用 pointcut 之前会 调用 aspect bean 的 before 方法 --> <aop:before method="before" pointcut="execution(* org.yang.learn.spring.aop.service.*.*(..))" /> <!-- After Returning Advice: https://docs.spring.io/spring-framework/reference/core/aop/schema.html#aop-schema-advice-after-returning 调用 pointcut 之后会 调用 aspect bean 的 after 方法 --> <aop:after-returning method="after" pointcut="execution(* org.yang.learn.spring.aop.service.*.*(..))" /> <aop:around method="around" pointcut="execution(* org.yang.learn.spring.aop.service.*.*(..))" /> </aop:aspect> </aop:config> public static void main(String[] args) { System.out.println("=========================================================="); System.out.println(""); ApplicationContext context = new ClassPathXmlApplicationContext("application-aop.xml"); /** * 如果目标bean有接口的话, 会使用JDK动态代理实现; * 获取的也是接口 (ProductServiceInter) */ ProductServiceInter carService = (ProductServiceInter) context.getBean("carService"); carService.doProduct("五颜六色"); System.out.println(""); System.out.println("=========================================================="); } 表达式 (execution) 使用 execution 表达式, 需要引入aop项目的 optional("org.aspectj:aspectjweaver") 依赖 execution 表达式的格式, 括号中各个pattern分别表示: 修饰符匹配(modifier-pattern?) 返回值匹配(ret-type-pattern) 类路径匹配(declaring-type-pattern?) 方法名匹配(name-pattern) 参数匹配 (param-pattern) 异常类型匹配(throws-pattern?) 其中后面跟着"?"的是可选项 定义时, 还可以使用&&, ||, ! 其他通知 (Advice) spring有如下几种 Advice (通知) 前置通知(Before advice) 在某连接点之前执行的通知. https://docs.spring.io/spring-framework/reference/core/aop/schema.html#aop-schema-advice-before 后置通知(After returning advice) 在某连接点正常完成后执行的通知. https://docs.spring.io/spring-framework/reference/core/aop/schema.html#aop-schema-advice-after-returning 异常通知(After throwing advice): 在方法抛出异常退出时执行的通知; 最终通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出); 环绕通知(Around Advice): 包围一个连接点的通知, 如方法调用; 这是最强大的一种通知类型; 环绕通知可以在方法调用前后完成自定义的行为; 它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行; <!-- 配置正常业务 bean --> <bean id="bicycleService" class="org.yang.learn.spring.aop.service.BicycleService"></bean> <bean id="carService" class="org.yang.learn.spring.aop.service.CarService"></bean> <!-- 配置义一个切面bean--> <bean id="myAdvice" class="org.yang.learn.spring.aop.aspect.AdviceService"></bean> <!-- 需要引入aop xsd --> <aop:config> <!-- 把容器中的普通bean 变成一个切面bean --> <aop:aspect ref="myAdvice"> <!-- 前置通知(Before) 后置通知(After returning advice) 异常通知(After throwing advice) 最终通知(After (finally) advice) 环绕通知(Around Advice) method 指定织入 pointcut 指定匹配目标类,方法 * 号为通配符 (..)不限定参数 --> <!--前置通知(Before) 只要 Before Advice执行完成, // 目标方法总会被调用 // 可以通过抛出异常来阻止目标方法的调用 <aop:before method="before" pointcut="execution(* com.yang.service.Test*_Service.before(..))" /> --> <!--后置通知(After returning advice) // 在目标方法调用之后,无论是否成功结束织入增加处理 // 不能阻止目标方法的调用 // 它有点类似 finlly 块 <aop:after method="after" pointcut="execution(* com.yang.service.Test*_Service.*(..))" /> --> <!--必须正常return, returning参考异常通知ex, // 即使没有返回值 也会执行, // 不同的是它可以访问返回值 <aop:after-returning method="" returning="ret"/> --> <!--异常通知(After throwing advice),它在执行匹配pointcut的方法时出现异常调用 // throwing="ex" 意味着after_throwing方法可以,public void after_throwing(ArithmeticException ex) // 定义 ex 参数接受该异常,注意 这个ex类型,只有发生ArithmeticException异常,类型相同,它才会调用 <aop:after-throwing method="after_throwing" pointcut="execution(* com.yang.service.Test1_Service.*(..))" throwing="ex"/> --> <!-- 最终通知(After (finally) advice) 自己看文档--> <!-- 环绕通知(Around Advice,在目标方法之前调用,它的处理方法必须包含ProceedingJoinPoint的形参 // 最强大通知, // 可以阻止目标方法的调用 // 可以方法,参数,返回值 // 甚至可以修改,参数,返回值 <aop:around method="around" pointcut="execution(* com.yang.service.Test1_Service.*(..))" /> --> </aop:aspect> </aop:config> 基于注解 的配置方式 @AspectJ support - https://docs.spring.io/spring-framework/reference/core/aop/ataspectj.html 启用注解支持 @Configuration @EnableAspectJAutoProxy public class AppConfig { } 配置切面 Aspect @Aspect @Component public class MyAdvice { } 配置切点 Pointcut @Aspect @Component public class MyAdvice { /** * 定义 pointcut (切入点) */ // @Pointcut("@annotation(net.jk.cloud.log.aop.log.Log)") //按照注解的方式, 只要在方法上加`net.jk.cloud.log.aop.log.Log`注解, 即会被织入 @Pointcut("execution(* com.yang.service.Test*_Service.*(..))")//按照表达式 public void logPointcut() { // 该方法无方法体,主要为了使用此切入点 } /** @Around("logPointcut()") 环绕通知使用 logPointcut() 切点; 相当于XML的 <aop:aspect ref="myAdvice"> <aop:around method="logAround" pointcut="execution(* com.yang.service.Test*_Service.*(..))" /> </aop:aspect> 例如 相对应的异常通知 @AfterThrowing(pointcut = "logPointcut()", throwing = "e") = <aop:after-throwing method="logAround" ... **/ @Around("logPointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { Object result = null; currentTime = System.currentTimeMillis(); result = joinPoint.proceed(); Log log = new Log(Log.TYRPE_INFO,System.currentTimeMillis() - currentTime); logService.save(getUsername(), StringUtils.getIP(RequestHolder.getHttpServletRequest()),joinPoint, log); return result; } } 配置通知 Advice @Aspect @Component public class MyAdvice { /** * 定义 pointcut (切入点) */ // @Pointcut("@annotation(net.jk.cloud.log.aop.log.Log)") //按照注解的方式, 只要在方法上加`net.jk.cloud.log.aop.log.Log`注解, 即会被织入 @Pointcut("execution(* com.yang.service.Test*_Service.*(..))")//按照表达式 public void logPointcut() { // 该方法无方法体,主要为了使用此切入点 } /** @Around("logPointcut()") 环绕通知使用 logPointcut() 切点; 相当于XML的 <aop:aspect ref="myAdvice"> <aop:around method="logAround" pointcut="execution(* com.yang.service.Test*_Service.*(..))" /> </aop:aspect> 例如, 相对应的异常通知 @AfterThrowing(pointcut = "logPointcut()", throwing = "e") = <aop:after-throwing method="logAround" ... **/ @Around("logPointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { Object result = null; currentTime = System.currentTimeMillis(); result = joinPoint.proceed(); Log log = new Log(Log.TYRPE_INFO,System.currentTimeMillis() - currentTime); logService.save(getUsername(), StringUtils.getIP(RequestHolder.getHttpServletRequest()),joinPoint, log); return result; } } 踩坑指南 spring boot 项目使用了AOP代理的Bean CglibAopProxy 方法是存在缓存的, 注意缓存 注意缓存 注意缓存! 另外: Spring 通过CGLIB 创建的代理类, 不会初始化代理类自身继承的任何成员变量, 包括final类型的成员变量! 正确使用AOP 需要避坑: 访问被注入的 Bean 时, 总是调用方法而非直接访问字段; 编写Bean时, 如果可能会被代理, 就不要编写 public final方法;