在上篇文章中,介绍了aspectJ
的环境配置及简单的使用,接下来,我们来看详细的使用:
Pointcut
意思为切入点,我们知道,在面向对象编程中,我们可以使用反射和动态代理的方式来截取某个类中的某个方法,但是这样是比较损耗性能的,所以才有了aspectJ
这样的插件诞生,使得我们不需要改变原来的代码结构即可在横向编程中增加自己的业务逻辑~
1、Join Point
先解释下Join Point
, 作为连接点,需要告诉程序(Pointcut
)需要切入的点在哪,在前一篇文章中,@Pointcut("within(com.makeramen.roundedimageview..*)")
,在官网中你回看到这样的写法:
aspect PointObserving {
private Vector Point.observers = new Vector();
public static void addObserver(Point p, Screen s) {
p.observers.add(s);
}
public static void removeObserver(Point p, Screen s) {
p.observers.remove(s);
}
pointcut changes(Point p): target(p) && call(void Point.set*(int));
after(Point p): changes(p) {
Iterator iter = p.observers.iterator();
while ( iter.hasNext() ) {
updateObserver(p, (Screen)iter.next());
}
}
static void updateObserver(Point p, Screen s) {
s.display(p);
}
}
这样的写法Android
编译器是不认识的,因为这是AspectJ
编译器认识的写法,所以,我们需要将Join Point
切入点写成字符串的方式来编译;我们来看下Join Point
的规则,
Join Point | 说明 | Pointcuts语法 |
---|---|---|
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 | 与构造函数有关,很少用到 | preinitialization(ConstructorPattern) |
Initialization | 与构造函数有关,很少用到 | initialization(ConstructorPattern) |
Static initialization | static 块初始化 | staticinitialization(TypePattern) |
Handler | 异常处理 | handler(TypePattern) |
Advice execution | 所有 Advice 执行 | adviceexcution() |
这里,我们看到不同的切入点调用的Pointcuts
方法也不同,这里先不作详细说明,下面会使用详细的例子来说明~
2、Pointcuts
在我们的例子中,使用了within
这样的写法获取到某个包下的信息,这就是Pointcuts
给我们带来的方便,他提供了多种写法给我们使用,让我们可以过滤到不需要的信息;下表中详细列出了使用方法:
Pointcuts语法 | 说明 | 示例 |
---|---|---|
within(TypePattern) | TypePattern标示package或者类TypePattern可以使用通配符 | 表示某个package或者类中的所有JPoint。比如within(Test):Test类中所有JPoint |
withincode(Constructor Signature/Method Signature) | 表示某个构造函数或其他函数执行过程中涉及到的JPoint | 比如 withinCode(* TestDerived.testMethod(..)) 表示testMethod涉及的JPoint。withinCode( *.Test.new(..))表示Test构造函数涉及的JPoint |
cflow(pointcuts) | cflow是call flow的意思,cflow的条件是一个pointcut | 比如cflow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,包括testMethod的call这个JPoint本身 |
cflowbelow(Pointcut) | cflow是call flow的意思 | 比如cflowblow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,不包括testMethod的call这个JPoint本身 |
this(Type) | Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型 | JPoint是代码段(不论是函数,异常处理,static block),从语法上说,它都属于一个类。如果这个类的类型是Type标示的类型,则和它相关的JPoint将全部被选中。图2示例的testMethod是TestDerived类。所以this(TestDerived)将会选中这个testMethod JPoint |
target(Type) | JPoint的target对象是Type类型和this相对的是target。不过target一般用在call的情况。call一个函数,这个函数可能定义在其他类。 | 比如testMethod是TestDerived类定义的。那么target(TestDerived)就会搜索到调用testMethod的地方。但是不包括testMethod的execution JPoint |
args(TypeSignature) | 用来对JPoint的参数进行条件搜索的 | 比如args(int,..),表示第一个参数是int,后面参数个数和类型不限的JPoint。 |
Pointcut 表达式还可以 !、&&、|| 来组合,语义和java一样。上面 Pointcuts 的语法中涉及到一些 Pattern,下面是这些 Pattern 的规则,[]里的内容是可选的:
Pattern | 规则 |
---|---|
MethodPattern | [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型] |
ConstructorPattern | [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型] |
FieldPattern | [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名 |
TypePattern | 其他 Pattern 涉及到的类型规则也是一样,可以使用 ‘!’、’‘、’..’、’+’,’!’ 表示取反,’‘ 匹配除 . 外的所有字符串,’*’ 单独使用事表示匹配任意类型,’..’ 匹配任意字符串,’..’ 单独使用时表示匹配任意长度任意类型,’+’ 匹配其自身及子类,还有一个 ‘…’表示不定个数。也可以使用 &&、|| 操作符 |
3、Pointcuts 语法
3.1 execution
3.1.1 execution 找类中方法
@Aspect
public class MethodApt {
public static final String TAG = "AnnotationApt";
@Pointcut("execution(* com.ellison.aop.method.ExcustionMethodActivity.test())")
public void executionFindMethod() {
}
@Before("executionFindMethod()")
public void invokMethod(JoinPoint point) throws Throwable {
Log.d(TAG, "方法之前");
((ProceedingJoinPoint) point).proceed();
Log.d(TAG, "方法之后");
}
}
使用execution
查找具体的某个类中的某个方法:
execution(* com.ellison.aop.method.ExcustionMethodActivity.test())
第一个 * 表示方法的修饰符,public
、private
、protected
,上面解释了 * 为通配符,查找类下得所有方法:
execution(* com.ellison.aop.method.ExecutionMethodActivity.**())
具体的返回值、修饰符、方法查找就在上面的代码上修改即可~
3.1.2 execution 找注解
首先,我们编写一个自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExecutionAnnotationFindMethod {
}
该注解使用在METHOD
上,接下来我们编写aspectJ
代码:
@Aspect
public class AnnotationApt {
public static final String TAG = "AnnotationApt";
@Pointcut("execution(@com...ExecutionAnnotationFindMethod * *(..))")
public void annotationFindMethod() {
}
@Before("annotationFindMethod()")
public void invokMethod(JoinPoint point) throws Throwable {
Log.d(TAG, "具体方法之前");
((ProceedingJoinPoint) point).proceed();
}
}
因为包名太长,所以我省略了,在Pointcut
上,需要写上注解的包名全路径,很简单,我们需要给JoinPoint
切入点写下如下代码即可:
execution(@com.ellison.aop.annotation_method.ExecutionAnnotationFindMethod * *(..))
写上注解的全路径,之后就是匹配 返回值、方法名、方法参数,例如你需要找到注解了ExecutionAnnotationFindMethod
的方法,同时还需要找到返回值为ReturnParam
的方法,则需要编写如下Pointcut
:
execution(@com.ellison.aop.annotation_method.ExecutionAnnotationFindMethod ReturnParam *(..))
在比如,你需要找到参数为String
类型的并注解了ExecutionAnnotationFindMethod
的方法时,需要编写如下Pointcut
:
execution(@com.ellison.aop.annotation_method.ExecutionAnnotationFindMethod * *(String))
3.2 within
看到within
,想必大家都很熟悉;因为在我们第一个打印log
的实例中就使用到了within
切入点包括 对象初始化块、field、构造方法、方法、 类
3.2.1 within 查找包下的任意连接点
首先,我们先定义一个简单的自定义view
,这个自定义view
就在它的中间区域内画一个圆,对,就是这么简单:
public class RoundedView extends View {
private Context mContext;
private Paint mPaint;
public RoundedView(Context context) {
this(context, null);
}
public RoundedView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
private void initView() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.CYAN);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 100, mPaint);
}
}
初始化画笔paint
之后,在onDraw()
方法中直接使用canvas
画圆即可,效果图如下:
因为我们在
Activity
下使用了这个自定义view
,所以下面我们开始使用within
查找这个view
所在的包下所有连接点并打印出方法、字段、构造函数:
@Aspect
public class WithinApt {
public static final String TAG = "WithinApt";
@Pointcut("within(com.ellison.aop.within..*)")
public void withinFindPackage() {
}
@Before("withinFindPackage()")
public void invokeMethod(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, "具体方法之前");
((ProceedingJoinPoint)joinPoint).proceed();
}
}
上面,我们使用within
找到了包下面所有的方法、字段、构造方法的连接切入点,
within(com.ellison.aop.within.*)
com.ellison.aop.within
包下任意连接点
within(com.ellison.aop.within..*)
com.ellison.aop.within
包或子包下任意连接点
within(RoundedView)
RoundedView
类下的任意连接点
within(@com.xyz.service.BehavioClass *)
持有com.xyz.service.BehavioClass注解的任意连接点
网友评论