AOP是Aspect Oriented Programming的缩写,即『面向切面编程』。它和咱们平时接触到的OOP都是编程的不一样思想,OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太同样,它提倡的是针对同一类问题的统一处理,固然,咱们在实际编程过程当中,不可能单纯的安装AOP或者OOP的思想来编程,不少时候,可能会混合多种编程思想,你们也没必要要纠结该使用哪一种思想,取百家之长,才是正道。java
那么AOP这种编程思想有什么用呢,通常来讲,主要用于不想侵入原有代码的场景中,例如SDK须要无侵入的在宿主中插入一些代码,作日志埋点、性能监控、动态权限控制、甚至是代码调试等等。android
AspectJ其实是对AOP编程思想的一个实践,固然,除了AspectJ之外,还有不少其它的AOP实现,例如ASMDex,但目前最好、最方便的,依然是AspectJ。git
AOP的用处很是广,从Spring到Android,各个地方都有使用,特别是在后端,Spring中已经使用的很是方便了,并且功能很是强大,可是在Android中,AspectJ的实现是略阉割的版本,并非全部功能都支持,但对于通常的客户端开发来讲,已经彻底足够用了。github
在Android上集成AspectJ其实是比较复杂的,不是一句话就能compile,可是,鄙司已经给你们把这个问题解决了,你们如今直接使用这个SDK就能够很方便的在Android Studio中使用AspectJ了。Github地址以下:编程
https://github.com/HujiangTec...后端
另一个比较成功的使用AOP的库是Jake大神的Hugo:微信
https://github.com/JakeWharto...app
首先,须要在项目根目录的build.gradle中增长依赖:ide
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
完整代码以下:模块化
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.0-beta2' classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
而后再主项目或者库的build.gradle中增长AspectJ的依赖:
compile 'org.aspectj:aspectjrt:1.8.9'
同时在build.gradle中加入AspectJX模块:
apply plugin: 'android-aspectjx'
这样就把整个Android Studio中的AspectJ的环境配置完毕了,若是在编译的时候,遇到一些『can't determine superclass of missing type xxxxx』这样的错误,请参考项目README中关于excludeJarFilter的使用。
aspectjx { //includes the libs that you want to weave includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library' //excludes the libs that you don't want to weave excludeJarFilter 'universal-image-loader' }
咱们经过一段简单的代码来了解下基本的使用方法和功能,新建一个AspectTest类文件,代码以下:
@Aspect public class AspectTest { private static final String TAG = "xuyisheng"; @Before("execution(* android.app.Activity.on**(..))") public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodBefore: " + key); } }
在类的最开始,咱们使用@Aspect注解来定义这样一个AspectJ文件,编译器在编译的时候,就会自动去解析,并不须要主动去调用AspectJ类里面的代码。
个人原始代码很简单:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
经过这种方式编译后,咱们来看下生成的代码是怎样的。AspectJ的原理其实是在编译的时候,根据必定的规则解析,而后插入一些代码,经过aspectjx生成的代码,会在Build目录下:
经过反编译工具查看下生成内容:
咱们能够发现,在onCreate的最前面,插入了一行AspectJ的代码。这个就是AspectJ的主要功能,抛开AOP的思想来讲,咱们想作的,实际上就是『在不侵入原有代码的基础上,增长新的代码』。
Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不一样的部分。例如,构造方法调用、调用方法、方法执行、异常等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪一个地方,是插在构造方法中,仍是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,固然,不是全部地方都能给你插的,只有能插的地方,才叫Join Points。
Join Points和Pointcuts的区别实际上很难说,我也不敢说我理解的必定对,但这些都是概念上的内容,并不影响咱们去使用。
Pointcuts,在我理解,实际上就是在Join Points中经过必定条件选择出咱们所须要的Join Points,因此说,Pointcuts,也就是带条件的Join Points,做为咱们须要的代码切入点。
又来一个Advice,Advice实际上是最好理解的,也就是咱们具体插入的代码,以及如何插入这些代码。咱们最开始举的那个例子,里面就是使用的最简单的Advice——Before。相似的还有After、Around,咱们后面来说讲他们的区别。
咱们之前面的Demo来看下最简单的AspectJ语法:
@Before("execution(* android.app.Activity.on**(..))") public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable { }
这里会分红几个部分,咱们依次来看:
@Before:Advice,也就是具体的插入点
execution:处理Join Point的类型,例如call、execution
( android.app.Activity.on*(..)):这个是最重要的表达式,第一个『*』表示返回值,『*』表示返回值为任意类型,后面这个就是典型的包名路径,其中能够包含『*』来进行通配,几个『*』没区别。同时,这里能够经过『&&、||、!』来进行条件组合。()表明这个方法的参数,你能够指定类型,例如android.os.Bundle,或者(..)这样来表明任意类型、任意个数的参数。
public void onActivityMethodBefore:实际切入的代码。
这里还有一些匹配规则,能够做为示例来进行讲解:
表达式 | 含义 |
---|---|
java.lang.String | 匹配String类型 |
java.*.String | 匹配java包下的任何“一级子包”下的String类型,如匹配java.lang.String,但不匹配java.lang.ss.String |
java..* | 匹配java包及任何子包下的任何类型,如匹配java.lang.String、java.lang.annotation.Annotation |
java.lang.*ing | 匹配任何java.lang包下的以ing结尾的类型 |
java.lang.Number+ | 匹配java.lang包下的任何Number的自类型,如匹配java.lang.Integer,也匹配java.math.BigInteger |
参数 | 含义 |
---|---|
() | 表示方法没有任何参数 |
(..) | 表示匹配接受任意个参数的方法 |
(..,java.lang.String) | 表示匹配接受java.lang.String类型的参数结束,且其前边能够接受有任意个参数的方法 |
(java.lang.String,..) | 表示匹配接受java.lang.String类型的参数开始,且其后边能够接受任意个参数的方法 |
(*,java.lang.String) | 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法 |
这两个Advice应该是使用的最多的,因此,咱们先来看下这两个Advice的实例,首先看下Before和After。
@Before("execution(* com.xys.aspectjxdemo.MainActivity.on*(android.os.Bundle))") public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodBefore: " + key); } @After("execution(* com.xys.aspectjxdemo.MainActivity.on*(android.os.Bundle))") public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodAfter: " + key); }
通过上面的语法解释,如今看这个应该很好理解了,咱们来看下编译后的类:
咱们能够看见,在原始代码的基础上,增长了Before和After的代码,Log也能被正确的插入并打印出来。
Before和After其实仍是很好理解的,也就是在Pointcuts以前和以后,插入代码,那么Around呢,从字面含义上来说,也就是在方法先后各插入代码,是的,他包含了Before和After的所有功能,代码以下:
@Around("execution(* com.xys.aspectjxdemo.MainActivity.testAOP())") public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String key = proceedingJoinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodAroundFirst: " + key); proceedingJoinPoint.proceed(); Log.d(TAG, "onActivityMethodAroundSecond: " + key); }
其中,proceedingJoinPoint.proceed()表明执行原始的方法,在这以前、以后,均可以进行各类逻辑处理。
原始代码:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testAOP(); } public void testAOP() { Log.d("xuyisheng", "testAOP"); } }
咱们先来看下编译后的代码:
咱们能够发现,Around确实实现了Before和After的功能,可是要注意的是,Around和After是不能同时做用在同一个方法上的,会产生重复切入的问题。
自定义Pointcuts可让咱们更加精确的切入一个或多个指定的切入点。
首先,咱们须要自定义一个注解类,例如——DebugTool.java:
/** * 自定义AOP注解 * <p> * Created by xuyisheng on 17/1/12. */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface DebugTool { }
而后在须要插入代码的地方使用这个注解:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testAOP(); } @DebugTool public void testAOP() { Log.d("xuyisheng", "testAOP"); } }
最后,咱们来建立本身的切入文件。
@Pointcut("execution(@com.xys.aspectjxdemo.DebugTool * *(..))") public void DebugToolMethod() { } @Before("DebugToolMethod()") public void onDebugToolMethodBefore(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onDebugToolMethodBefore: " + key); }
先定义Pointcut,并申明要监控的方法名,最后,在Before或者其它Advice里面添加切入代码,便可完成切入。
编译好的代码以下:
经过这种方式,咱们能够很是方便的监控指定的Pointcut,从而增长监控的粒度。
在AspectJ的切入点表达式中,咱们前面都是使用的execution,实际上,还有一种类型——call,那么这两种语法有什么区别呢,咱们来试验下就知道了。
被切代码依然很简单:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testAOP(); } public void testAOP() { Log.d("xuyisheng", "testAOP"); } }
先来看execution,代码以下:
@Before("execution(* com.xys.aspectjxdemo.MainActivity.testAOP(..))") public void methodAOPTest(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "methodAOPTest: " + key); }
编译以后的代码以下所示:
再来看下call,代码以下:
@Before("call(* com.xys.aspectjxdemo.MainActivity.testAOP(..))") public void methodAOPTest(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "methodAOPTest: " + key); }
编译以后的代码以下所示:
其实对照起来看就一目了然了,execution是在被切入的方法中,call是在调用被切入的方法前或者后。
对于Call来讲:
Call(Before) Pointcut{ Pointcut Method } Call(After)
对于Execution来讲:
Pointcut{ execution(Before) Pointcut Method execution(After) }
除了前面提到的call和execution,比较经常使用的还有一个withincode。这个语法一般来进行一些切入点条件的过滤,做更加精确的切入控制。咱们能够参考下面这个例子:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testAOP1(); testAOP2(); } public void testAOP() { Log.d("xuyisheng", "testAOP"); } public void testAOP1() { testAOP(); } public void testAOP2() { testAOP(); } }
testAOP1()和testAOP2()都调用了testAOP()方法,可是,如今想在testAOP2()方法调用testAOP()方法的时候,才切入代码,那么这个时候,就须要使用到Pointcut和withincode组合的方式,来精肯定位切入点。
// 在testAOP2()方法内 @Pointcut("withincode(* com.xys.aspectjxdemo.MainActivity.testAOP2(..))") public void invokeAOP2() { } // 调用testAOP()方法的时候 @Pointcut("call(* com.xys.aspectjxdemo.MainActivity.testAOP(..))") public void invokeAOP() { } // 同时知足前面的条件,即在testAOP2()方法内调用testAOP()方法的时候才切入 @Pointcut("invokeAOP() && invokeAOP2()") public void invokeAOPOnlyInAOP2() { } @Before("invokeAOPOnlyInAOP2()") public void beforeInvokeAOPOnlyInAOP2(JoinPoint joinPoint) { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onDebugToolMethodBefore: " + key); }
咱们再来看下编译后的代码:
咱们能够看见,只有在testAOP2()方法中被插入了代码,这就作到了精确条件的插入。
AfterThrowing是一个比较少见的Advice,他用于处理程序中未处理的异常,记住,这点很重要,是未处理的异常,具体缘由,咱们等会看反编译出来的代码就知道了。咱们随手写一个异常,代码以下:
public void testAOP() { View view = null; view.animate(); }
而后使用AfterThrowing来进行AOP代码的编写:
@AfterThrowing(pointcut = "execution(* com.xys.aspectjxdemo.*.*(..))", throwing = "exception") public void catchExceptionMethod(Exception exception) { String message = exception.toString(); Log.d(TAG, "catchExceptionMethod: " + message); }
这段代码很简单,一样是使用咱们前面相似的表达式,可是这里是为了处理异常,因此,使用了*.*来进行通配,在异常中,咱们执行一行日志,编译好的代码以下:
咱们能够看见com.xys.aspectjxdemo包下的全部方法都被加上了try catch,同时,在catch中,被插入了咱们切入的代码,可是最后,他依然会throw e,也就是说,这个异常已经会被抛出去,崩溃依旧是会发生的。同时,若是你的原始代码中已经try catch了,那么一样也没法处理,具体缘由,咱们看一个反编译的代码:
能够看见,实际上,原始代码的catch中,又被套了一层try catch,因此,e.printStackTrace()被try catch,也就不会再有异常发生了,也就没法切入了。
目前鄙司的不少项目都已经使用了这套AOP方案,例如基于AOP的动态权限管理、基于AOP的业务数据埋点、基于AOP的性能监测系统等等。
如今已经开源了一部分基于AOP的动态权限管理的源码,但因为须要剥离业务代码,因此后面会更加完善这功能代码,你们能够继续关注,github地址以下所示:
https://github.com/firefly112...
其它的AOP项目陆续开源中,你们能够持续关注~
欢迎关注个人微信公众号