记一次AOP+反射动态修改注解值成功后注解没有生效

记一次AOP+反射动态修改注解值成功后注解没有生效

最近重新看了一下反射,突发奇想,在运行的时候在不同的方法上放入不同的注解值,然后获取到注解值进行修改。于是拿了hirbernate的@Validated来玩玩。

我的想法是自定义注解里面@Validated是空的{},然后我们通过获取不同接口(路由)上的@Validation注解值,然后修改进去@Validated里面。

如果看不懂修改注解值,可以参考一下这篇文章。获取出来的Annotation其实是个Proxy对象。https://blog.csdn.net/qq_39309348/article/details/110455235

上代码。

自定义注解

@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Validated
public @interface Validation {

    Class<?>[] value() default {};

    interface PutGroup {
    }

    interface DeleteGroup {
    }

    interface ModifyGroup {
    }

    interface QueryGroup {
    }

    interface UpdateGroup extends DeleteGroup, ModifyGroup {
    }

}

这是Test类

@Data
public class Test {

    @NotNull(groups = {Validation.DeleteGroup.class})
    String a;

    @NotNull(groups = {Validation.DeleteGroup.class,Validation.ModifyGroup.class})
    String b;

    @NotNull(groups = {Validation.ModifyGroup.class})
    String c;
}

这是测试接口

@RequestMapping("/test")
    @SuppressWarnings("unchecked")
    public String test(@Validation(Validation.ModifyGroup.class) Test test) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
        // 这里通过反射获取到@Validation注解里面@Validated的注解值
        Method method = TestController.class.getMethod("test", Test.class);
        int parameterCount = method.getParameterCount();
        Annotation[][] pas = method.getParameterAnnotations();
        tag:
        for (int i = 0; i < parameterCount; i++) {
            for (Annotation annotation : pas[i]) {
                //判断注解是否为指定类型
                InvocationHandler ih = Proxy.getInvocationHandler(annotation);
                Field type = ih.getClass().getDeclaredField("type");
                type.setAccessible(true);
                Class<Validation> clazz = (Class<Validation>) type.get(ih);
                String name = clazz.getName();
                String validationName = Validation.class.getName();
                if (StringUtils.equals(validationName, name)) {
                    // 拿到注解上的@Validated注解
                    Validated validated = clazz.getAnnotation(Validated.class);
                    InvocationHandler h = Proxy.getInvocationHandler(validated);
                    Field memberValues = h.getClass().getDeclaredField("memberValues");
                    memberValues.setAccessible(true);
                    Map<String, Object> map = (Map<String, Object>) memberValues.get(h);
                    Class<?>[] value = validated.value();
                    if (value.length != 0) {
                        for (Class<?> aClass : value) {
                            System.out.println("修改后在test中的值为:" + aClass);
                        }
                    }else{
                        System.out.println("修改后没有值");
                    }
                }
                break tag;
            }
        }
        return test.toString();
    }

AOP拦截@RequestMapping

@Aspect
@Component
public class TimeAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void beforeTime() {
    }

    @Before("beforeTime()")
    @SuppressWarnings("unchecked")
    public void dynamicModify(JoinPoint joinPoint) throws NoSuchFieldException, IllegalAccessException {
        //获取参数注解及个数
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        int parameterCount = method.getParameterCount();
        Annotation[][] pas = method.getParameterAnnotations();
        //遍历
       tag:
        for (int i = 0; i < parameterCount; i++) {
            for (Annotation annotation : pas[i]) {
                //判断注解是否为指定类型
                InvocationHandler ih = Proxy.getInvocationHandler(annotation);
                Field type = ih.getClass().getDeclaredField("type");
                type.setAccessible(true);
                Class<Validation> clazz = (Class<Validation>) type.get(ih);
                String name = clazz.getName();
                String validationName = Validation.class.getName();
                if (StringUtils.equals(validationName, name)) {
                    // 拿到注解值
                    Validation validation = (Validation) annotation;
                    Class<?>[] valueCustomer = validation.value();
                    // 拿到注解上的@Validated注解
                    Validated validated = clazz.getAnnotation(Validated.class);
                    InvocationHandler h = Proxy.getInvocationHandler(validated);
                    Field memberValues = h.getClass().getDeclaredField("memberValues");
                    memberValues.setAccessible(true);
                    Map<String, Object> map = (Map<String, Object>) memberValues.get(h);
                    Class<?>[] value = validated.value();
                    if (value.length != 0) {
                        for (Class<?> aClass : value) {
                            System.out.println("修改前的值为:" + aClass);
                        }
                    }else{
                        System.out.println("修改前没有值");
                    }
                    map.put("value", valueCustomer);
                    Class<?>[] value1 = validated.value();
                    if (value1.length != 0) {
                        for (Class<?> aClass : value1) {
                            System.out.println("修改后的值为:" + aClass);
                        }
                    }
                }
                break tag;
            }
        }
    }
}

