经过上一节的,我们可以自己解析spel表达式。那么我现在的想法是,在注解的第一层aop中解析spel,然后将解析后的值设置到属性中,那么在之后的aop中就不用解析了。

找出注解中值存放位置

    继续上一节的代码,在上一节的AOP中添加注解@Order(0),再新增一个注解,添加@Order(1)。注意order这个注解有坑的,最好先百度完再使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Aspect
@Order(0)
public class InterestResolveELAspect {
// resolve spel...
}

@Component
@Aspect
@Order(1)
public class InterestHandleAspect {
@Around("@annotation(anno)")
public Object invoked(ProceedingJoinPoint pjp, Interest anno) throws Throwable {
String key = anno.key();
String unless = anno.unless();
logger.info("call InterestHandleAspect.invoked, resolvedKey:[{}], resolvedUnless:[{}]"
, key, unless);
return pjp.proceed();
}
}

    我们在上一节代码中的String keySpel = anno.key()打下断点。查看当前栈的变量。
操作流程图
    发现注解的对象是一个Proxy的实例,Proxy的作用就是为java类生一个代理对象,有这个代理对象去调用真实方法,就像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface A {
String func1();
}

public class B implements A {

@Override
public String func1() { //do something ... }

public String func2() { //do something ... };
}

public static void main(String ...args) {
B bInstance = new B();

B bProxy = Proxy.newProxyInstance(
B.class.getClassLoader(), // B 类的类加载器
B.class.getInterfaces(), // B 类所实现的接口,如果你想拦截B类的某个方法,必须让这个方法在某个接口中声明并让B类实现该接口
new InvocationHandler() { // 调用处理器,任何对 B类所实现的接口方法的调用都会触发此处理器
@Override
public Object invoke (Object proxy, // 这个是代理的实例,method.invoke时不能使用这个,否则会死循环
Method method, // 触发的接口方法
Object[] args // 此次调用该方法的参数
) throws Throwable {
System.out.println(String.format("调用 %s 之前", method.getName()));
/**
* 这里必须使用B类的某个具体实现类的实例,因为触发时这里的method只是一个接口方法的引用,
* 也就是说它是空的,你需要为它指定具有逻辑的上下文(bInstance)。
*/
Object obj = method.invoke(bInstance, args);
System.out.println(String.format("调用 %s 之后", method.getName()));
return obj; //返回调用结果
}
}
);
}

    再回想注解实质上是一个接口,它本身没有逻辑,那么它的值存在什么地方呢?那么答案就是Proxy实例中了。
    这个Proxy实例有一个类型为AnnotationInvocationHandler的变量h,我回到上面创建Proxy对象的代码中,Proxy.newProxyInstance()的第三个参数就是InvocationHandler,而这个变量h就是它的实现类。
    继续往变量h里看,它有一个字段memberValues,是一个map,而在这个map中,我发现了注解值存放的位置。key为注解的属性名,value就是属性值。

修改注解值

    找到了注解值存放位置,那么修改就简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Aspect
@Order(0)
public class InterestResolveELAspect {
@Around("@annotation(anno)")
public Object invoked(ProceedingJoinPoint pjp, Interest anno) throws Throwable {
// resolve spel
String key = resolve spel;
Boolean unless = resolve spel;

InvocationHandler h = Proxy.getInvocationHandler(anno);
Field hField = h.getClass().getDeclaredField("memberValues");
hField.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) hField.get(h);
memberValues.put("key", key);
memberValues.put("unless", unless.toString());

return pjp.proceed();
}
}

    赶紧测试一下,看看InterestHandleAspect打印的内容是不是你想要的。。。。