面向切面编程

Aop是一种编程思想,面向切面编程的编程思想。

深入理解Android之AOP

​ 具体实现的框架有:

方法 说明 原理 实现 特点
APT 注解处理器 编译器通过注解采集信息,生成Java/Class文件 ButterKnife,Glide,EventBus3 基于Javac,但是无法修改已经存在的代码的内部结构
AspectJ 面向切面编程 在编译后修改/生成指定的Class Hugo 功能强大,底层原理利用ASM,不够轻量
Javassit/ASM 操作class的框架 按照Class文件的格式,解析修改生成class Qzone超级补丁,TInker热修复 Javassit的java Api更易于使用,ASM性能更好

AspectJ


      这个框架有几个重要的概念,JoinPoints:切入点,也就是在那个方法进行切入。Advice:切入的方式,是在这个方法之前切入还是之后切入,还是周围切入也就是之前和之后同时切入。

在Idea使用AspecJ框架流程:

  1. 导入库文件:aspectjrt.jar文件
img-0 img-0
  1. 导入编译工具aspectjtools.jar
img-0

​ 编译程序改完后我们就可以新建Aspect类:

img-0
img-0

​ 至此我们就可以使用aspectj框架了。

@Pointcut 的 12 种用法,你知道几种? - 知乎 (zhihu.com)

切入点标签

  • @Pointcut

    ​ 用来指定切入点,他的作用仅仅用来帮我们定义切入点,不能在这个切入点里面写任何代码,他是搭配@Before @After @Around标签来使用的。

    1
    2
    3
    4
    5
    6
    7
    @Aspect
    public class Aspect1 {
    @Pointcut("execution(* main.Service1.*(..))")
    public void pointcut1(JoinPoint joinPoint){
    System.out.println("PointCut执行了");
    }
    }

    ​ 按照这个例子写我们的程序会报错:Pointcuts without an if() expression should have an empty method body

    ​ 正确的写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    @Aspect
    public class Aspect1 {
    @Pointcut("execution(* main.Service1.*(..))")
    public void pointcut1(){
    }

    @Before(value = "pointcut1()")
    public void before(JoinPoint joinPoint){
    System.out.println("前置通知"+joinPoint);
    }

    @After(value = "pointcut1()")
    public void after(JoinPoint joinPoint){
    System.out.println("后置通知"+joinPoint);
    }
    //@4:定义了一个异常通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
    @AfterThrowing(value = "pointcut1()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
    //发生异常之后输出异常信息
    System.out.println(joinPoint + ",发生异常:" + e.getMessage());
    }
    }
    public class Test {
    public static void main(String[] args) {
    Service1 service1 = new Service1();
    service1.m1();
    }
    }
    // 输出结果:
    // 前置通知execution(void main.Service1.m1())
    // 我是m1方法
    // 后置通知execution(void main.Service1.m1())

    在我们找到切入点的时候

  • @Before

  • @After

  • @Around

  • @AfterReturning

  • @AfterThrowing

注意这些标签都是用来帮助我们去寻找插入点 也就是 切入点

切入点的标签知道了,现在就是表达式标签了。表达式标签也就是刚才我们写在@Pointcut括号里面的那一堆东西。

表达式标签

标签 描述
execution 用于匹配方法执行的连接点
within 用于匹配指定类型内的方法执行
this 用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象类型匹配,这样就可能包括引入接口*类型匹配
target 用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配,这样就不包括引入接口类型匹配
args 用于匹配当前执行的方法传入的参数为指定类型的执行方法
@within 用于匹配所以持有指定注解类型内的方法
@target 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args 用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation 用于匹配当前执行方法持有指定注解的方法
bean Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

表达式标签使用介绍:

executation

1
2
executation(@注解? 修饰符匹配? 返回值类型 类路径?方法名匹配 异常类型匹配?)
// 带问号的是不必须参数

案例:

