AspectJ语法
回顾
上节写了一个简单的测试用例,look下代码
@Aspect
public class CoreAspetJ {
@After("call(* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))")
public void showTaost(JoinPoint joinPoint) {
Log.d("wyz", "AspectJ:牛B个锤子");
}
}
解释
讲解下@Aspect注解使用在类上,代表这个类是注入的类
- @After作用于方法(Advice),也就是具体的插入点
- call处理Join Point的类型(其实是代码需要注入的地方)
- (* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))这个是最重要的表达式,第一个『』表示返回值,『』表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含『』来进行通配,几个『』没区别。同时,这里可以通过『&&、||、!』来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(…)这样来代表任意类型、任意个数的参数。
这里还有一些匹配规则,可以作为示例来进行讲解:
表达式 | 含义 |
---|---|
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) | 表示只有2个参数,第一个参数任意类型,第二个必须是java.lang.String |
总结下
- * 代表占1个位置,任意类型
- … 代表任意个数任意类型
- + 代表子类
Advice
我们的代码中使用了==@Before==,代表注入点是方法前哦!注意@Before和@After不能一起使用哦,如果要一起使用请用@Around替换
间接选择Join Point
间接选择Join Point | 说明 |
---|---|
within(TypePattern) | 符合 TypePattern 的代码中的 Join Point |
withincode(MethodPattern) | 在某些方法中的 Join Point |
withincode(ConstructorPattern) | 在某些构造函数中的 Join Point |
cflow(Pointcut) | Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,包括 P 本身 |
cflowbelow(Pointcut) | Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,不包括 P 本身 |
this(Type or Id) | Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型 |
target(Type or Id) | Join Point 所在的对象(例如 call 或 execution 操作符应用的对象)是否 instanceOf Type 或者 Id 的类型 |
args(Type or Id, …) | 方法或构造函数参数的类型 |
if(BooleanExpression) | 满足表达式的 Join Point,表达式只能使用静态属性、Pointcuts 或 Advice 暴露的参数、thisJoinPoint 对象 |
直接选择Join Point
理解
call和execution
@After("call(* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))")
call的意思说,在所有调用MainActivity的showMsg的时候调用,注意我这里说的是调用哦
execution的意思是说,直接方法内部的前或者后添加
@After丶@Before
插入代码前和后的区别,当然如果是call,就是在调用前插入,同理execution是在方法内部的签名
public void showMsg(String msg){
Log.d("wyz", msg);
}
// 下面是AspectJ代码,call是调用的前面插入哦
@After("call(* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))")
public void showTaost(JoinPoint joinPoint) {
Log.d("wyz", "AspectJ:牛B个锤子");
}
如果调用 showMsg(“我好牛B哦”);
输出顺序是:我好牛币哦丶AspectJ:牛B个锤子
public void showMsg(String msg){
Log.d("wyz", msg);
}
// 下面是AspectJ代码,call是在调用的后面插入哦
@Before("call(* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))")
public void showTaost(JoinPoint joinPoint) {
Log.d("wyz", "AspectJ:牛B个锤子");
}
输出顺序是:AspectJ:牛B个锤子丶我好牛币哦
withincode
我的理解是编写条件的一个操作符吧
例如,上门的showMsg方法在2个地方调用了,一个是onCreate和onResume都调用了showMsg方法,但是我只想对onCreate方法拦截呢?
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showMsg("我是onCreate的好牛B");
}
@Override
protected void onResume() {
super.onResume();
showMsg("我是onResume的好牛B");
}
public void showMsg(String msg){
Log.d("wyz", msg);
}
}
AspectJ代码
@Before("call(* com.mmvoice.aspectjdemo.MainActivity.showMsg(java.lang.String))" +
" && " +
"withincode(* com.mmvoice.aspectjdemo.MainActivity.onCreate(..))")
public void showTaost(JoinPoint joinPoint) {
Log.d("wyz", "AspectJ:牛B个锤子");
}
运行结果,可以看到只注入了onCreate方法
within丶this
这个和withincode有啥区别呢?我的个人理解是withincode是作用于方法,而within的范围要光一个写作用于类
,例如
public class ToastUtil {
private ToastUtil(){
if (SingletonHolder.instance != null) {
throw new IllegalStateException();
}
}
private static class SingletonHolder {
private static ToastUtil instance = new ToastUtil();
}
public static ToastUtil getInstance(){
return SingletonHolder.instance;
}
public void shwoToast(Context context, String msg){
Toast.makeText(context,msg,Toast.LENGTH_LONG).show();
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToastUtil.getInstance().shwoToast(getApplicationContext(),getClass().getSimpleName());
showMsg("我是onCreate的好牛B");
}
}
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
ToastUtil.getInstance().shwoToast(getApplicationContext(),getClass().getSimpleName());
}
}
可以看到ToastUtil有2处调用了,那如果我只想注入MainActivity的呢?
@After("call(* com.mmvoice.aspectjdemo.ToastUtil.showToast(..))" +
" && " +
"!within(com.mmvoice.aspectjdemo.Main2Activity)")
public void hookToastUtil(JoinPoint joinPoint) {
Log.d("wyz", "AspectJ:ToastUtil调用了:" + joinPoint.getArgs()[1]);
}
这里使用within,这里我使用了1个!,java中非的意思,所以语法是支持,&& ,||,!
这里补充下this,上面也解释的很清楚,代码如下
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
ToastUtil.getInstance().showToast(getApplicationContext(),getClass().getSimpleName());
NeiClass.call(getApplicationContext());
}
public static class NeiClass{
public static void call(Context context){
ToastUtil.getInstance().showToast(context,NeiClass.class.getSimpleName());
}
}
}
如果使用的是within,Main2Activity的内部类,也不会注入的,但是使用this就能注入哦
@Around
例如我想在一个方法调用前做操作,调用后做操作?最简单的,统计MainActivity的所有方法的调用时间,有认识用@Before+@After不就行了吗?不行哦,前面说过这2个不能同时作用于一个方法哦
@Around("execution(* com.mmvoice.aspectjdemo.MainActivity.*(..))")
public Object statisticsTime(ProceedingJoinPoint joinPoint) {
Object proceed = null;
long start = System.currentTimeMillis();
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
Log.d("wyz", joinPoint.getSignature().getName() + ":执行时间:" + (end - start));
return proceed;
}
这里注意一个地方哦,关于使用@Around有没有返回值,最好都写一下返回,我一开始就没写返回,一直报错,花了好长时间,以后只要使用@Around就都写返回,绝对不会报错
args
args是用于方法参数的描述,例如args(int,…),方法参数为int开头的,举个例子代码
@Pointcut("execution(* com.mmvoice.aspectjdemo.MainActivity.testParameter(..)) && args(data,value)")
public void getParameter(String data, int value) {
}
上门定义了一个Pointcut,看条件的意思是com.mmvoice.aspectjdemo.MainActivity的testParameter方法且参数第一个为String类型,第二个为int类型,注意args里面参数,要和下面方法参数昵称必须一致
我们可以这样引用
@Before("getParameter(data,value)")
public void hookParameter(JoinPoint joinPoint, String data, int value) {
Log.d("wyz", "hook:" + data + " " + value);
}
总结
方法的先讲到这里,我也理解了这些,后来慢慢补充