资讯专栏INFORMATION COLUMN

[Java] 通过反射,动态修改注解的某个属性值

Kyxy / 3477人阅读

摘要:今天对此尝试了一番,发现通过反射来动态修改注解的属性值是可以做到的众所周知,这个包下面都是的反射类和工具。一个注解通过指定其生命周期,本文所讨论的动态修改注解属性值,建立在这种情况。

昨晚看到一条问题,大意是楼主希望可以动态得建立多个Spring 的定时任务。

这个题目我并不是很熟悉,不过根据题目描述和查阅相关 Spring 创建定时任务 的资料,发现这也许涉及到通过Java代码动态修改注解的属性值。

今天对此尝试了一番,发现通过反射来动态修改注解的属性值是可以做到的:

众所周知,java/lang/reflect 这个包下面都是Java的反射类和工具。

Annotation 注解,也是位于这个包里的。注解自从Java 5.0版本引入后,就成为了Java平台中非常重要的一部分,常见的如 @Override@Deprecated

关于注解更详细的信息和使用方法,网上已经有很多资料,这里就不再赘述了。

一个注解通过 @Retention 指定其生命周期,本文所讨论的动态修改注解属性值,建立在 @Retention(RetentionPolicy.RUNTIM) 这种情况。毕竟这种注解才能在运行时(runtime)通过反射机制进行操作。

那么现在我们定义一个 @Foo 注解,它有一个类型为 Stringvalue 属性,该注解应用再Field上:

/**
 * Created by krun on 2017/9/18.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
    String value();
}

再定义一个普通的Java对象 Bar,它有一个私有的String属性 val,并为它设置属性值为"fff"@Foo 注解:

public class Bar {

    @Foo ("fff")
    private String val;
}

接下来在 main 方法中我们来尝试修改 Bar.val 上的 @Foo注解的属性值为 "ffffd"

先是正常的获取注解属性值:

/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException {
        //获取Bar实例
        Bar bar = new Bar();
        //获取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //获取val字段上的Foo注解实例
        Foo foo = field.getAnnotation(Foo.class);
        //获取Foo注解实例的 value 属性值
        String value = foo.value();
        //打印该值
        System.out.println(value); // fff
    }
}

首先,我们要知道注解的值是存在哪里的。

String value = foo.value(); 处下断点,我们跑一下可以发现:

当前栈中有这么几个变量,不过其中有一点很特别:foo,其实是个Proxy实例。

Proxy也是 java/lang/reflect下的东西,它的作用是为一个Java类生成一个代理,就像这样:

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; //返回调用结果
            }
        }
    );
}

这样你就可以拦截这个Java类的某个方法调用,但是你只能拦截到 func1的调用,想想为什么?

那么注意了:

ClassLoader 这是个class就会有,注解也不例外。那么注解和interfaces有什么关系?

注解本质上就是一个接口,它的实质定义为: interface SomeAnnotation extends Annotation
这个 Annotation 接口位于 java/lang/annotation 包,它的注释中第一句话就是 The common interface extended by all annotation types.

如此说来,Foo 注解本身只是个接口,这就意味着它没有任何代码逻辑,那么它的 value 属性究竟是存在哪里的呢?

展开 foo 可以发现:

这个 Proxy 实例持有一个 AnnotationInvocationHandler,还记得之前提到过如何创建一个 Proxy 实例么? 第三个参数就是一个 InvocationHandler
看名字这个handler即是Annotation所特有的,我们看一下它的代码:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

    private final Class type;
    private final Map memberValues;
    private transient volatile Method[] memberMethods = null;
    
    /* 后续无关代码就省略了,想看的话可以查看 sun/reflect/annotation/AnnotationInvocationHandler */
   
}

我们一眼就可以看到一个有意思的名字: memberValues,这是一个Map,而断点中可以看到这是一个 LinknedHashMapkey为注解的属性名称,value即为注解的属性值。

现在我们找到了注解的属性值存在哪里了,那么接下来的事就好办了:

/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException, IllegalAccessException {
        //获取Bar实例
        Bar bar = new Bar();
        //获取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //获取val字段上的Foo注解实例
        Foo foo = field.getAnnotation(Foo.class);
        //获取 foo 这个代理实例所持有的 InvocationHandler
        InvocationHandler h = Proxy.getInvocationHandler(foo);
        // 获取 AnnotationInvocationHandler 的 memberValues 字段
        Field hField = h.getClass().getDeclaredField("memberValues");
        // 因为这个字段事 private final 修饰,所以要打开权限
        hField.setAccessible(true);
        // 获取 memberValues
        Map memberValues = (Map) hField.get(h);
        // 修改 value 属性值
        memberValues.put("value", "ffffd");
        // 获取 foo 的 value 属性值
        String value = foo.value();
        System.out.println(value); // ffffd
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70449.html

相关文章

  • 第12章 元编程与注解反射 《Kotlin 项目实战开发》

    摘要:第章元编程与注解反射反射是在运行时获取类的函数方法属性父类接口注解元数据泛型信息等类的内部信息的机制。本章介绍中的注解与反射编程的相关内容。元编程本质上是一种对源代码本身进行高层次抽象的编码技术。反射是促进元编程的一种很有价值的语言特性。 第12章 元编程与注解、反射 反射(Reflection)是在运行时获取类的函数(方法)、属性、父类、接口、注解元数据、泛型信息等类的内部信息的机...

    joyqi 评论0 收藏0
  • Spring入门IOC和AOP学习笔记

    摘要:入门和学习笔记概述框架的核心有两个容器作为超级大工厂,负责管理创建所有的对象,这些对象被称为。中的一些术语切面切面组织多个,放在切面中定义。 Spring入门IOC和AOP学习笔记 概述 Spring框架的核心有两个: Spring容器作为超级大工厂,负责管理、创建所有的Java对象,这些Java对象被称为Bean。 Spring容器管理容器中Bean之间的依赖关系,使用一种叫做依赖...

    wenyiweb 评论0 收藏0
  • 反射(Reflection)

    摘要:装载类的装载是通过类加载器完成的,加载器将文件的字节码文件装入的方法区,并且在堆区创建描述这个类的对象。通过指定的对象来实例化对象取得父指定的构造类型给传入参数赋初值实例化反射操作获得某个类的所有的字段,包括父类。 什么是反射 反射就是在运行时把 Java 类中的各种成分映射成相应的 Java 类(Method、Annotation等),可以动态得获取所有的属性以及动态调用任意一个方法...

    LMou 评论0 收藏0
  • 造个轮子,我学到了什么

    摘要:阅读原文造个轮子我学到了什么听说的最多的是不是不要重复的造轮子不要被这句话蒙骗了,这句话应该还没说完整,在什么情况下不要造轮子实际项目中由于工期和质量原因,肯定不希望你造轮子,你造轮子花费时间且质量不如现有的轮子。 阅读原文:造个轮子,我学到了什么 听说的最多的是不是不要重复的造轮子?不要被这句话蒙骗了,这句话应该还没说完整,在什么情况下不要造轮子?实际项目中由于工期和质量原因,肯定不...

    Acceml 评论0 收藏0
  • Java基础知识整理之注解

    摘要:注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素类方法成员变量等进行关联。为程序的元素类方法成员变量加上更直观更明了的说明,这些说明与程序的业务逻辑无关,并且提供给指定的工具或框架使用。 什么是注解? Annotation 是 Java5 之后开始引入的新特性,中文为注解。注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(...

    blastz 评论0 收藏0

发表评论

0条评论

Kyxy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<