表达式 描述
public *.*(..) 任何公共方法执行
* com.pince..IPService.*() com.pince包及所有子包下IPService接口中的任何任何无参方法
* com.pince..*.*(...) com.pince包及所有子包下任何类的任何方法
* com.pince..IPSercice.() com.pince包及所有子包下IPService接口的任何只有一个参数方法
* com.pince..IPSercice+.*() com.pince包及所有子包下IPService接口及子类型的任何无参方法
* Service1.*(String) 匹配Service1中只有一个参数且为String的方法
* Service1.*(*,String) 匹配Service1中只有两个参数且第二份参数为String的方法
* Service1.*(..,String) 匹配Service1中最后要给参数为String的方法

within

用法:within(类型表达式):目标对象target的类型是否和within中指定的类型匹配

匹配原则:target.getClass().equals(within表达式中指定的类型)

自我解释:只匹配对应类的数据,你不是目标类我就不会匹配上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.javacode2018.aop.demo9.test2;

public class C1 {
public void m1() {
System.out.println("我是m1");
}

public void m2() {
System.out.println("我是m2");
}
}
public class C2 extends C1 {
@Override
public void m2() {
super.m2();
}

public void m3() {
System.out.println("我是m3");
}
}
@Aspect
public class AspectTest2 {

@Pointcut("within(C1)") //@1
public void pc() {
}

@Before("pc()") //@2
public void beforeAdvice(JoinPoint joinpoint) {
System.out.println(joinpoint);
}

}
// 输出结果:我是m1 我是m2 我是m3
// 看结果可知并没有走到切面里面去
// 修改
@Pointcut("within(C1+)")
@Pointcut("within(C2)")

Demo

需求1:自定义注解,然后获取被注解标记的方法的入参,注解值

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface note2 {
String value();
}
1
2
3
4
5
6
7
public class Service2 {
@note2(value = "我是权限")
public String m3(int num){
System.out.println("我是m3方法");
return "我是返回值";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class Aspect1 {
//找到被note2标记的方法 并获得注解的值
@Pointcut("execution(@main.note2 String *(..)) && @annotation(noValue)&&args(num)")
public void logPoint(note2 noValue,int num){
}

@Around(value = "logPoint(noValue,num)")
public void aopFour(JoinPoint joinPoint,note2 noValue,int num) {
System.out.println(noValue.value()+" "+num);
}
}
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Service2 service2 = new Service2();
service2.m3(90);
}
}
// 执行结果:
// 我是权限 90

上面这个案例可以看到我们的Service2的m3方法是有返回值的,那我们在切面里面想获取返回值可以这么修改:

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class Aspect1 {
//找到被note2标记的方法 并获得注解的值
@Pointcut("execution(@main.note2 String *(..)) && @annotation(noValue)&&args(num)")
public void logPoint(note2 noValue,int num){
}

@AfterReturning(value = "logPoint(noValue,num)",returning = "rvt")
public void aopFour(JoinPoint joinPoint,note2 noValue,int num,String rvt) {
System.out.println(noValue.value()+" "+num+" "+rvt);
}
}

AspectJ 命令常用参数介绍

1 -inpath: .class文件路径,可以是在jar文件中也可以是在文件目录中,路径应该包含那些AspectJ相关的文件,只有这些文件才会被AspectJ处理。输出文件会包含这些.class 。该路径就是一个单一参数,多个路径的话用分隔符隔开。

2 -classpath: 指定去哪找用户使用到的.class文件,路径可以是zip文件也可以是文件目录,该路径就是一个单一参数,多个路径的话用分隔符隔开。

3 -aspectPath: 需要被处理的切面路径,存在于jar文件或者文件目录中。在Andorid中使用的话一般指的是被@Aspect注解标示的class文件路径。需要注意的是编译版本需要与Java编译版本一致。classpath指定的路径应该包含所有的aspectpath指定的.class文件。不过默认情况下,inPath和aspectPath中的路径不一定非要放置在classPath中,因为编译器会自动处理把它们加入。路径格式与classpath和inpath样,都需要用分隔符隔开。

4 **-bootClasspath: ** 重载跟VM相关的bootClasspath,例如在Android中使用android-27的源码进行编译。路径格式与之前一样。

5 -d: 指定由AspectJ处理后的.class文件存放目录,如果不指定的话会放置在当前的工作目录中。

6 -outjar: 指定被AspectJ处理后的jar包存放的文件目录,