网站建设述职报告,拼多多运营怎么做,搭建的wordpress没显示出来,产品质量推广营销语Spring AOP 文章目录Spring AOP1.概念1.面向切面编程2.AOP的目的3.AOP实现的分类4.AOP 术语2. Spring AOP的特性1.能力与目标2.AOP机制1.理解SpringAOP的代理2.AOP代理类的自调用代码的粒度如何让自调用走代理#xff1f;3.Enabling AspectJ Support3. 定义切面与切点1. 声明切…Spring AOP 文章目录Spring AOP1.概念1.面向切面编程2.AOP的目的3.AOP实现的分类4.AOP 术语2. Spring AOP的特性1.能力与目标2.AOP机制1.理解SpringAOP的代理2.AOP代理类的自调用代码的粒度如何让自调用走代理3.Enabling AspectJ Support3. 定义切面与切点1. 声明切面切面实例化模型2.声明切点1.Pointcut注解抽取切点表达式2. 定义公共的切点4.切点表达式1.Spring支持的PCD1.execution: 用于匹配联结点即方法的执行SpringAOP最主要的用法。2.within: 匹配目标类。限制联结点必须出现在某些特定的类中这些类中所有的方法均会成为联结点。3.this: 通过代理类的类型来匹配。如果某个Bean的代理类属于该类型则拦截该Bean的方法。4.target: 通过目标类的类型来匹配。如果某个Bean即被代理的类属于该类型则拦截该Bean的方法。5.args: 通过联结点的形参来匹配。6.annotation: 通过联结点是否被相应的注解所修饰来进行匹配。7.within: 指定一个作用于类型的注解通过目标对象的类型是否属于被该注解修饰的子类型来进行匹配。8.target: 指定一个作用于类型的注解通过目标对象是否被该注解所修饰来进行匹配。9.args: 指定一个作用于类型的注解通过传入联结点的实参的运行时类型是否被该注解所修饰来进行匹配。10. Spring还支持一个额外的PCD5.增强1.增强的类型2.增强方法的特殊说明3.增强方法的参数1.JoinPoint2.ProceedingJoinPoint3.传递实参4.传递实参泛型5.参数绑定关系6.增强方法的执行顺序1.同一 aspect、不同 advice2.同一 advice、不同 aspect3.同一 aspect、同一 advice7.引入1.概念
AOP(Aspect Oriented Programming) 直译过来即为面向切面编程。AOP 是一种编程思想是面向对象编程OOP的一种补充提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中我们以类(class)作为我们的基本单元而 AOP 中的基本单元是切面(Aspect)。好比下图所谓切面相当于应用对象间的横切点我们可以将其单独抽象为单独的模块。 1.面向切面编程
在面向切面编程的思想里面可以把功能分为两种
核心业务登陆、注册、增、删、改、查等都叫核心业务周边功能日志、事务管理等为周边业务
在面向切面编程中核心业务功能和周边功能是分别独立进行开发两者不是耦合的。然后把切面功能和核心业务功能 “编织”在一起便谓之AOP。
2.AOP的目的
AOP 要达到的效果是保证开发者不修改源代码的前提下去为系统中的业务组件添加某种通用功能。即AOP能够将那些与业务无关却为业务模块所共同调用的逻辑或责任例如事务处理、日志管理、权限控制等封装起来便于减少系统的重复代码降低模块间的耦合度并有利于未来的可拓展性和可维护性。
在技术上AOP可以在程序运行期间在不修改源码的情况下对方法进行功能增强如此一来不难理解 AOP 其实就是代理模式的典型应用。
3.AOP实现的分类
按照 AOP 修改源代码的时机可以将其分为两类
静态 AOP 实现AOP 框架在编译阶段对程序源代码进行修改生成了静态的 AOP 代理类生成的 *.class 文件已经被改掉了需要使用特定的编译器。例如 AspectJ。动态 AOP 实现 AOP 框架在运行阶段动态生成代理对象在内存中以 JDK 或 CGlib 动态代理动态地生成 AOP 代理类。如 SpringAOP。
SpringAOP:
JDK 代理基于接口的动态代理技术。cglib 代理 基于父类的动态代理技术。
常用AOP实现比较
类别机制原理优点缺点静态AOP静态织入在编译期切面直接以字节码的形式编译到目标字节码文件中对系统无性能影响灵活性不足动态AOPJDK动态代理在运行期目标类加载后为接口动态生成代理类将切面织入到代理类中相对于静态AOP更加灵活1) 切入的关注点需要实现接口 2) 对系统有一点性能损耗动态字节码生成CGLIB在运行期目标类加载后动态生成目标类的子类将切面逻辑加入到子类中没有接口也可以织入扩展类的实例方法用final修饰时则无法进行织入自定义类加载器在运行期目标类加载前将切面逻辑加到目标字节码里可以对绝大部分类进行织入代码中如果使用了其他类加载器则这些类将不会织入字节码转换在运行期所有类加载器加载字节码前进行拦截可以对所有类进行织入
4.AOP 术语 Unfortunately, AOP terminology is not particularly intuitive. 谓词predicate在计算机语言的环境下谓词是指条件表达式的求值返回真或假的过程。 术语中文含义Aspect切面横跨多个类的某个功能如事务管理。从概念上它是切点和增强的结合。Join point联结点程序执行过程中的一个时机点。例如方法的调用、异常的抛出。 在 Spring AOP 中联结点总是方法的调用。Advice增强在某个联结点上某个切面执行的具体动作这里对advice译为增强为意译。 很多AOP框架包括Spring都把增强建模为一个拦截器并且相应地围绕联结点维护了一个拦截器链。Pointcut切点一个用来匹配联结点的谓词。 Advice is associated with a pointcut expression and runs at any join point matched by the pointcut. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching.Introduction引入引入是为一个类声明额外的方法或字段。 Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object.Target object目标对象要被切面增强的原始对象也常称作advised object增强对象这种称呼并不好有歧义。 由于Spring是基于运行时代理的机制实现AOP的因此目标对象总是一个proxied object被代理的对象。AOP ProxyAOP代理AOP为实现切面功能而创建的对象故名之代理对象。 在Spring中代理对象总是一个JDK代理或CGLIB代理对象。Weaving织入描述了把增强处理添加到目标对象、并创建一个被增强的对象代理这一过程不对应一份实体。 1) 织入可以发生在编译时、加载时、运行时 2) SpringAOP的织入总是发生在运行时。
增强的类型
前置增强返回后增强异常抛出增强后置增强环绕增强
2. Spring AOP的特性
1.能力与目标
Spring AOP does not need to control the class loader hierarchy and is thus suitable for use in a servlet container or application server.
Spring AOP当前只支持对Spring Bean的方法作为联结点不支持对字段的拦截尽管对字段拦截的支持不需要破坏Spring AOP的核心API。如果希望拦截对字段的访问与更新建议直接使用AspectJ。
SpringAOP使用时需要结合SpringIoC容器因此SpringAOP无法对非常细粒度的对象进行增强典型的例子就是domain objects对于这些场景选择AspectJ吧。
2.AOP机制
AOP技术在Spring中实现的内容Spring框架监控切点方法的执行一旦监控到切入点方法被运行即使用动态代理机制动态创建目标对象的代理对象根据增强类别在代理对象的相应位置将Advice对应的功能织入从而完成增强后的整个代码逻辑的执行。
Spring 的 AOP 实现底层就是对 JDK 代理、cglib 代理的方式进行了封装封装后我们只需要对需要关注的部分进行代码编写并通过配置的方式完成指定目标的方法增强。 JDK 代理基于接口的动态代理技术。cglib 代理 基于父类的动态代理技术。
默认情况下Spring 会根据目标类是否实现了接口来决定采用哪种动态代理的方式。如果一个对象没有实现任何接口则会使用CGLIB代理否则使用JDK代理。 当使用JDK代理的时候所有该目标对象实现的接口都会被代理 如果需要也可以强制使用CGLIB代理方法是设置proxy-target-class为true aop:aspectj-autoproxy proxy-target-classtrue/copyerrorcopiedSpring使用CGLIB代理时需注意如下事项 final方法无法被增强因为它们无法被在运行时生成的子类所覆盖 正常情况下CGLIB代理是通过Objenesis创建的但当JVM不允许绕过构造函数时SpringAOP会对构造器进行双重调用来达成目的此时Spring会记录相应的debug日志信息。 Objenesis是一个轻量的Java库作用是绕过构造器创建实例。
1.理解SpringAOP的代理
Spring AOP是基于代理的牢记这一点很重要这是本质特征
假定有一个纯天然的POJO类
public class SimplePojo implements Pojo {public void foo() {// this next method invocation is a direct call on the this referencethis.bar();}public void bar() {// some logic...}
}对于POJO类的实例pojo调用pojo的方法时毫无疑问会直接调用该对象的相应方法
public class Main {public static void main(String[] args) {Pojo pojo new SimplePojo();// this is a direct method call on the pojo referencepojo.foo();}
}然而如果pojo引用的是代理类的代理对象时调用方式会发生改变
public class Main {public static void main(String[] args) {ProxyFactory factory new ProxyFactory(new SimplePojo());factory.addInterface(Pojo.class);factory.addAdvice(new RetryAdvice());Pojo pojo (Pojo) factory.getProxy();// this is a method call on the proxy!pojo.foo();}
}However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls that it may make on itself.
Such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy.It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to run.
综上最好的方式永远是代码中不要出现有自调用的情况。其次如果真的迫不得已可以通过在代码中使用Spring提供的一些API来解决如下
这种方案首先使得代码与Spring发生了强耦合其次使得这个类本身知道了自己即将被代理后者与AOP的理念背道而驰了。
public class SimplePojo implements Pojo {public void foo() {// this works, but... gah!((Pojo) AopContext.currentProxy()).bar();}public void bar() {// some logic...}
}public class Main {public static void main(String[] args) {ProxyFactory factory new ProxyFactory(new SimplePojo());factory.addInterface(Pojo.class);factory.addAdvice(new RetryAdvice());factory.setExposeProxy(true);Pojo pojo (Pojo) factory.getProxy();// this is a method call on the proxy!pojo.foo();}
}最后需要强调的是上述自调用问题的根源是由于Spring AOP是基于代理这一机制实现的故而在AspectJ中不存在这一问题。
2.AOP代理类的自调用
这里所谓的自调用是指一个类的方法调用本类的其他方法。
代码的粒度
当一个切面对一个业务类生效时我们使用的业务类对象实际上是Spring帮我们生成的一个代理对象而这个代理的粒度是类级别的。
正因为AOP代理的粒度是类级别的所以在自调用时不会走其切面逻辑。例如Spring的事务管理中有个 Transactional 注解可以方便的管理事务其是基于 AOP 实现的该注解在这样的情况下会失效“外部类调用本类的一个没有 Transactional 注解的函数而该函数调用本类的一个有 Transactional 注解的函数”失效原因就是因为代理是类级别的。
Aspect
Component
public class Aspect {// 前置增强TestBefore(execution(* TargetBean.hi(..)))public void before(JoinPoint joinPoint){System.out.println(--------我是前置通知--------);}
}Component
public class TargetBean {// 一个切点方法public void hi() {System.out.println(hi);}// 一个非切点方法但调用了上面的切点方法public void hello() {System.out.println(hello);this.hi(); // 调用上述切点方法发现并未走其对应的增强方法}
}为什么要这样设计呢其实技术上也能实现自调用时也走切面逻辑比如 cglib 的 MethodInterceptor。然而有些场景自调用走代理更合适而另外一些场景不走代理更合适因此选择类级别的代理是权衡的结果。
如何让自调用走代理
有两种方式但其本质其实是一个道理即获取本类被代理后的对象。 自注入 Component
public class TargetBean {Autowiredprivate TargetBean self; // 注入自己此时注入的是代理后的对象public void hi() {System.out.println(hi);}public void hello() {System.out.println(hello);self.hi(); // 会调用到增强方法}
}手动获取当前代理AopContext.currentProxy() EnableAspectJAutoProxy(exposeProxy true) // 需开启exposeProxy true
Component
public class TargetBean {public void hi() {System.out.println(hi);}public void hello() {System.out.println(hello);TargetBean self (TargetBean) AopContext.currentProxy(); // 获取当前代理self.hi();}
}eg: 当调用B方法时会出现调用不到A方法的切面的问题。
解决方案
主动
主动获取代理方法要把下面的设为true 3.Enabling AspectJ Support
The AspectJ support can be enabled with XML- or Java-style configuration xml aop:aspectj-autoproxy/java-style Configuration
EnableAspectJAutoProxy
public class AppConfig {}3. 定义切面与切点
1. 声明切面
在Component修饰Bean的基础上使用Aspect注解。
在SpringAOP中切面自身不能作为目标对象被其他切面增强。因为Aspect注解除了将一个类标记为切面类以外还将这个类排除在了auto-proxying之外。
切面实例化模型
默认情况下每一个切面在Spring容器中都是单例的但是也可以定义不同生命周期的切面。Spring支持AspectJ的 singleton默认情况、perthis 和 pertarget 实例化模型但目前不支持 percflow, percflowbelow, pertypewithin。
在Aspect注解中声明一个perthis 分句就可以声明perthis切面了如下所示
Aspect(perthis(com.xyz.myapp.CommonPointcuts.businessService()))
public class MyAspect {private int someState;Before(com.xyz.myapp.CommonPointcuts.businessService())public void recordServiceUsage() {// ...}
}the effect of the perthis clause is that one aspect instance is created for each unique service object that performs a business service (each unique object bound to this at join points matched by the pointcut expression).The aspect instance is created the first time that a method is invoked on the service object. The aspect goes out of scope when the service object goes out of scope.Before the aspect instance is created, none of the advice within it runs. As soon as the aspect instance has been created, the advice declared within it runs at matched join points, but only when the service object is the one with which this aspect is associated.
pertarget 实例模型的用法同 perthis只不过它为每一个不同的目标对象来创建切面对象。
2.声明切点
1.Pointcut注解抽取切点表达式
当多个增强方法都锚定的是同一个切点时可以将相应切点表达式抽取出来避免多次重复硬编码。
抽取方式是设定一个方法起标识作用往往可以是空方法在该方法上使用Pointcut注解定义切点表达式然后在增强注解中进行引用即可
Aspect
Component
public class BuyAspect {Before(buyPoint())public void preAdvice() {System.out.println(前置增强);}After(buyPoint())public void afterAdvice() {System.out.println(后置增强);}Pointcut(execution(* com.chuan.service.IBuy.buy(..)))public void buyPoint() {}
}
引用切点表达式时java方法的作用域修饰符是起作用的即你不能在某一个类中引用另一个类里定义的private的Pointcut方法。
2. 定义公共的切点
有时候可以定义一些公共的切点(把很多切点都提取到一个类中)从而在项目中可以被作为一种公共资源来使用。
例如如下一个类中定义了若干切点表达式
package com.xyz.myapp;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;Aspect
public class CommonPointcuts {/*** A join point is in the web layer if the method is defined* in a type in the com.xyz.myapp.web package or any sub-package* under that.*/Pointcut(within(com.xyz.myapp.web..*))public void inWebLayer() {}/*** A join point is in the service layer if the method is defined* in a type in the com.xyz.myapp.service package or any sub-package* under that.*/Pointcut(within(com.xyz.myapp.service..*))public void inServiceLayer() {}/*** A join point is in the data access layer if the method is defined* in a type in the com.xyz.myapp.dao package or any sub-package* under that.*/Pointcut(within(com.xyz.myapp.dao..*))public void inDataAccessLayer() {}/*** A business service is the execution of any method defined on a service* interface. This definition assumes that interfaces are placed in the* service package, and that implementation types are in sub-packages.** If you group service interfaces by functional area (for example,* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then* the pointcut expression execution(* com.xyz.myapp..service.*.*(..))* could be used instead.** Alternatively, you can write the expression using the bean* PCD, like so bean(*Service). (This assumes that you have* named your Spring service beans in a consistent fashion.)*/Pointcut(execution(* com.xyz.myapp..service.*.*(..)))public void businessService() {}/*** A data access operation is the execution of any method defined on a* dao interface. This definition assumes that interfaces are placed in the* dao package, and that implementation types are in sub-packages.*/Pointcut(execution(* com.xyz.myapp.dao.*.*(..)))public void dataAccessOperation() {}}如果想给项目中的service层加入事务机制businessService可以进行如下配置
aop:configaop:advisorpointcutcom.xyz.myapp.CommonPointcuts.businessService()advice-reftx-advice/
/aop:configtx:advice idtx-advicetx:attributestx:method name* propagationREQUIRED//tx:attributes
/tx:advice4.切点表达式
1.Spring支持的PCD
Spring AOP 中的切点表达式(pointcut designators, PCD)是AspectJ的一个子集其支持的写法有
①execution: 用于匹配联结点即方法的执行SpringAOP最主要的用法。②within: 匹配目标类。限制联结点必须出现在某些特定的类中这些类中所有的方法均会成为联结点。③this: 通过代理类的类型来匹配。如果某个Bean的代理类属于该类型则拦截该Bean的方法。④target: 通过目标类的类型来匹配。如果某个Bean即被代理的类属于该类型则拦截该Bean的方法。⑤args: 通过联结点的形参来匹配。⑥annotation: 通过联结点是否被相应的注解所修饰来进行匹配。⑦within: 指定一个修饰类型的注解通过目标对象的类型是否属于被该注解修饰的子类型来进行匹配。⑧target: 指定一个修饰类型的注解通过目标对象是否被该注解所修饰来进行匹配。⑨args: 指定一个修饰类型的注解通过传入联结点的实参的运行时类型是否被该注解所修饰来进行匹配。
1.execution: 用于匹配联结点即方法的执行SpringAOP最主要的用法。 语法 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?namepattern(param-pattern) throws-pattern?) 其中各个部分支持使用通配符*。 modifiers-pattern, declaring-type-pattern, throws-pattern是可选的非必填项。 modifiers-pattern修饰符 ret-type-pattern返回值类型 必填 declaring-type-pattern类的类型类名如果存在需要使用.与namepattern分隔。包名与类名之间一个点.代表当前包下的类两个点..表示当前包及其子包下的类 namepattern方法名 (必填) param-pattern方法形参必填 () 匹配没有参数的方法(..) 匹配有任意数量参数的方法(*) 匹配有一个任意类型参数的方法(*,String) 匹配有两个参数的方法并且第一个为任意类型第二个为 String 类型 throws-pattern异常类型。省略时匹配任意类型
例子 The execution of any public method: execution(public * *(..))copyerrorcopiedThe execution of any method with a name that begins with set: execution(* set*(..))copyerrorcopiedThe execution of any method defined by the AccountService interface: execution(* com.xyz.service.AccountService.*(..))copyerrorcopiedThe execution of any method defined in the service package: execution(* com.xyz.service.*.*(..))copyerrorcopiedThe execution of any method defined in the service package or one of its sub-packages: execution(* com.xyz.service..*.*(..))2.within: 匹配目标类。限制联结点必须出现在某些特定的类中这些类中所有的方法均会成为联结点。 Any join point (method execution only in Spring AOP) within the service package: within(com.xyz.service.*)copyerrorcopiedAny join point (method execution only in Spring AOP) within the service package or one of its sub-packages: within(com.xyz.service..*)3.this: 通过代理类的类型来匹配。如果某个Bean的代理类属于该类型则拦截该Bean的方法。
Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface: this(com.xyz.service.AccountService)4.target: 通过目标类的类型来匹配。如果某个Bean即被代理的类属于该类型则拦截该Bean的方法。 Any join point (method execution only in Spring AOP) where the target object implements the AccountService interface: target(com.xyz.service.AccountService)5.args: 通过联结点的形参来匹配。 Any join point (method execution only in Spring AOP) that takes a single parameter and where the argument passed at runtime is Serializable: args(java.io.Serializable)注意通过args来匹配联结点与通过在execution中指定形参来匹配联结点的两种方式有所不同。args 是以运时行类型作为匹配条件而相应的execution则以声明方法时的方法签名作为匹配条件。
6.annotation: 通过联结点是否被相应的注解所修饰来进行匹配。 Any join point (method execution only in Spring AOP) where the executing method has an Transactional annotation: annotation(org.springframework.transaction.annotation.Transactional)7.within: 指定一个作用于类型的注解通过目标对象的类型是否属于被该注解修饰的子类型来进行匹配。 Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP). Any join point (method execution only in Spring AOP) where the declared type of the target object has an Transactional annotation: within(org.springframework.transaction.annotation.Transactional)8.target: 指定一个作用于类型的注解通过目标对象是否被该注解所修饰来进行匹配。 Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type. Any join point (method execution only in Spring AOP) where the target object has a Transactional annotation: target(org.springframework.transaction.annotation.Transactional)9.args: 指定一个作用于类型的注解通过传入联结点的实参的运行时类型是否被该注解所修饰来进行匹配。 Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types. Any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the argument passed has the Classified annotation: args(com.xyz.security.Classified)10. Spring还支持一个额外的PCD bean: 通过 Bean 的ID或名称来进行匹配。可使用通配符来匹配多个Bean。 This PCD lets you limit the matching of join points to a particular named Spring bean or to a set of named Spring beans (when using wildcards). The bean PCD has the following form: bean(idOrNameOfBean)The bean PCD operates at the instance level (building on the Spring bean name concept) rather than at the type level only (to which weaving-based AOP is limited). Instance-based pointcut designators are a special capability of Spring’s proxy-based AOP framework and its close integration with the Spring bean factory, where it is natural and straightforward to identify specific beans by name. 示例 Any join point (method execution only in Spring AOP) on a Spring bean named tradeService: bean(tradeService)Any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression *Service: bean(*Service)多个匹配之间我们可以使用链接符 、||、!来表示 “且”、“或”、“非”的关系。不过在使用 XML 文件配置时这些符号有特殊的含义所以相应使用 “and”、“or”、“not”来表示。
5.增强
1.增强的类型
SpringAOP中有5种增强方式其相应的注解如下其使用语法均为增强注解(切点表达式)
名称注解说明前置增强Before增强动作在切点方法调用之前执行。若前置增强抛异常则切点方法将无法执行。返回后增强AfterReturning增强动作在切点方法正常返回之后执行异常抛出增强AfterThrowing增强动作在切点方法抛出异常之后执行后置增强After增强动作无视切点方法是否正常返回或发生异常其总在切点方法执行退出后执行环绕增强Around增强动作会将切点方法封装起来可以选择在切点方法执行的前后各执行一段处理并可以选择是否执行该切点方法还可以自由地返回任意数据作为切点方法的返回值或直接抛出异常。环绕增强是最强大的一种增强。在选择增强的类型时在满足业务要求的情况下建议选择最小强度的增强类型。 2.增强方法的特殊说明
对于冠以以上增强注解的增强方法 联结点信息即JoinPoint其包含了类名、被切面的方法名参数等属性可供读取使用。 每个增强方法里都可以根据需要加上或者不加参数JoinPoint。如果加参数JoinPoint的话应该将其声明在第一个形参的位置。 对于Around方法其联结点参数类型还可以使用类型ProceedingJoinPoint该类型实际上是JoinPoint子接口。 AfterReturning方法里可以通过注解中的returning “xxx”以xxx形参接收切点方法的返回值同一个引用。 注意与此同时这也成为切点表达式匹配联结点的一个限制条件联结点的返回类型必须符合相应的类型。 AfterThrowing方法里可以通过注解中的throwing “xxx”以xxx形参获取切点方法的异常信息。 注意与此同时这也成为切点表达式匹配联结点的一个限制条件联结点的异常类型必须符合相应的类型。 Around方法 增强方法的返回值将会作为切点方法的返回值通常应该将返回值类型声明为Object类型。第一个形参必须是ProceedingJoinPoint类型。
Aspect
Component
public class SimpleBeanAspect {Pointcut(execution(* com.ggqq.springaop.controller.SimpleBean.aMethod(..)))private void aMethodPointcut() {}After(value aMethodPointcut())public void afterAMethod() {System.out.println(after A method);}AfterReturning(value aMethodPointcut(), returning res)public void afterAMethodReturning(JoinPoint joinPoint, String res) {System.out.println(after A method returning);}AfterThrowing(value aMethodPointcut(), throwing nullPointerException)public void afterAMethodThrowing(JoinPoint joinPoint, NullPointerException nullPointerException) {System.out.println(after A method throwing);}}3.增强方法的参数
1.JoinPoint
public interface JoinPoint {/*** 获取联结点方法运行时的入参列表*/Object[] getArgs();/*** 获取联结点的方法签名对象*/Signature getSignature();/*** 获取联结点所在的目标对象*/Object getTarget();/*** 获取代理对象本身*/Object getThis();/*** 获取联结点方法的一个描述信息*/String toString();String toShortString();String toLongString();String getKind();SourceLocation getSourceLocation();JoinPoint.StaticPart getStaticPart();public interface EnclosingStaticPart extends JoinPoint.StaticPart {}// ...
}2.ProceedingJoinPoint
package org.aspectj.lang;import org.aspectj.runtime.internal.AroundClosure;public interface ProceedingJoinPoint extends JoinPoint {/*** 通过反射执行目标对象的联结点处的方法*/Object proceed() throws Throwable;/*** 通过反射执行目标对象联结点处的方法不过使用新的入参替换原来的入参*/Object proceed(Object[] var1) throws Throwable;void set$AroundClosure(AroundClosure var1);default void stack$AroundClosure(AroundClosure arc) {throw new UnsupportedOperationException();}}3.传递实参
To make argument values available to the advice body, you can use the binding form of args.
If you use a parameter name in place of a type name in an args expression, the value of the corresponding argument is passed as the parameter value when the advice is invoked.
对于PCD里的args表达式this, target, within, target, annotation, args同理如果传入的不再是一个类型的全限定名而是一个普通形参名同时将原类型名与新的形参名对应着写到增强方法的参数列表中则可将目标方法执行时的实参传入增强方法中。例如
Before(com.xyz.myapp.CommonPointcuts.dataAccessOperation() args(account,..))
public void validateAccount(Account account) {// ...
}此时上例中切点表达式中的 args(account,..) 有两个作用
限制切点表达式匹配的联结点必须至少含有一个参数且为Account类型使得切点方法 Account 类型的参数值通过account变量传递给了增强方法。
另一种达成相同功能的类似写法是定义一个Pointcut方法来接收 Account 对象然而在相应的增强方法中引用该Pointcut方法。代码如下
Pointcut(com.xyz.myapp.CommonPointcuts.dataAccessOperation() args(account,..))
private void accountDataAccessOperation(Account account) {}Before(accountDataAccessOperation(account))
public void validateAccount(Account account) {// ...
}4.传递实参泛型
SpringAOP也能处理在类声明或方法形参里的泛型。对于如下的接口
public interface SampleT {void sampleGenericMethod(T param);void sampleGenericCollectionMethod(CollectionT param);
}通过在增强方法中显示指定类型可以令AOP只拦截对应类型的方法
Before(execution(* ..Sample.sampleGenericMethod(*)) args(param))
public void beforeSampleMethod(MyType param) {// Advice implementation
}不过这种方法对集合类型是无效的所以下述代码其实发挥不了希望的作用
Before(execution(* ..Sample.sampleGenericCollectionMethod(*)) args(param))
public void beforeSampleMethod(CollectionMyType param) {// Advice implementation
}To make this work, we would have to inspect every element of the collection, which is not reasonable, as we also cannot decide how to treat null values in general. To achieve something similar to this, you have to type the parameter to Collection? and manually check the type of the elements. 5.参数绑定关系 指的是注解中的变量与增强方法中形参的绑定 The parameter binding in advice invocations relies on matching names used in pointcut expressions to declared parameter names in advice and pointcut method signatures.
Parameter names are not available through Java reflection, so Spring AOP uses the following strategy to determine parameter names: 所有的增强注解及Pointcut注解都有一个 argNames 属性可以用来显式指定注解方法即增强方法中对应的形参名。如 Before(valuecom.xyz.lib.Pointcuts.anyPublicMethod() target(bean) annotation(auditable),argNamesbean,auditable)
public void audit(Object bean, Auditable auditable) {AuditCode code auditable.value();// ... use code and bean
}If the argNames attribute has not been specified, Spring AOP looks at the debug information for the class and tries to determine the parameter names from the local variable table. This information is present as long as the classes have been compiled with debug information (-g:vars at a minimum). If an AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without the debug information, you need not add the argNames attribute, as the compiler retain the needed information. 如果代码编译后无法找到任何debug信息SpringAOP将尝试自己去推断绑定变量与方法参数的配对比如如果注解中和方法中均只有一个参数这种配对是很明显的。如果在推断过程中发现这种绑定关系是模棱两可的Spring会抛出一个AmbiguousBindingException异常。 如果上述所有策略都失败Spring会抛出一个IllegalArgumentException异常。
值得一提的是如果增强方法中第一个形参是 JoinPoint, ProceedingJoinPoint, JoinPoint.StaticPart 类型这种匹配完全不需要依赖 argNames 属性因此 argNames 中可以不申明这个参数。
6.增强方法的执行顺序
1.同一 aspect、不同 advice 需要注意的是对于Around环绕增强如果增强方法内部没有调用 pjp.proceed()那么将导致其他的增强方法失去了判断执行的入口其他类型的增强advice将失效
2.同一 advice、不同 aspect
Spring可以支持多个切面同时运行如果刚好多个切面的切点相同切面的运行顺序便很重要了。默认情况下切面的运行顺序是混乱的undefined如果需要指定切面的运行顺序Spring AOP 通过指定aspect的优先级来控制。比如
Aspect 类添加注解org.springframework.core.annotation.Order使用注解value属性指定优先级。Aspect 类实现接口org.springframework.core.Ordered实现 Ordered 接口的 getOrder() 方法。
Order(1)
Aspect
Component
public class FirstAspect {……
}Order(2)
Aspect
Component
public class SecondAspect {……
}Order注解中的值就是切面的顺序但对于切面而言他们不是顺序执行的先后关系而是包含嵌套关系先入后出、后入先出。 3.同一 aspect、同一 advice
同一aspect、相同advice的执行顺序是无法确定的 Order 在advice方法上也无效因此尽量不要使用这种方式。
7.引入
引入指一个切面能够为目标对象实现引入一个指定的接口。 Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a given interface, and to provide an implementation of that interface on behalf of those objects. 创建引入使用 DeclareParents 注解该注解的作用为某一个类目标对象声明一个新的父接口
此注解修饰的是字段类型即是待实现的父接口的类型value属性是一个AspectJ的type pattern用以匹配目标对象defaultImpl是为指定的父接口提供一个默认的实现类
例如给定一个UsageTracked接口及其实现类 DefaultUsageTracked如下切面宣告了所有的service包下的接口的实现类同时实现了UsageTracked接口
Aspect
public class UsageTracking {DeclareParents(valuecom.xzy.myapp.service.*, defaultImplDefaultUsageTracked.class)public static UsageTracked mixin;Before(com.xyz.myapp.CommonPointcuts.businessService() this(usageTracked))public void recordUsage(UsageTracked usageTracked) {usageTracked.incrementUseCount();}}