其实人最大悲哀莫过于知道本身想要什么,殊不知道怎么坚持!最近迷恋上了死侍 其实和我平时的状态差很少,以一个混子的心态去作任何事情,每每成功的几率会更大!!!java
一张图片镇楼!!!android
上文说到了AspectJ的集成问题,若是没有看过上一篇文章的小伙伴能够看看本系列的第一篇文章。git
AOP埋点从入门到放弃(一)github
这篇文章充分的讲解了关于AspectJ的集成问题,接下来咱们讲讲怎么更好的使用AspectJ来惟我所用。。。编程
其实我感受这个东西提及来是最难的,由于要记住一大堆概念!其实记忆力是个人最烦的东西,可是我是一只猿,一只牛逼的猿!因此当背课文了...bash
先来看一段代码app
@Aspect
public class TraceAspect {
private static final String TAG = "hjl";
@Before("execution(* android.app.Activity.on*(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);
}
}
复制代码
先说一下这段代码实现了什么?主要实现了在Activity中全部以on开头的方法前面打印一段**Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);**代码!接下来咱们来逐一讲解!框架
关于@Aspect这个注解,是下面全部内容的基础,若是没有这个注解AspectJ没有相应的入口,就不会有相应的切面了!AspectJ会找到全部@Aspect注解,而后函数
首先说一下通配符的大致格式:(我也不知道理解的对不对,可是项目中使用的时候没有发现什么不对的地方)post
@注解 | 访问权限 | 返回值类型 | 类名 | 函数名 | 参数 |
---|
大致上的通配符都是这个格式的,咱们用上面的一个通配符去说明一下:
execution(* android.app.Activity.on*(..))
复制代码
execution 通常指定方法的执行,在日后由于没有注解和访问权限的限制,因此这里什么也没写,返回值用*代替,说明能够用任何返回值,android.app.Activity.on*表明函数名称的全路径,后面的一个型号表明on后面能够接任何东西,后面的(..)表明其参数能够为任何值。
上面就是一段通配符的含义了!其实学习AspectJ的时候,我以为最难懂的就是相应的操做符了,若是操做符弄明白了的话,真的就很简单了!可是若是以前作事后台的话,这个应该就很简单了,就是Spring框架中的AOP是同样的都是用的Pointcut语法。由于本身不是java后台开发人员,因此解释的可能不到位,你能够去找大家java后台组的人去问问,学习一下!应该比我讲的强不少,由于我真是第一次接触这个东西!
由于平时没接触过,因此这里就写一些经常使用的吧!
分类
JPoint | 说明 | Pointcut语法说明 |
---|---|---|
method execution | 通常指定方法的执行 | execution(MethodSignnature) |
method call | 函数被调用 | call(MethodSignnature) |
constructor call | 构造函数被调用 | call(ConstructorSignature) |
constructor execution | 构造函数执行内部 | execution(ConstructorSignature) |
field get | 读变量 | get(FieldSIgnature) |
field set | 写变量 | set(FieldSIgnature) |
handler | 异常处理 | handler(TypeSignature) 注意:只能和@Before()配合使用,不支持@After、@Around等 |
advice execution | advice执行 | adciceexectuin() |
Signature参考
Sigbature | 语法(间隔一个空格) |
---|---|
MethodSignature | @注解 访问权限 返回值类型 类名.函数名(参数) |
ConstructorSignature | @注解 访问权限 类名.new(参数) |
FieldSignature | @注解 访问权限 变量类型 类名.类成员变量名 |
Signature语法明细
Sigbature语法明细 | 解释 |
---|---|
@注解 | @完整类名,若是没有则不写 |
访问权限 | public/private/portect,以及static/final,若是没有则不写 注意:若是只写public则匹配出来的是所有,若是写public final则匹配的是全部public final开头的内容 |
返回值类型 | 若是不限定类型,使用通配符*表示 |
类名.函数名 | 可使用的通配符,包括*和..以及+号。其中*号用于陪陪除.号以外的任意字符,而..则表示任意字package,+号表示子类 注意:1.ConstructorSignature的函数名只能为new 2.(.函数名能够不写),重用和注解一块儿使用 3.不能以..开头 |
变量类型 | 成员变量类型,*表明任意类型 |
类名.成员变量名 | 类名可使用通配符,与函数。函数名相似 |
Advice内容
Advice | 说明 |
---|---|
@Before(Pointcut) | 执行在jPoint以前 |
@After(Pointcut) | 执行在jPoint以后 |
@Around(Pointcut) | 替代原来的代码,若是要执行原来的代码,须要使用proceedingJoinPoint.proceed(); 注意:不能够和@After和@Before等一块儿使用 |
上面这写表的你先简单看一下,估计你一会仍是会回来看的!!!
@Pointcut("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void callMethod() {
//为了演示call方法的使用
}
@Before("callMethod()")
public void beforeCallMethod(JoinPoint joinPoint) {
Log.e(TAG, "call方法的演示");
}
复制代码
说一下上面代码:@Pointcut是来注解方法的,call后面添加了一系列的通配符,简单说就是一个方法的地址,*表明没有返回值,@Before是说明在切片前执行!这里千万别把com.jinlong.aspectjdemo.MainActivity.callMethod这个地址写错了就行!
其实上面这段代码能够简化为
@Before("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void beforeCallMethod(JoinPoint joinPoint){
Log.e(TAG, "call方法的演示");
}
复制代码
若是你把上面的@Before换为@After,那么就会在方法以后打印!!!
再来看一段代码:
@Around("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void aroundCallMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long endTime = System.currentTimeMillis();
Log.e(TAG, "方法执行的时间为" + (endTime - startTime));
}
复制代码
这段代码是统计方法执行时间的,这里着重讲两个内容
因此这里就能计算出方法的执行时间了!!!也就是@Around的用法了!
以前的时候,我总以为execution和call是同样的,可是后来我知道了,他们最大的区别是这样的!!!
好比你有一个方法:callMethod()对吧! 而后使用call是这个样子滴~
call的相应方法();
callMethod();
复制代码
可是若是是execution的话就编程这个样子滴了~
callMethod(){
execution的相应方法();
}
复制代码
其余的就没有什么区别了,也就不在这里举例说明了。
先说下这个东西是构造方法上用的,也就是说针对于相应的构造方法进行相应切面操做的!可是我一只有一个疑问不明白,若是我按照上面方法的通配符进行操做的话,按照常理说应该也是能在相应切面进行操做的才对啊!编译不报错,但就是怎么也打印不出来结果,还请明白的大神帮我解答一下!
按照上面的表格还有一种方案解决相应构造方法的问题
@Before("execution(com.jinlong.aspectjdemo.Person.new(..))")
public void beforeConstructorExecution(JoinPoint joinPoint) {
//这个是显示Constructor的
Log.e(TAG, "before->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
}
复制代码
这段代码我尝试过了,能够打印出结果。也就是以**ConstructorSignature @注解 访问权限 类名.new(参数)**这种方式就能够打印出相应结果来!不过我劝你把那个@Before换成@After不然你打印出来的内容多是一个空!
就是至关你能够修改类的成员变量,无论你怎么设置最终返回的都是你设定的值!
看下面这段代码:
这里是正常的一个类:
public class Person {
private String name;
private String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
复制代码
这才是核心代码!
@Around("get(String com.jinlong.aspectjdemo.Person.age)")
public String aroundFieldGet(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行原代码
Object obj = joinPoint.proceed();
String age = obj.toString();
Log.e(TAG, "age: " + age);
return "100";
}
复制代码
这里用到的的是上面FieldSignature @注解 访问权限 变量类型 类名.类成员变量名上面这段代码的含义是这样滴,在每次用到age这个内容的时候,都会被修改,可是有一个问题,就是你不能重写类的**toString()**方法,若是你重写了这个方法的话,obj返回的就是一个空!我还真不知道为何,还请明白的告知一二!,这里面get是一个关键字,就这么理解吧!由于没有设置访问权限和注解,因此这里直接就省返回的变量类型(String类型)和类成员变量名的全路径了!
在看下面这段代码:
@Around("set(String com.jinlong.aspectjdemo.Person.age)")
public void aroundFieleSet(ProceedingJoinPoint joinPoint) {
Log.e(TAG, "aroundFieleSet: " + joinPoint.getTarget().toString() +
joinPoint.getSignature().getName());
}
复制代码
这个能够对相应的age属性进行设置的方法,也就是当发生赋值操做的时候都会被修改!这里和你们说明一下,上面这段代码没有**joinPoint.proceed();**代码,因此以前的代码中执行的内容就会失效了!也就是说被打印这段话替换了!其实上面这段代码你运行的时候你会发现一件事,Log被打印了两次,为何呢?你想啊!this.age = age;在set方法中出现一次,并且还在构造方法中出现一次呢。仔细看看,因此这里要排除构造方法总的那一次,怎么处理呢?就要用到 withincode了!
怎么理解这个东西呢?表示某个构造方法或函数中涉及到的JPoint。不理解吧!不要紧,看一段代码你就理解了!
@Around("set(String com.jinlong.aspectjdemo.Person.age)&&!withincode(com.jinlong.aspectjdemo.Person.new(..))")
public void aroundFieleSet(ProceedingJoinPoint joinPoint) throws Throwable {
//设置相应的成员变量
joinPoint.proceed();
}
复制代码
在1.2.4上面说到set会在两个地方都有,可是其实我是不想要构造方法中的那个的,怎么把他排除呢?那就是后面添加的这句代码**com.jinlong.aspectjdemo.Person.new(..))**就是把构造方法中的内容排除!其实很好理解,就是排除相应的构造方法,能够简单理解withincode就是带着某个内容,可是因为取反了,因此就是不带着这个东西了!!!就酱紫了。。。
这个相比较之下就简单一点了,直接上代码:
这是在代码中的一个异常,很简单的一个异常,若是这个方法走了相应的catch,那么就能捕获相应的异常了!
private void catchMethod() {
try {
int sum = 100 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
关键代码来了。。。
@Before("handler(java.lang.Exception)")
public void handlerMethod() {
Log.e(TAG, "handlerMethod: 异常产生了");
}
复制代码
是否是很简单,使用一个handler关键字,加上一个异常的全路径,ok搞定,可是这里必定要注意,前面的注解只能是@Before,切记!!!
有许多第三方你是不知道具体方法名称的,可是你还想使用的话怎么办?那就是注解了,由于注解能够很好的解决这种需求。
再来看一段代码:
定义一段注解内容:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
复制代码
由于注解的这些内容不是本篇文章的重点,因此这里不许备讲解了。感兴趣的你能够百度一下,这个注解主要是在编译完成后也会起做用,而且是方法和成员变量均可以使用!
在加上下面这段代码就能够进行相应切面的操做了!
@Pointcut("execution(@com.jinlong.aspectjdemo.DebugTrace * *(..))")
public void DebugTraceMethod() {
}
@Before("DebugTraceMethod()")
public void beforeDebugTraceMethod(JoinPoint joinPoint) {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "注解这个方法执行了: ");
}
复制代码
看到在这段代码你们应该不怎么陌生了,就是在方法内容添加相应的切面操做。最后在使用的方法的上面添加相应的注解就能够了!就这么简单!
@DebugTrace
private void mothod1() {
Log.e(TAG, "方法1执行了");
}
复制代码
上面基本上包含了咱们APP使用中,能用到一些内容,若是有什么讲的不到位的地方还请指出。由于是第一次接触这个东西,可能有些细节讲解的不是很到位,还请谅解!!!
想看源码吗?想看连接吗?点这里