Javaは反射によって、注釈のある属性値をどうやって動的に修正しますか?


Java反射は、注釈の属性値を動的に修正します。
昨夜は一つの問題を見ましたが、スレ主は複数のSpringのタイミングタスクを動的に構築することを望んでいます。
このテーマはあまり詳しくないですが、テーマによってはSpringの作成タイミングに関する資料を説明して調べてみます。これはJavaコードを通じて注釈の属性値を動的に修正することにつながるかもしれません。
今日はそれを試してみました。
反射によって注釈の属性値を動的に修正することができます。
ご存知のように、java/lang/reflectというカバンの下にはJavaの反射類と道具があります。
Annotationの注釈もこのカバンの中にあります。注釈はJava 5.0バージョンが導入されてから、Javaプラットフォームの中の非常に重要な一部になりました。よくあるのは@Override、@Deprecatedなどです。
注解の詳細な情報と使い方については、インターネット上にすでに多くの資料がありますので、ここでは詳しく説明しません。
一つの注釈は@Retensionによってそのライフサイクルを指定し、ここで論じた動的に注釈属性値を修正して、@Retension Policy.RUNTIMに建立された。この注釈は動作時に反射機構によって操作することができる。
では、@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;
}
次に、メインメソッドで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類のある方法での呼び出しをブロックできますが、Fnc 1の呼び出しをブロックするしかないです。なぜですか?
それでは注意します
Class Loaderこれはクラスです。注釈も例外ではありません。注釈とinterfacesは何の関係がありますか?
注釈は本質的にインターフェースであり、その本質はinterface SomeAnnotation extens Annotationと定義されている。このAnnotationインターフェースはjava/lang/annotationパッケージにあります。その注釈の中の最初の言葉はThe common interface exteded by all annotation typesです。
このように、Foo注解自体はインターフェースだけで、コードロジックがないということですが、value属性はどこにありますか?
fooを展開すると、

このProxyの例はAnnotationInvocationHandlerを持っていますが、どうやってProxyを作成するかという話を前にしましたか?三つ目のパラメータはInvocationHandlerです。
名前のハンドルは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 */    
}
私たちはひと目で面白い名前を見ることができます。これは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です。
注釈の詳細な情報と使用方法については、ネット上で多くの資料があります。自分で調べます。
一つの注釈は@Retensionによってそのライフサイクルを指定し、ここで論じた動的に注釈属性値を修正して、@Retension Policy.RUNTIMに建立された。
この注釈は運転中に反射機構により属性を修正する操作が可能である。
まずカスタムコメントを定義します。@TestAnno
これはStringのname属性のタイプがあります。この注釈はまたMethodに適用されます。

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface TestAnno { 
   String name() default ""; 
}
ユーザー定義の注釈を使って、まず注解の値がどこにあるかを確認しなければなりません。メールでテストしてもいいです。
反射で注釈@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; //      
            }
        }
    );
}
注意しました
Class Loaderこれはクラスです。注釈も例外ではありません。注釈とinterfacesは何の関係がありますか?
注釈は本質的にインターフェースであり、その本質はinterface SomeAnnotation extens Annotationと定義されている。
このAnnotationインターフェースはjava/lang/annotationパッケージにあります。その注釈の中の最初の言葉はThe common interface exteded by all annotation typesです。
そういえば、@TestAnno注解自体はインターフェースだけで、コードロジックがないということですが、value属性はどこにありますか?
展開@TestAnnoは発見できます。

このProxyの例はAnnotationInvocationHandlerを持っていますが、どうやってProxyを作成するかという話を前にしましたか?三つ目のパラメータはInvocationHandlerです。
名前のハンドルは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 */
   
}
私たちはひと目で面白い名前を見ることができます。これは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方法で注解した$
プロジェクト構成ファイル:appication.propertiesは一つの値を追加します。

nba.kobe=  
String s = environment.resolvePlaceholders(testAnno.name());
この行のコードは、元々の注釈値である$もしあなたが元の注釈のname値を修正するだけで、配置ファイルを取りに行くのではなく、この行のコードを使わずに、直接にmberrValuesの中の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;
    }
そしてプロジェクトを起動してRetryTest ServiceのretryTest()を呼び出すだけでaopに入ることができます。プリントの結果を確認してください。

結果として、最初のaopは確かにretryTest()@TestAnnoのname値をもとの@TestAnno($nba.kobe)$
以上は個人の経験ですので、参考にしていただければと思います。