AOP全称呼 Aspect Oriented Programming ,国内大致译作 面向切面编程 ,跟OOP(面向对象编程思想)一样是一种编程思想,两者间相互补充。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
说人话的讲法可以大致这样说:在一处地方编写代码,然后自动编译到你指定的方法中,而不需要自己一个方法一个方法去添加。这就是面向切面编程。
AOP既然是一种思想,那么就有多种对这种思想的实现。其实这个我并没有做调研,推荐一下 juejin.im/post/5c0153… 这篇文章中有对AOP的实现方案有一个全面的展示。
日志记录,性能统计,安全控制,事务处理,异常处理,热修复,权限控制等等等 将这些行为代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
最简单日常开发需求,比如 对点击事件进行埋点上传行为数据、对方法进行耗时的统计、防止点击事件重复 等。 假设要埋点的方法有几百个那在每个方法都进行同样的编码不仅显得臃肿,并且当需求变更的时候,涉及更改的地方有几百个想想都觉得头疼。
这个是时候面向切面编程的作用就显得非常重要了。
给一段AspectJ的代码展示一下 加深印象:
@Aspect // 切面类 类下可以定义多个切入点和通知(引介) public class TestAnnoAspectJava { //自定义切点 @Pointcut("execution(* com.mzs.aopstudydemo.MainJavaActivity.threadTest())") public void pointcut(){ } //自定义切点 @Pointcut("execution(* com.mzs.aopstudydemo.MainJavaActivity.stepOn1(..))") public void pointcutOn(){ } //在切点pointcut()前面运行 @Before("pointcut()") public void before(JoinPoint point) { } //在切点pointcut()中运行,围绕的意思 //需要注意的是这个记得写 joinPoint.proceed(); // 写在代码后面就是在切入原方法前面运行 @Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { } //在切点pointcut()方法后面运行 @After("pointcut()") public void after(JoinPoint point) { } //在切点pointcut()方法返回后运行 @AfterReturning("pointcut()") public void afterReturning(JoinPoint point, Object returnValue) { } //在切点pointcut()抛异常后运行 @AfterThrowing(value = "pointcut()", throwing = "ex") public void afterThrowing(Throwable ex) { } } 复制代码
<切入点指示符> (<@注解符>?<修饰符>? <返回类型> <方法名>(<参数>) <异常>?) 复制代码
注意:注解符、 修饰符、异常 、参数(没有参数的时候)可以省略,其它的不能省略
示例:
//正常方法等的切点 @Pointcut("execution(* com.mzs.aopstudydemo.MainJavaActivity.threadTest())") public void pointcut(){ } //注解的切点 @Pointcut("execution(@com.mzs.aopstudydemo.CheckLogin * *(..))") public void checkLogin() { } 复制代码
*
:匹配任何字符;
…
:匹配多个任何字符,如在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数。
+
:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
示例:
1. 匹配返回任何类型的修饰符,跟指定java文件下的`stepOn`开头的方法名 @Pointcut("execution(* com.mzs.aopstudydemo.MainJavaActivity.stepOn*(..))") public void pointcutOn() { } 2. 匹配com.mzs.aopstudydemo包下的所有String返回类型的方法 @Pointcut("execution(String com.mzs.aopstudydemo..*(..))") public void afterReturning(JoinPoint point, Object returnValue) { } 3. 匹配所有public方法,在方法执行之前打印"YOYO"。 @Before("execution(public * *(..))") public void before(JoinPoint point) { System.out.println("YOYO"); } 4. 匹配com.mzs包及其子包中的所有方法,当方法抛出异常时,打印"ex = 报错信息"。 @AfterThrowing(value = "execution(* com.mzs..*(..))", throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("ex = " + ex.getMessage()); } 复制代码
切入点指示符有好多,这里只用到了 execution
其它的大家看一下 blog.csdn.net/zhengchao19… 这里就不展示了 有兴趣的同学看一下这个文章
AspectJ
是一个实现AOP的思想的框架,完全兼容 Java
,它有一个专门的编译器用来生成遵守 Java
字节编码规范的 Class
文件,只需要加上 AspectJ
提供的注解跟一些简单的语法就可以实现绝大部分功能上的需求了。
Android Studio与eclipse的导入方式不同,这里我展示的是Android studio的。(eclipse的话,麻烦同学百度下吧~~)
module
的 build.gradle
下面添加 dependencies { ... implementation 'org.aspectj:aspectjrt:1.8.9' } 复制代码
module
的 build.gradle
下面添加(跟 android {}
同级) buildscript { repositories { mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main final def log = project.logger final def variants = project.android.applicationVariants variants.all { variant -> if (!variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") return; } JavaCompile javaCompile = variant.javaCompile javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.8", "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] log.debug "ajc args: " + Arrays.toString(args) MessageHandler handler = new MessageHandler(true); new Main().run(args, handler); for (IMessage message : handler.getMessages(null, true)) { switch (message.getKind()) { case IMessage.ABORT: case IMessage.ERROR: case IMessage.FAIL: log.error message.message, message.thrown break; case IMessage.WARNING: log.warn message.message, message.thrown break; case IMessage.INFO: log.info message.message, message.thrown break; case IMessage.DEBUG: log.debug message.message, message.thrown break; } } } } 复制代码
TestAnnoAspectJava.java
类,并创建切点 /** * Create by ldr * on 2020/1/8 9:26. */ @Aspect public class TestAnnoAspectJava { @Pointcut("execution(* com.mzs.aopstudydemo.MainJavaActivity.test())") public void pointcut() { } @Before("pointcut()") public void before(JoinPoint point) { System.out.println("@Before"); } @Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("@Around"); joinPoint.proceed(); } @After("pointcut()") public void after(JoinPoint point) { System.out.println("@After"); } @AfterReturning("pointcut()") public void afterReturning(JoinPoint point, Object returnValue) { System.out.println("@AfterReturning"); } @AfterThrowing(value = "pointcut()", throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("@afterThrowing"); System.out.println("ex = " + ex.getMessage()); } } 复制代码
com.mzs.aopstudydemo.MainJavaActivity
定义方法 public void test() { System.out.println("Hello,I am LIN"); } -------------------打印的信息 2020-01-09 15:38:53.903 18238-18238/com.mzs.aopstudydemo I/System.out: @Before 2020-01-09 15:38:53.903 18238-18238/com.mzs.aopstudydemo I/System.out: @Around 2020-01-09 15:38:53.903 18238-18238/com.mzs.aopstudydemo I/System.out: Hello,I am LIN 2020-01-09 15:38:53.903 18238-18238/com.mzs.aopstudydemo I/System.out: @After 2020-01-09 15:38:53.903 18238-18238/com.mzs.aopstudydemo I/System.out: @AfterReturning 复制代码
反编译看一下生成的 test
方法的源码:
public void test() { JoinPoint joinPoint = Factory.makeJP(ajc$tjp_0, this, this); try { TestAnnoAspectJava.aspectOf().before(joinPoint); test_aroundBody1$advice(this, joinPoint, TestAnnoAspectJava.aspectOf(), (ProceedingJoinPoint)joinPoint); } finally { TestAnnoAspectJava.aspectOf().after(joinPoint); } } 复制代码
在反编译的源码下可以看到,编译后的源码加上了TestAnnoAspectJava中定义的对应逻辑。 还有一个关键点所有的通知都会至少携带一个 JointPoint
参数
point.getKind() : method-execution //point的种类 point.getSignature() : void com.mzs.aopstudydemo.MainJavaActivity.stepOn1() // 函数的签名信息 point.getSourceLocation() : MainJavaActivity.java:74 //源码所在的位置 point.getStaticPart() : execution(void com.mzs.aopstudydemo.MainJavaActivity.stepOn1()) //返回一个对象,该对象封装了静态部分的连接点 point.getTarget() : com.mzs.aopstudydemo.MainJavaActivity@7992dfa //返回目标对象 point.getThis() :com.mzs.aopstudydemo.MainJavaActivity@7992dfa //返回当前对象 point.toShortString() : execution(MainJavaActivity.stepOn1()) point.toLongString() : execution(private void com.mzs.aopstudydemo.MainJavaActivity.stepOn1()) point.toString() : execution(void com.mzs.aopstudydemo.MainJavaActivity.stepOn1()) 复制代码
关于怎么自定义注解之类不是本章的重点,请大家可以看一下其它的相关类型的文章,下面切入正题~~
1. 自定义注解创建注解类 CheckLogin
,定义对应的元注解信息,具体解释看上面的图。 并声明一个 isSkip
值。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckLogin { boolean isSkip() default false;//增加额外的信息,决定要不要跳过检查,默认不跳过 } 复制代码
2.定义切点,定义通知在切面类 TestAnnoAspectJava
中
//定义一个变量模拟登录状态 public static Boolean isLoagin = false; //定义切点 @Pointcut("execution(@com.mzs.aopstudydemo.CheckLogin * *(..))") public void checkLogin() { } //定义切入信息通知 @Around("checkLogin()") public void checkLoginPoint(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //1. 获取函数的签名信息,获取方法信息 MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); Method method = signature.getMethod(); //2. 检查是否存在我们定义的CheckLogin注解 CheckLogin annotation = method.getAnnotation(CheckLogin.class); //判断是要跳过检查 boolean isSkip = annotation.isSkip(); //3.根据注解情况进行处理 if (annotation != null) { if (isSkip) { Log.i(TAG, "isSkip=true 这里不需要检查登录状态~~~~~~"); proceedingJoinPoint.proceed(); } else { if (isLoagin) { Log.i(TAG, "您已经登录过了~~~~"); proceedingJoinPoint.proceed(); } else { Log.i(TAG, "请先登录~~~~~"); } } } } 复制代码
这里有 @Pointcut("execution(@com.mzs.aopstudydemo.CheckLogin * *(..))")
:切点表达式使用注解,一定是 @+注解全路径
!!
@CheckLogin() public void LoginAfter(){ Log.i(TAG,"这里是登录成功后才会显示的数据——浪里个浪~~~"); } @CheckLogin(isSkip = true) public void unCheckLogin(){ Log.i(TAG,"这里是不需求要登录判断的~~~~"); } button4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TestAnnoAspectJava.isLoagin = !TestAnnoAspectJava.isLoagin; } }); button5.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { LoginAfter(); } }); button6.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unCheckLogin(); } }); } ---------------------------------------------------------------------------------------- ---------------点击button6打印出来的Log----------------------------------------- I/TestAnnoAspectJava: isSkip=true 这里不需要检查登录状态~~~~~~ I/MainActivity: 这里是不需求要登录判断的~~~~ ---------------先点击button5,再点击button4,再点击button5---打印出来的Log------ I/TestAnnoAspectJava: 请先登录~~~~~ I/TestAnnoAspectJava: 您已经登录过了~~~~ I/MainActivity: 这里是登录成功后才会显示的数据——浪里个浪~~~ 复制代码
上面的示例用的是 Java
,但是如果使用 Kotlin
的话就支持不了。所以需要的话可以使用沪江的 gradle_plugin_android_aspectjx ,简称 AspectJX
这里就不做展示了。有需要的同学自己去翻看一下。