aop切⾯排除某个类_AOP你看这⼀篇就够了
⽹上很多⼈在介绍AOP时都这样说:⾯向切⾯编程,通过预编译⽅式和运⾏期动态代理实现程序功能的统⼀维护的⼀种技术。个⼈认为这句话是错误。AOP和OOP⼀样,是⼀种程序设计思想,⽽⾮技术⼿段。
程序设计有六⼤原则,其中第⼀原则就是 单⼀职责原则 。意思就是⼀个类只负责⼀件事情。这与OOP的封装特性相得益彰。在这个条件下,我们的程序会被分散到不同的类、不同的⽅法中去。这样做的好处是降低了类的复杂性,提⾼了程序的可维护性。但是同时,它也使代码变得啰嗦了。例如,我们要为⽅法添加调⽤⽇志,那就必须为所有类的所有⽅法添加⽇志调⽤,尽管它们都是相同的。为了解决上述问题,AOP应运⽽⽣了。
AOP旨在将 横切关注点 与业务主体进⾏分类,从⽽提⾼程序代码的模块化程度。横切关注点是⼀个抽象的概念,它是指那些在项⽬中贯穿多个模块的业务。上个例⼦中⽇志功能就是⼀个典型的横切关注点。
AOP的⼏种实现⽅式
动态代理
动态代理是⼀种设计模式。它有以下特征:
我们不需要⾃⼰写代理类。
运⾏期通过接⼝直接⽣成代理对象。
运⾏期间才确定代理哪个对象。
以下⾯这个例⼦为例,我们看⼀下动态代理的类图结构。
通常我们的APP都有⼀部分功能要求⽤户登录之后才能访问。如修改密码、修改⽤户名等功能。当⽤户打算使⽤这些功能时,我们⼀般要对⽤户的登录状态进⾏判断,只有⽤户登录了,才能正常使⽤这些功能。⽽如果⽤户未登录,我们的APP要跳转到登录页。就以修改密码为例我们看⼀下动态代理的类图。
InvocationHandler是Java JDK提供的动态代理的⼊⼝,⽤来对被代理对象的⽅法做处理。
代码如下:
经过这样封装之后,检查登录跳转登录页的逻辑作为 横切关注点 就和业务主体进⾏了分离。当有新的需求需要登录检查时,我们只需要通过LoginCheckHandler⽣成新的代理对象即可。
APT
APT(Annotation Processing Tool)是⼀种编译期注解处理技术。它通过定义注解和处理器来实现编译期⽣成代码的功能,并且将⽣成的代码和源代码⼀起编译成.class⽂件。通过APT技术,我们将 横切关注点 封装到注解处理器中,从⽽实现 横切关注点 与业务主体的分离。更详细的介绍请移步 Android编译期插桩,让程序⾃⼰写代码(⼀) 。
AspectJ
AspectJ就是⼀种编译器,它在Java编译器的基础上增加了关键字识别和编译⽅法。因此,AspectJ可以编译Java代码。它还提供了Aspect程序。在编译期间,将开发者编写的Aspect程序织⼊到⽬标程序中,
扩展⽬标程序的功能。开发者通过编写AspectJ程序实现AOP 功能。更详细的介绍请移步 Android编译期插桩,让程序⾃⼰写代码(⼆) 。
Transform + Javassist/ASM
Transform是Android Gradle提供的,可以操作字节码的⼀种⽅式。App编译时,源代码⾸先会被编译成class,然后再被编译成dex。在class编译成dex的过程中,会经过⼀系列 Transform 处理。 Javassist/ASM 是⼀个能够⾮常⽅便操作字节码的库。我们通过它们可以修改编译的.class⽂件。
横切关注点
影响应⽤多处的功能(⽇志、事务、安全)
增强(Advice)
增强定义了切⾯要完成的功能以及什么时候执⾏这个功能。
Spring 切⾯可以应⽤ 5 种类型的增强:
前置增强(Before) 在⽬标⽅法被调⽤前调⽤增强功能
后置增强(After) 在⽬标⽅法完成之后调⽤增强, 不关注⽅法输出是什么 。
返回增强(After-returning) 在⽬标⽅法成功执⾏之后调⽤增强
异常增强(After-throwing) 在⽬标⽅法抛出异常后调⽤增强
环绕增强(Around) 在被增强的⽅法调⽤之前和调⽤之后执⾏⾃定义⾏为,即包括前置增强和后置增强。
连接点(Join Point)
应⽤中每⼀个有可能会被增强的点被称为连接点。
切点(Pointcut)
切点是规则匹配出来的连接点。
切⾯(Aspect)
切⾯是增强和切点的结合,定义了在何时和何处完成其功能。
引⼊(Introduction)
引⼊允许我们向现有的类中添加新⽅法和属性。可以在不修改现有的类的情况下,让类具有新的⾏为和状态。
织⼊(Weaving)
织⼊是把切⾯应⽤到⽬标对象中并创建新的代理对象的过程。在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
编译器:切⾯在⽬标类编译时织⼊。这种⽅式需要特殊的编译器。AspectJ 的织⼊编译器就是以这种⽅式织⼊切⾯的。
类加载器:切⾯在⽬标类加载到 JVM 时被织⼊。这种⽅式需要特殊的类加载器(ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5 的加载时织⼊(LTW)⽀持以这种⽅式织⼊。
运⾏期:切⾯在应⽤运⾏时的某个时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP 容器会为⽬标对象动态地创建⼀个代理对象。
Spring AOP 就是以这种⽅式织⼊切⾯的。
Spring 对 AOP 的⽀持
Spring 对 AOP 的⽀持在很多⽅⾯借鉴了 AspectJ 项⽬。⽬前 Spring 提供了 4 种类型的 AOP ⽀持:
基于代理的经典 AOP
纯 POJO 切⾯
@AspectJ 注解驱动的切⾯
注⼊式 AspectJ 切⾯
Spring AOP 构建在动态代理基础之上,因此 Spring 对 AOP 的⽀持局限于⽅法拦截。
运⾏时增强
通过在代理中包裹切⾯,Spring 在运⾏期把切⾯织⼊到 Spring 管理的 bean 中。代理类封装了⽬标类,并拦截被增强⽅法的调⽤,再把调⽤转发给真正的⽬标 bean。在代理拦截到⽅法调⽤时,在调⽤⽬标 bean ⽅法之前,会执⾏切⾯逻辑。
直到应⽤需要代理的 bean 时,Spring 才创建代理对象。如果使⽤ ApplicationContext 的话,在 ApplicationContext 从 BeanFactory 中加载所有 bean 的时候,Spring 才会创建被代理的对象。
spring aop应用场景⽅法级别的连接点
Spring 基于动态代理实现 AOP,所以 Spring 只⽀持⽅法连接点。其他的 AOP 框架⽐如 AspectJ 与 JBoss,都提供了字段和构造器接⼊点,允许创建细粒度的增强。
切点表达式
Spring AOP 中,使⽤ AspectJ 的切点表达式来定义切点。Spring 只⽀持 AspectJ 切点指⽰器(pointcut designator)的⼀个⼦集。
指⽰器
AspectJ 指⽰器描述arg( )限制连接点匹配参数为指定类型的执⾏⽅法execution( )⽤于匹配连接点this指定匹配 AOP 代理的 bean 引⽤的类型target指定匹配对象为特定的类within( )指定连接点匹配的类型@annotation匹配带有指定注解的连接点
编写切点
package concert;public interface Performance { public void perform();}复制代码
Performance 类可以代表任何类型的现场表演,⽐如电影、舞台剧等。现在编写⼀个切点表达式来限定 perform() ⽅法执⾏时触发的增强。
execution(* concert.Performance.perform(..))复制代码
每个部分的意义如下图所⽰:
也可以引⼊其他注解对匹配规则做进⼀步限制。⽐如
execution(* concert.Performance.perform(..)) && within(concert.*)复制代码
within() 指⽰器限制了切点仅匹配 concert 包。
Spring 还有⼀个 bean() 指⽰器,允许我们在切点表达式中使⽤ bean 的 ID 表⽰ bean。
execution(* concert.Performance.perform(..)) && bean('woodstock')复制代码
以上的切点就表⽰限定切点的 bean 的 ID 为 woodstock 。
使⽤注解创建切⾯
定义切⾯
在⼀场演出之前,我们需要让观众将⼿机静⾳且就座,观众在表演之后⿎掌,在表演失败之后可以退票。在观众类中定义这些功能。
@Aspectpublic class Audience {  @Pointcut("execution(* concert.Performance.perform(..)))") public void performance(){} @Before("performance()") public
@AspectJ 注解表名了该类是⼀个切⾯。 @Pointcut 定义了⼀个类中可重⽤的切点,写切点表达式时,如果切点相同,可以重⽤该切点。
其余⽅法上的注解定义了增强被调⽤的时间,根据注解名可以知道具体调⽤时间。
到⽬前为⽌, Audience 仍然只是 Spring 容器中的⼀个 bean。即使使⽤了 AspectJ 注解,但是这些注解仍然不会解析,因为⽬前还缺
乏代理的相关配置。
如果使⽤ JavaConfig,在配置类的类级别上使⽤ @EnableAspectJAutoProxy 注解启⽤⾃动代理功能。
@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }复制代码如果使⽤ xml ,那么需要引⼊ 元素。
<?xml version="1.0" encoding="UTF-8"?>复制代码
环绕增强
环绕增强就像在⼀个增强⽅法中同时编写了前置增强和后置增强。
@Aspectpublic class Audience { @Pointcut("execution(* concert.Performance.perform(..)))") public void performance(){} @Around("performance()") public
可以看到,这个增强达到的效果与分开写前置增强与后置增强是⼀样的,但是现在所有的功能都位于同⼀个⽅法内。 注意该⽅法接收ProceedingJoinPoint 作为参数,这个对象必须要有,因为需要通过它来调⽤被增强的⽅法。 注意,在这个⽅法中,我们可以控制不调⽤
proceed() ⽅法,从⽽阻塞对增强⽅法的访问。同样,我们也可以在增强⽅法失败后,多次调⽤ proceed() 进⾏重试。
增强⽅法参数
修改 Perform#perform() ⽅法,添加参数
package concert;public interface Performance { public void perform(int audienceNumbers);}复制代码
我们可以通过切点表达式来获取被增强⽅法中的参数。
@Pointcut("execution(* concert.Performance.perform(int)) && args(audienceNumbers)))") public void performance(int audienceNumbers){}复制代码
注意,此时⽅法接收的参数为 int 型, args(audienceNumbers) 指定参数名为 audienceNumbers ,与切点⽅法签名中的参数匹配,该
参数不⼀定与增强⽅法的参数名⼀致。
引⼊增强
切⾯不仅仅能够增强现有⽅法,也能为对象新增新的⽅法。 我们可以在代理中暴露新的接⼝,当引⼊接⼝的⽅法被调⽤时,代理会把此调
⽤委托给实现了新接⼝的某个其他对象。实际上,就是⼀个 bean 的实现被拆分到多个类中了。 定义 Encoreable 接⼝,将其引⼊到
Performance 的实现类中。
public interface Encoreable { void performEncore();}复制代码
创建⼀个新的切⾯
@Aspectpublic class EncoreableIntroducer { @DeclareParents(value = "concert.Performance+

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。