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

Java反射动态修改注解的某个属性值

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

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

今天对此尝试了一番,

发现通过反射来动态修改注解的属性值是可以做到的:

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

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

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

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

那么现在我们定义一个@Foo注解,它有一个类型为String的value属性,该注解应用再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注解的属性值为"ddd"。

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

/**
 * 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<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;
    /* 后续无关代码就省略了,想看的话可以查看 sun/reflect/annotation/AnnotationInvocationHandler */
}

我们一眼就可以看到一个有意思的名字:memberValues,这是一个Map,而断点中可以看到这是一个LinknedHashMap,key为注解的属性名称,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", "ddd");
        // 获取 foo 的 value 属性值
        String value = foo.value();
        System.out.println(value); // ddd
    }
}

通过反射动态修改自定义注解属性值

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

Annotation 注解,也是位于这个包里的。

注解自从Java 5.0版本引入后,就成为了Java平台中非常重要的一部分,常见的有 @Override、 @Deprecated

关于注解更详细的信息和使用方法,网上已经有很多资料,自行查看。

一个注解通过 @Retention 指定其生命周期,本文所讨论的动态修改注解属性值,建立在 @Retention(RetentionPolicy.RUNTIM) 这种情况。

这种注解才能在运行时(runtime)通过反射机制进行修改属性的操作。

我们先定义一个自定义注解 @TestAnno

它有一个类型为 String 的 name属性,该注解应用再Method上:

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface TestAnno {
   String name() default "";
}

我用自定义注解首先得了解清楚注解的值存储在什么地方,我们可以写个main方法测试一下:

通过反射获取注解@TestAnno的值

我们定义了一个RetryTestService 在它的方法 retryTest() 上添加@TestAnno 注解,然后在main方法里面反射获取注解的name值

@Service
public class RetryTestService {
    @TimeLog
    @TestAnno(name = "${nba.kobe}")
    public String retryTest(){
        System.out.println("---进行了接口请求....");
        return "success";
    }
    public static void main(String[] args) throws NoSuchMethodException {
        RetryTestService service = new RetryTestService();
        Method method = service.getClass().getDeclaredMethod("retryTest",null);
        TestAnno testAnno = method.getDeclaredAnnotation(TestAnno.class);
        System.out.println(testAnno.name());
    }
}

当前栈中有这么几个变量,不过其中有一点很特别:@TestAnno,其实是个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; //返回调用结果
            }
        }
    );
}

注意了:

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

注解本质上就是一个接口,它的实质定义为: interface SomeAnnotation extends Annotation。

这个 Annotation 接口位于 java/lang/annotation 包,它的注释中第一句话就是 The common interface extended by all annotation types.

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

展开 @TestAnno 可以发现:

这个 Proxy 实例持有一个 AnnotationInvocationHandler,还记得之前提到过如何创建一个 Proxy 实例么? 第三个参数就是一个 InvocationHandler。

看名字这个handler即是Annotation所特有的,我们看一下它的代码:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;

    /* 后续无关代码就省略了,想看的话可以查看 sun/reflect/annotation/AnnotationInvocationHandler */

}

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

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

我这里写两个aop。第一个aop拦截带@TestAnno注解的方法,然后改变注解的name值,第二个aop我们再把注解的name值打印出来,看看是不是真被改了

第一个aop:

@Aspect
@Component
@Order(1) //aop执行顺序1表示先执行此aop
public class AuthDemoAspect implements EnvironmentAware {
    Environment environment;
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    } 

    @Pointcut("@annotation(com.ali.hangz.tooltest.config.TestAnno)")
    public void myPointCut() {
    } 

    @Before(value = "myPointCut()")
    public void check(){
    } 

    @After(value = "myPointCut()")
    public void bye(){
    }

    /**
     *配置文件配置
     * @return
     */
    @Around("myPointCut() && @annotation(testAnno)")
    public Object around(ProceedingJoinPoint joinPoint, TestAnno testAnno){
        try {
            System.out.println("---修改前注解@TestAnno的name指为:" + testAnno.name());
            String s = environment.resolvePlaceholders(testAnno.name());
            //获取 foo 这个代理实例所持有的 InvocationHandler
            InvocationHandler h = Proxy.getInvocationHandler(testAnno);
            // 获取 AnnotationInvocationHandler 的 memberValues 字段
            Field hField = h.getClass().getDeclaredField("memberValues");
            // 因为这个字段事 private final 修饰,所以要打开权限
            hField.setAccessible(true);
            // 获取 memberValues
            Map memberValues = (Map) hField.get(h);
            // 修改 value 属性值
            memberValues.put("name",s);
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }
}