运行后输出为:
在这里插入图片描述
这里很明显已经成功修改了。无论是前置通知还是方法中。可是如果修改成功,@Validated应该会生效才对的啊。

百思不得其解。那就干脆直接拿@Validated来试试吧。

修改一下

测试接口

@RequestMapping("/test")
    @SuppressWarnings("unchecked")
    public String test(@Validated(Validation.DeleteGroup.class) Test test) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
        Method method = TestController.class.getMethod("test", Test.class);
        int parameterCount = method.getParameterCount();
        Annotation[][] pas = method.getParameterAnnotations();
        //遍历
        tag:
        for (int i = 0; i < parameterCount; i++) {
            for (Annotation annotation : pas[i]) {
                //判断注解是否为指定类型
                InvocationHandler ih = Proxy.getInvocationHandler(annotation);
                Field type = ih.getClass().getDeclaredField("type");
                type.setAccessible(true);
                Class clazz = (Class) type.get(ih);
                String name = clazz.getName();
                String validatedName = Validated.class.getName();
                System.out.println(name.equals(validatedName));
                if (StringUtils.equals(validatedName, name)) {
                    // 拿到注解值
                    Validated validated = (Validated) annotation;
                    Class<?>[] valueCustomer = validated.value();
                    if (valueCustomer.length != 0) {
                        for (Class<?> aClass : valueCustomer) {
                            System.out.println("修改后在test方法中的注解值为:" + aClass);
                        }
                    }
                }
                break tag;
            }
        }
        return test.toString();
    }

AOP改为直接修改@Validated值,从Validation.DeleteGroup.class改成Validation.ModifyGroup.class


@Aspect
@Component
@Slf4j(topic = "log")
public class TimeAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void beforeTime() {
    }


    @Before("beforeTime()")
    @SuppressWarnings("unchecked")
    public void dynamicModify(JoinPoint joinPoint) throws NoSuchFieldException, IllegalAccessException {
        //获取参数注解及个数
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        int parameterCount = method.getParameterCount();
        Annotation[][] pas = method.getParameterAnnotations();
        //遍历
        tag:
        for (int i = 0; i < parameterCount; i++) {
            for (Annotation annotation : pas[i]) {
                //判断注解是否为指定类型
                InvocationHandler ih = Proxy.getInvocationHandler(annotation);
                Field type = ih.getClass().getDeclaredField("type");
                type.setAccessible(true);
                Class clazz = (Class) type.get(ih);
                String name = clazz.getName();
                String validatedName = Validated.class.getName();
                System.out.println(name.equals(validatedName));
                if (StringUtils.equals(validatedName, name)) {
                    // 拿到注解值
                    Validated validated = (Validated) annotation;
                    Class<?>[] valueCustomer = validated.value();
                    if (valueCustomer.length != 0) {
                        for (Class<?> aClass : valueCustomer) {
                            System.out.println("修改前注解值为:" + aClass);
                        }
                    }

                    //修改注解值
                    Field memberValues = ih.getClass().getDeclaredField("memberValues");
                    memberValues.setAccessible(true);
                    Map map = (Map) memberValues.get(ih);
                    map.put("value", new Class[]{Validation.ModifyGroup.class});

                    Class<?>[] value = validated.value();
                    for (Class<?> aClass : value) {
                        System.out.println("修改后注解值为:" + aClass);
                    }

                }
                break tag;
            }
        }
    }
}

测试结果为
在这里插入图片描述
结果发现这个@Validated的值又被改回来了。

结论

反射只是相当于一个镜子,通过运行时类对象看到类的结构。我们通过@Validation修改的@Validated的值,仅仅是在@Validation这个类对象Class上的值。并不能修改到.class文件里面去。而@Validated去校验的时候,还是读取.class的值。所以看似修改了,实质还是没有起效果。

同样的,当我们直接使用@Validated的时候,虽然在增强中是修改了,但是一进行校验,@Validated底层又去.class文件读取。所以值就被修改回来了。

有错误的话希望路过的大佬可以指点一下


版权声明:本文为qq_39309348原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。