1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 通过反射动态修改自定义注解属性值

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

时间:2020-05-21 12:16:43

相关推荐

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

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

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)@Componentpublic @interface TestAnno {String name() default "";}

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

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

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

@Servicepublic 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 {@Overridepublic 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类所实现的接口方法的调用都会触发此处理器@Overridepublic 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表示先执行此aoppublic class AuthDemoAspect implements EnvironmentAware {Environment environment;@Overridepublic 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 这个代理实例所持有的 InvocationHandlerInvocationHandler h = Proxy.getInvocationHandler(testAnno);// 获取 AnnotationInvocationHandler 的 memberValues 字段Field hField = h.getClass().getDeclaredField("memberValues");// 因为这个字段事 private final 修饰,所以要打开权限hField.setAccessible(true);// 获取 memberValuesMap 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;@Overridepublic 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}值动态修改成了配置文件里面配置的“科比”了。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。