自定义注解2-动态修改注解的属性值

2020-10-27 10:28:57 浏览数 (1)

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

找出注解中值存放位置

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

代码语言:txt复制
@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类生一个代理对象,有这个代理对象去调用真实方法,就像这样

代码语言:txt复制
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就是属性值。

修改注解值

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

代码语言:txt复制
@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打印的内容是不是你想要的。。。。

0 人点赞