第一个aop里面我改变注解的name值,由上面service方法上注解的${nba.kobe} 改成读取配置文件 nba.kobe的配置值

项目配置文件:application.properties增加一个值

nba.kobe=科比
String s = environment.resolvePlaceholders(testAnno.name());

这行代码其实就是通过原本注解值${nba.kobe}去配置文件取nba.kobe 对应的值。如果你只是修改原来注解的name值而不是去取配置文件大可以不用此行代码,直接给memberValues 里面的name put新的值就行。

注意:@Order(1) 可以控制aop的执行顺序

然后我再写第二个aop,打印出注解@TestAnno 的name值看看是不是第一个aop已经成功把值改掉了

第二个aop:

@Aspect
@Component
@Order(2)
public class AuthDemoAspectTwo implements EnvironmentAware {
    Environment environment;
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    } 

    @Pointcut("@annotation(com.ali.hangz.tooltest.config.TestAnno)")
    public void myPointCut() {
    } 

    @Before(value = "myPointCut()")
    public void check(){
    } 

    @After(value = "myPointCut()")
    public void bye(){
    } 

    /**
     *配置文件配置
     * @return
     */
    @Around("myPointCut() && @annotation(testAnno)")
    public Object around(ProceedingJoinPoint joinPoint, TestAnno testAnno){
        try {
            System.out.println("---修改后的注解名称:" + testAnno.name());
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

然后我们只需要启动项目调用一下RetryTestService的 retryTest()方法 就可以进入aop 看看打印出来的结果了

通过结果我们可以发现第一个aop的确把retryTest()方法上面注解@TestAnno的name值由原先的 @TestAnno(name = "${nba.kobe}") ${nba.kobe}值动态修改成了配置文件里面配置的“科比”了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java反射机制根据属性名获取属性值的操作

    一.考虑安全访问范围内的属性,没有权限访问到的属性不读取 /** * 根据属性名获取属性值 * * @param fieldName * @param object * @return */ private String getFieldValueByFieldName(String fieldName, Object object) { try { Field field = object.getClass().getField(fieldName); //设置对象的访问权限,保证对priva

  • 如何动态修改JavaBean中注解的参数值

    我这里有一个需求需要修改Person类中的一个属性上的注解的值进行修改,例如: public class Person { private int age; @ApiParam(access="lala") private String name; //get set 方法忽略 } 将@ApiParam(access="lala") 修改为@ApiParam(access="fafa"),经过分析是可以实现的,需要用到动态代理进行操作. 具体源码

  • Java通过反射机制动态设置对象属性值的方法

    /** * MethodName: getReflection<br> * Description:解析respXML 在通过反射设置对象属性值 * User: liqijing * Date:2015-7-19下午12:42:55 * @param clzzName * @param respXML * @return * @throws ClassNotFoundException * @throws DocumentException * @throws IllegalArgumentE

  • 如何利用反射批量修改java类某一属性的代码详解

    下面看下代码,具体代码如下所示: package utils.copyProperty; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; public class CopyProperty { public static Pro

  • Java 用反射设置对象的属性值实例详解

    Java 用反射设置对象的属性值实例详解 /** * 用反射设置对象的属性值 * @param obj 需要設置值的對象 * @param fieldName 需要設置值的屬性 * @param value 需要设置的值 * @return 设置值后的对象 */ private Object invoke(Object obj, String fieldName, Object value) { String firstWord = fieldName.substring(0, 1).toUpp

  • Java通过反射,如何动态修改注解的某个属性值

    Java反射动态修改注解的某个属性值 昨晚看到一条问题,大意是楼主希望可以动态得建立多个Spring 的定时任务. 这个题目我并不是很熟悉,不过根据题目描述和查阅相关Spring 创建定时任务的资料,发现这也许涉及到通过Java代码动态修改注解的属性值. 今天对此尝试了一番, 发现通过反射来动态修改注解的属性值是可以做到的: 众所周知,java/lang/reflect这个包下面都是Java的反射类和工具. Annotation注解,也是位于这个包里的.注解自从Java 5.0版本引入后,就成为

  • 在vue中动态修改css其中一个属性值操作

    我就废话不多说了,大家还是直接看代码吧~ <template> <!--此div的高度取屏幕可视区域的高度--> <div class="hello" :style="{'height':getClientHeight}"> <h5>{{ msg }}</h5> </div> </template> <script> export default { data() { r

  • Java使用反射和动态代理实现一个View注解绑定库

    目录 支持的功能 代码 注解类 实现类 使用 使用反射结合动态代理实现一个View注解绑定库,支持View和事件绑定,代码简洁,使用简单,扩展性强. 支持的功能 @ContentView 绑定layout 替代setContentView() @BindView 绑定View 替代findViewById() @OnClick 绑定点击事件 替代setOnClickListener() @OnLongClick 绑定长按事件 替代setOnLongClickListener() 代码 注解类 @

  • Java的反射机制---动态调用对象的简单方法

    唉!我还真是在面试中学习新东东啊,一个公司刚刚给了个测试,不过我很奇怪的是为什么web developer的职位居然考java的反射机制题,不过学习研究一下反射机制对我来说是件好事啦! 先说说什么是java反射机制吧,在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这 种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.主要功能:在运行时判断任意一个对象所属的类:在运行时构造任意一个类的对 象:在运行时判断任意一个

  • Java基于反射机制实现全部注解获取的方法示例

    本文实例讲述了Java基于反射机制实现全部注解获取的方法.分享给大家供大家参考,具体如下: 一 代码 class Info{ //给mytoString方法加了2个内建Annotation @Deprecated @SuppressWarnings(value = "This is a waring!") public String mytoString(){ return "hello world"; } } class GetAnnotations{ publi

  • java从list中取出对象并获得其属性值的方法

    最近公司的项目需要导出csv文件,一个同事用最原始的方式将每条记录取出然后加","解决.但是客户后面要求在每个页面当中都加入这个功能.于是,问题来了,分开写代码太多,合起来又不能确定在list中存储的对象为哪个对象,不能用get方法获得属性.我一直认为当初他那样写就将程序写死了.可是,在多次尝试后,还是通过java的反射从list中取出了对象,从对象中取出了属性值: 下面是代码: 复制代码 代码如下: package com.hb.test; import java.lang.refl

  • java利用反射实现动态代理示例

    复制代码 代码如下: package com.et59.cus.domain.dao.ex; import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Modifier; import org.apache.log4j.Logger;/** *  * <p>Title: ReflectUtil.java</p> * <p>Description: 反射&

  • java 实现反射 json动态转实体类--fastjson

    我就废话不多说了,大家还是直接看代码吧~ package test.refect; public class Student { // 姓名 private String name; // 年龄 private String age; // 住址 private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public

  • java中反射和注解的简单使用方法

    目录 什么反射? Java反射机制提供的功能 反射相关的主要API Class 类 获取Class 类的实例( 四种方法) 哪些类型可以有Class 对象? 演示Class类的常用方法 有了Class对象,能做什么? 调用运行时类的指定结构 1. 调用指定方法 关于setAccessible 调用Class对象的newInstance()方法 综合案例: 注解 什么是注解? 常见的Annotation JDK 中的元注解 自定义 Annotation 最后通过反射获取注解信息: 总结 什么反射?

随机推荐