Aop之AspectJ

AOP之AspectJ

前言:这几天一直在学习aop切面编程,以前一直也有听过aop但是实际用的还是比较少,不是很清楚,这周版本刚上线,有点空闲时间,所以就把之前想学习的aop技术重点花时间学习了下,所以才有这篇博客总结下学习的情况,其实现在的博客大都是这个抄写那个,那个抄写这个的,其实我认为不管怎么抄写,只要自己掌握了,就没关系。

AOP技术有很多,目前比较出名流行的技术框架大概是这几类:1.APT 2.ASM 3.AspectJ,经过这几天的学习,我还是认为AspectJ是最简单最快的实现方式,所以接下来,我会对AspectJ重点总结下这几天的学习。

开始接入AspectJ步骤:

1.首先,需要在项目根目录的build.gradle中增加依赖:

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'

2.然后再主项目或者库的build.gradle中增加AspectJ的依赖和插件依赖:

apply plugin: 'android-aspectjx'
compile 'org.aspectj:aspectjrt:1.8.10'

3.excludeJarFilter解释:用该属性,可以过滤减少AspectJ扫描库文件,用法:

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'
}

AspectJ语法

Join Point

Join Point是AspectJ前言核心的地方,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造方法调用、调用方法、方法执行、异常等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。插入点的列举:

JoinPoint 说明
Method call 方法被调用
Content Cell Content Cell
Method execution 方法执行
Constructor call 构造函数被调用
Constructor execution 构造函数执行
Field get 读取属性
Field set 写入属性
Pre-initialization 与构造函数有关,很少用到
Initialization 与构造函数有关,很少用到
Static initialization static 块初始化
Handler 异常处理
Advice execution 所有 Advice 执行

以上都是AspectJ插入代码的点。

Pointcuts

Pointcuts 是具体的切入点,可以确定具体织入代码的地方,基本的 Pointcuts 是和 Join Point 相对应的,在我理解,实际上就是在Join Points中通过一定条件选择出我们所需要的Join Points,所以说,Pointcuts,也就是带条件的Join Points,作为我们需要的代码切入点,两者相对应的关系点:

Pointcuts 说明
Join Point Pointcuts syntax
Method call call(MethodPattern)
Method execution execution(MethodPattern)
Constructor call call(ConstructorPattern)
Constructor execution execution(ConstructorPattern)
Field get get(FieldPattern)
Field set set(FieldPattern)
Pre-initialization initialization(ConstructorPattern)
Initialization preinitialization(ConstructorPattern)
Static initialization staticinitialization(TypePattern)
Handler handler(TypePattern)
Advice execution advice excution()

Advice

Advice是在插入点的地方植入代码,在 AspectJ 中有五种类型:Before、After、AfterReturning、AfterThrowing、Around。

Advice 说明
@Before 在执行 Join Point 之前
@After 在执行 Join Point 之后,包括正常的 return 和 throw 异常
@AfterReturning Join Point 为方法调用且正常 return 时,不指定返回类型时匹配所有类型
@AfterThrowing Join Point 为方法调用且抛出异常时,不指定异常类型时匹配所有类型
@Around 替代 Join Point 的代码,如果要执行原来代码的话,要使用ProceedingJoinPoint.proceed()

注意: After 和 Before 没有返回值,但是 Around 的目标是替代原 Join Point 的,所以它一般会有返回值,而且返回值的类型需要匹配被选中的 Join Point 的代码。而且不能和其他 Advice 一起使用,如果在对一个 Pointcut 声明 Around 之后还声明 Before 或者 After 则会失效。

Advice 注解修改的方法必须为 public,Before、After、AfterReturning、AfterThrowing 四种类型修饰的方法返回值也必须为 void,Advice 需要使用 JoinPoint、JoinPointStaticPart、JoinPoint.EnclosingStaticPart 时,要在方法中声明为额外的参数,@Around 方法可以使用 ProceedingJoinPoint,用以调用 proceed() 方法.

注意(Advice 不能使用 After 和 Around)

例子用法说明

/**
 * Created by wuminjian on 17/11/2.
 */
