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

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上:

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Foo {

String value();

}

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

public class Bar {

@Foo ("fffhttp://")

private String val;

}

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

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

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()));

            

            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 memberValues;

private transient volatile Method[] memberMethods=null;     

}

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

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

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()));

            

            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 memberValues;

private transient volatile Method[] memberMethods=null;

}

我们一眼就可以看到一个有意思的名字: 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(){

}





@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);

        // 获取  的 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(){

} 





@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}值动态修改成了配置文件里面配置的“科比”了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值