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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容