@Aspect
public class FragmentAspect {
    private static final String TAG = "FragmentAspect";
    @Before("execution(* com.test.aspectj.MainActivity.on**(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.d(TAG, "onActivityMethodBefore: " + key);
    }
}
  • @Aspect:这个注解的意思是一个AspectJ文件,编译器在编译的时候,就会自动去解析,然后将代码注入到相应的JPonit中。
  • @Before: 是Advice,也就是具体的插入点,是指调用这个某个Jpoint之前插入代码。
  • execution: 处理JPoint的类型,例如call,execution
    • com.test.aspectj.MainActivity.on*(..)):这个是重要的表达式,第一个[ * ]表示返回值, 表示返回任意类型,后面的com.test.aspectj.MainActivity.on **表示典型的包名路径,其中可用 * 来进行通配.
  • onActivityMethodBefore:是调用MainActivity.on**方法之前插入的实际代码。

通过这种方式编译后,我们来看下生成的代码是怎样的。AspectJ的原理实际上是在编译的时候,根据一定的规则解析,然后插入一些代码,通过aspectjx生成的代码,会在Build目录:

CE63E24D-B07B-47DD-8B32-B32233EA7D49.png

通过JD-GUI查看jar文件如下:


77fb8413bb1ed6fb9484.png

我们可以发现,在onCreate的最前面,插入了一行AspectJ的代码。这个就是AspectJ的主要功能。

自定义Pointcuts

自定义Pointcuts可以让我们更加精确的切入一个或多个指定的切入点.

  • 首先,我们需要自定义一个注解类,例如——WmjLog
/**
 * Created by wuminjian on 17/11/3.
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WmjLog {
    
}
  • 接着,创建一个切入文件,内部通过一个Pointcut来指定在带有我们上面自定义注解类WmjLog注解的所有方法上进行拦截。
/**
 * Created by wuminjian on 17/11/3.
 */
@Aspect
public class WmjAppLogAspect {

    //在带有AopLog注解的方法进行切入(注:此处的 * *前面都要有一个空格)
    @Pointcut("execution(@com.test.aspectj.model.WmjLog * *(..))")
    public void wmjLogPointcut() {

    } 
    //注意,这个函数必须要有实现,否则Java编译器会报错
    @After("wmjLogPointcut()")
    public void onLogPointcutAfter(JoinPoint joinPoint) throws Throwable {
        Log.i("WmjAOP", "onLogPointcutAfter:" + joinPoint.getSignature());
    }
}
  • 最后,在app项目中写一个类来测试,代码如下:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.test.aspectj.model.WmjLog;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
    @WmjLog
    public void WmjTestAop(){
        Log.i("AOP","in wmjTestAop");
    }
}

通过这种方式,我们可以非常方便的监控指定的Pointcut,从而增加监控的粒度,其实AspectJ很简单关键就是Points的匹配写法。

call和execution

在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上,还有一种类型——call,execution是在被切入的方法中,call是在调用被切入的方法前或者后。具体事咧就不说了,大家自己去多试下就知道了。

@Before、@After、@ Around

Before、After

这两个Advice应该是使用的最多的,所以,我们先来看下这两个Advice的实例,首先看下Before和After:

@Before("execution(* com.test.aspectj.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.test.aspectj.MainActivity.on*(android.os.Bundle))")
public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAfter: " + key);
}

经过上面的语法解释,现在看这个应该很好理解了,我们来看下编译后的类:


ee82fd1b58848ef17751.png

其实就是在方法的前后插入代码。

Around

Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Around呢,从字面含义上来讲,也就是在方法前后各插入代码,是的,他包含了Before和After的全部功能,代码如下:

@Around("execution(* com.test.aspectj.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("wmj", "testAOP");
    }
}

编译之后查看class文件如下:


c6690d3cb753127061ee.png

我们可以发现,Around确实实现了Before和After的功能,但是要注意的是,Around和After是不能同时作用在同一个方法上的,会产生重复切入的问题。

总结:AspectJ还是很简单,很好用的,关键是要熟悉他的语法就行,以上还是还有很多小点没讲到,自己多看资料吧,AspectJ其实编译是用ajc去编译的,以上列子集成了一个apply plugin: 'android-aspectjx'一个插件,插件帮我们做好了一切,所以我们能直接使用@Aspect注解,以下是几个写的很好的列子:

gradle_plugin_android_aspectjx

hugo

gradle-android-aspectj-plugin

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容