面向切面编程
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框架流程:
- 导入库文件:aspectjrt.jar文件
- 导入编译工具aspectjtools.jar
编译程序改完后我们就可以新建Aspect类:
至此我们就可以使用aspectj框架了。
@Pointcut 的 12 种用法,你知道几种? - 知乎 (zhihu.com)
切入点标签
@Pointcut
用来指定切入点,他的作用仅仅用来帮我们定义切入点,不能在这个切入点里面写任何代码,他是搭配@Before @After @Around标签来使用的。
1
2
3
4
5
6
7
public class Aspect1 {
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
public class Aspect1 {
public void pointcut1(){
}
public void before(JoinPoint joinPoint){
System.out.println("前置通知"+joinPoint);
}
public void after(JoinPoint joinPoint){
System.out.println("后置通知"+joinPoint);
}
//@4:定义了一个异常通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
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 | 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 | package com.javacode2018.aop.demo9.test2; |
Demo
需求1:自定义注解,然后获取被注解标记的方法的入参,注解值
1 |
|
1 | public class Service2 { |
1 |
|
1 | public class Test { |
上面这个案例可以看到我们的Service2的m3方法是有返回值的,那我们在切面里面想获取返回值可以这么修改:
1 |
|
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包存放的文件目录,