2016-03-27 4 views
0

私はカスタムアノテーションを持つSpringビーンに注釈を付けましたが、ビーンの作成後にSpringがカスタムアノテーションを削除したようです。カスタムアノテーションを持つスプリングビーン

AnnotatedBean bean = ctx.getBean(AnnotatedBean.class); 

Foo.findAndDoStuffWithAnnotatedThings(bean); 

第2ステップは機能しません。私のカスタム注釈は失われます。 findAndDoStuffWithAnnotatedThings
で間違ったのものは豆がどこのクラスに渡される私のカスタム注釈の1

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface Condition { 

} 

の(Propablyによるプロキシもの)

マイ豆

@Rule(name = "RoutePickupRule") 
@Transactional 
@Component 
public class AnnotatedBean{ 


    @Autowired 
    private ICarpoolDoa carpoolDAO; 

    @Condition 
    public boolean condition(CustomLocation customLocation, String userId) { 
     //snip 
    } 

    @Action 
    public void action() { 
     //snip 
    } 

例私のカスタム注釈は検証されますが、私の検証者は注釈を見つけることができません。 (UtilはisAnnotationPresentメソッドを使用します)。再び、私が自分のbeanを "new"を使って作成するときは、問題はありません。

public class RuleAnnotationVerifier { 


    public void RuleAnnotationVerifier(final Object rule) { 
     checkRuleClass(rule); 
     checkConditionMethod(rule); 
     checkActionMethod(rule); 
    } 

    private void checkRuleClass(final Object rule) { 
     if (!Utils.isClassAnnotatedWith(rule.getClass(), Rule.class)) { 
      throw new IllegalArgumentException(String.format("Rule '%s' is not annotated with '%s'", rule.getClass().getName(), Rule.class.getName())); 
     } 

     if (rule.getClass().getAnnotation(Rule.class).name().isEmpty()) { 
      throw new IllegalArgumentException(String.format("Rule '%s' annotation is empty", rule.getClass().getName())); 
     } 
    } 
    ... 

Beanにカスタムアノテーションを保存する方法はありますか?クラスをBeanに変更する前に、私のプログラムが正しく動作していました。

なぜ私はこれをしたいですか?
私のクラス内のメソッドはリフレクションによって呼び出されますが、メソッドではクラスをBeanにする必要があるAutowired DOAを使用したいと思います。 :)私はを試みたが、
https://stackoverflow.com/a/14248490/3187643
はそれが回避策なしでは不可能と思われ、ここで答えを見つけることが
AopProxyUtils.ultimateTargetClass(豆)を

動作しませんでした何

。回答は3歳ですので、おそらく別の方法がありますか?

+0

注釈コードを表示しますか? https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicy.html –

+0

私の注釈は100%正確です。すべての機能が動作しないためですBeanを使用して(保持は実行時に設定されます)。古いスタックポストから回答を追加しました。私の問題のようなものは、解決策がないと思われます。/ –

+0

[ask]をお読みください。私はあなたのコードを頼んだので、それはSOの方針です。自分のコードを表示せずに「なぜ私のコードは機能しないのですか」と尋ねることはできません。私は今あなたの質問を閉じるために投票しました。 –

答えて

1

これは、同じことをやろうとしている間にこの問題を発見しました。つまり、Beanクラスのいくつかのメソッドに、自分の注釈を付けて後でハントし、リフレクションでそれらを実行するという意図で注釈を付けます。

豆がプロキシされると、いくつかの複雑さが生じ、これらの問題は、プロキシがCGLibプロキシかJDK動的プロキシのどちらであるかによっても影響を受けます。私はSpring 4.3.9を使用していくつかの実験を行い、動作の違いを観察しました。 (私はプロキシー実装の副作用にどれだけの意図があるのか​​不明であるため、将来のバージョンでは異なる動作をする可能性があります。また、AspectJ製織の使用効果も試していません)。

その理由は、Beanがプロキシされているためです(いずれのメソッドでも@Transactionalを使用した場合や、Beanを拡張するためにSpringのプロキシベースのAOP機能を使用した場合)。

プロキシがターゲットクラスにメソッドの重複を持っていますが、それは彼らの注釈を継承するか、コピーしません - あなたはプロキシのClassMethodを調べるときに、あなたは、元にあった注釈は表示されませんBeanのメソッド。(すなわち:プロキシオブジェクトラップ、デリゲートがそのことをやった後に呼び出して、実際のBeanインスタンス)

したがって、あなたの代わりにターゲット豆のClassを検査する必要がある、とAopProxyUtils.ultimateTargetClass()はあなたにこれを与えると思われます。元のbeanのClassが返され、そのメソッドを実行して注釈を見ることができます。

(あなたはまた、AopUtils.getTargetClass()を使用することができますが、それは、プロキシになるオブジェクトが別のプロキシ自体であることが可能です。ultimateTargetClass()を使用するとgetTargetClass()のみ1つのレベル下になるのに対し、プロキシチェーンのすべての方法を下に従うことになっています)。

どのようにしてultimateTargetClass()が「うまくいきませんでしたか」について詳しくは説明していませんが、プロキシは見つかったらそのメソッドを呼び出すためにいくつかの意味があります。あなたは方法のためターゲットのクラスをスキャンして以来

まず、何を持っていることは、ターゲットのクラスから来たMethodなくプロキシのクラスです。

この問題は、プロキシがCGLibプロキシかJDKプロキシかによって異なります。

CGLibプロキシの場合、プロキシオブジェクトはターゲットのクラスを拡張し、目的のクラスのMethodをそのまま使用してプロキシオブジェクトで呼び出すことができます。すなわち:

theAnnotatedMethod.invoke(theProxyObject, args...); 

しかし、それは動作しませんJDKプロキシである場合。 JDKプロキシはBeanのクラスを拡張せず、すべてのインタフェースを実装するだけなので、例外として「オブジェクトはクラスを宣言するインスタンスではありません」という例外が発生します。これを回避するには、プロキシ上のメソッドの邪悪な双子を見つけ、それでそれを呼び出す必要があります。

Method methodOnTheProxy = theProxyObject.getClass().getMethod(theAnnotatedMethod.getName(), 
theAnnotatedMethod.getParameterTypes()); 
methodOnTheProxy.invoke(theProxyObject, args...); 

これは動作します - しかし、あなたが起動しようとしている方法は、インターフェイスのいずれかによって宣言されている場合のみJDKは、Beanクラスによって実装されたインタフェースで宣言されたプロキシメソッドのみをプロキシするため、クラスが実装します。

アノテーションを付けて実装するBeanを宣言するすべてのメソッドのインタフェースを作成できますが、インタフェースを使用して呼び出すことができるときにリフレクションでそれらを見つけて呼び出すのはなぜですか?最初にこれらのメソッドのインターフェースの代わりに注釈を使用している理由に戻ります。

私の場合、この質問の研究につながったのは、インターフェイスで宣言された特定のタイプのBeanの特定のライフサイクルメソッドを廃止し、代わりに注釈を付けることです - onLoad、onHidden、onDisplayed、onRemoveなどのメソッドを検討します。私はそのようなメソッドをたくさん取得し始めており、多くの実装では実装が空の場合があります。そこでSpringのコントローラが@RequestMappingメソッドを宣言する方法や、単体テストの代わりにテストメソッドを@Testする方法に沿ったスタイルに移行したいと考えました。

JDKプロキシは、TYPEアノテーション(クラスレベルで適用するアノテーション)にも干渉します。通常、コンテキストメソッドgetBeansWithAnnotation()を使用して、指定された注釈でクラスが注釈付けされたBeanを検索できますが、BeanがJDKプロキシでプロキシされている場合はではなくがプロキシされますCGLibではまだ見つかるでしょう。(興味深いことに、どちらの場合も、プロキシのClassにあるisAnnotationPresent()を呼び出すとfalseが返されます)。また、BeanにJDKプロキシがある場合、getBeansOfType()と同じ問題が発生します。

これらのBeanにCGLibを使用することをSpringが推奨するかもしれないことを示唆しています。

デフォルトでは、Beanがメソッドを宣言するインタフェースを実装している場合、SpringはJDKプロキシを使用します(たとえそれらのインタフェースに実際にはないメソッドに@Transactionalを置いても!)。実装されたインタフェースがない場合、または宣言されたインタフェースのどれもがメソッドを宣言していない場合は、CGLibを使用します。参照:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies

この動作は、いくつかの場所で上書きすることができます。コンフィグレーションクラスで@EnableTransactionManagement(proxyTargetClass=false)を使用すると、プロキシされたBeanにCGLibを使用させることができます。古くなったTransactionPropertyFactoryBeanは、ネームスペース前XML構成で使用され、proxyTargetClassをプロパティとして指定できます。 XML名前空間の設定には<aop:config proxy-target-class="true">もあります。あなたが行く場合

 
    myBean is an AOP proxy 
    Using myBean instance of class com.example.AnnotatedProxyExample$$EnhancerBySpringCGLIB$$367d5296 returned from Spring context 
    ***Did not find any methods with MyAnnotation 

    Using the instance of class com.example.AnnotatedProxyExample returned by AopProxyUtils.ultimateTargetClass(MyBean): 
    Found MyAnnotation on secondMethod(), value=roti prata 
    object to invoke method on is a CGLib proxy 
    ***The object's class does not have the MyAnnotatedBean annotation 
    Invoking secondMethod() on object using annotated Method from the target's class 
    CALLED! secondMethod(), tx=false 
    Invoking secondMethod() on object using a matching Method from object's class 
    CALLED! secondMethod(), tx=false 

    Found MyAnnotation on thirdMethod(), value=economy bee hoon 
    object to invoke method on is a CGLib proxy 
    ***The object's class does not have the MyAnnotatedBean annotation 
    Invoking thirdMethod() on object using annotated Method from the target's class 
    CALLED! thirdMethod(), tx=true 
    Invoking thirdMethod() on object using a matching Method from object's class 
    CALLED! thirdMethod(), tx=true 

と:それは次の出力を与えるインタフェースを実装するトランザクションthirdMethod()を宣言していない

package com.example; 

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
import java.lang.reflect.Method; 
import java.util.Arrays; 
import java.util.Collection; 

import javax.sql.DataSource; 

import org.springframework.aop.framework.AopProxyUtils; 
import org.springframework.aop.support.AopUtils; 
import org.springframework.context.annotation.AnnotationConfigApplicationContext; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.jdbc.datasource.DataSourceTransactionManager; 
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 
import org.springframework.transaction.PlatformTransactionManager; 
import org.springframework.transaction.annotation.EnableTransactionManagement; 
import org.springframework.transaction.annotation.Transactional; 
import org.springframework.transaction.support.TransactionSynchronizationManager; 

/** 
* Example of finding methods with custom annotations when the bean is proxied 
* Dependencies: org.springframework/spring-core/4.3.9.RELEASE 
* org.springframework/spring-context/4.3.9.RELEASE 
* org.springframework/spring-tx/4.3.9.RELEASE 
* org.springframework/spring-jdbc/4.3.9.RELEASE org.hsqldb/hsqldb/2.4.0 (jdbc, 
* tx, and hsqldb are just there as a quick way of including Transactional as 
* the proxy example) 
*/ 
@MyAnnotatedBean 
public class AnnotatedProxyExample { 

    public static void main(String[] args) { 
    try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
     Config.class)) { 

     //Collection<?> beans = context.getBeansWithAnnotation(MyAnnotatedBean.class).values(); 
     //Collection<?> beans = context.getBeansOfType(AnnotatedProxyExample.class).values(); 
     Collection<?> beans = Arrays.asList(context.getBean("myBean")); 
     if(beans.isEmpty()) { 
     System.out.println("***No beans were found"); 
     } 
     else { 
     for(Object myBean : beans) { 

      if(AopUtils.isAopProxy(myBean)) { 
      System.out.println("myBean is an AOP proxy"); 
      } 
      else { 
      System.out.println("myBean is not an AOP proxy"); 
      } 

      System.out.println("Using myBean instance of class " 
       + myBean.getClass().getName() + " returned from Spring context"); 
      printAndCallMyAnnotatedMethods(myBean, myBean.getClass()); 

      Class<?> ultimateTargetClass = AopProxyUtils 
       .ultimateTargetClass(myBean); 
      if(ultimateTargetClass == myBean) { 
      System.out.println("(myBean is also the ultimateTarget of myBean)"); 
      } 
      else { 
      System.out.println("\nUsing the instance of class " 
       + ultimateTargetClass.getName() 
       + " returned by AopProxyUtils.ultimateTargetClass(MyBean):"); 
      printAndCallMyAnnotatedMethods(myBean, ultimateTargetClass); 
      } 
      System.out.println("---------------"); 
     } 
     } 
    } 
    } 

    private static void printAndCallMyAnnotatedMethods(Object myBean, 
     Class<?> targetClass) { 
    boolean foundAny = false; 
    for(Method method : targetClass.getMethods()) { 
     if(method.isAnnotationPresent(MyAnnotation.class)) { 
     foundAny = true; 
     MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); 
     System.out.println("Found MyAnnotation on " + method.getName() 
      + "(), value=" + annotation.value()); 
     invokeMethod(myBean, method); 
     System.out.println(); 
     } 
    } 
    if(!foundAny) { 
     System.out.println("***Did not find any methods with MyAnnotation"); 
    } 
    } 

    private static void invokeMethod(Object object, Method annotatedMethod) { 
    if(!AopUtils.isAopProxy(object)) { 
     System.out.println("object to invoke method on is not an AOP proxy"); 
    } 
    if(AopUtils.isCglibProxy(object)) { 
     System.out.println("object to invoke method on is a CGLib proxy"); 
    } 
    if(AopUtils.isJdkDynamicProxy(object)) { 
     System.out.println("object to invoke method on is a JDK proxy"); 
    } 
    String methodName = annotatedMethod.getName(); 
    Class<?> objectClass = object.getClass(); 

    if(objectClass.isAnnotationPresent(MyAnnotatedBean.class)) { 
     System.out 
      .println("The object's class has the MyAnnotatedBean annotation"); 
    } 
    else { 
     System.out.println(
      "***The object's class does not have the MyAnnotatedBean annotation"); 
    } 

    try { 
     //Call the method on the object, but using the Method from the target class 
     System.out.println("Invoking " + methodName 
      + "() on object using annotated Method from the target's class"); 
     annotatedMethod.invoke(object); 
    } catch(Exception e) { 
     System.out.println("*** Couldn't call " + methodName 
      + "() on instance of " + objectClass + " because " + e.getMessage()); 
    } 

    try { 
     //Find and call a method on object's actual class with the same signature as annotatedMethod 
     //nb: if object isn't a proxy this will be the same Method as the above 
     Method objectMethod = objectClass.getMethod(methodName, 
      annotatedMethod.getParameterTypes()); 
     if(objectMethod.equals(annotatedMethod)) { 
     System.out.println("(The target and object methods are the same here)"); 
     } 
     else { 
     System.out.println("Invoking " + methodName 
      + "() on object using a matching Method from object's class"); 
     objectMethod.invoke(object); 
     } 
    } catch(NoSuchMethodException notFound) { 
     System.out.println("***Couldn't find matching " + methodName 
      + "() on the instance of " + objectClass.getName()); 
    } catch(Exception e) { 
     System.out.println("*** Couldn't call " + methodName 
      + "() on instance of " + objectClass + " because " + e.getMessage()); 
    } 

    } 

    /////////////////////////////////////////////// 

    public void firstMethod() { 
    System.out.println("CALLED! firstMethod(), tx=" 
     + TransactionSynchronizationManager.isActualTransactionActive()); 
    } 

    @MyAnnotation("roti prata") 
    public void secondMethod() { 
    System.out.println("CALLED! secondMethod(), tx=" 
     + TransactionSynchronizationManager.isActualTransactionActive()); 
    } 

    @Transactional 
    @MyAnnotation("economy bee hoon") 
    public void thirdMethod() { 
    System.out.println("CALLED! thirdMethod(), tx=" 
     + TransactionSynchronizationManager.isActualTransactionActive()); 
    } 

} 

////////////////////////////////////////////////// 

interface MyInterface0 { 

} 

interface MyInterface1 { 
    //annotation won't be found because they aren't inherited from interfaces 
    @MyAnnotation("curry laksa") 
    public void firstMethod(); 
} 

interface MyInterface2 { 
    public void secondMethod(); 
} 

interface MyInterface3 { 
    public void thirdMethod(); 
} 

/** 
* Annotation that indicates which methods we want to find and call. 
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
@interface MyAnnotation { 
    public String value(); 
} 

////////////////////////////////////////////////// 

/** 
* Annotation that marks the classes of the beans we want to retrieve from the 
* context to search for methods having MyAnnotation 
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE) 
@interface MyAnnotatedBean { 
    ; 
} 

////////////////////////////////////////////////// 

//@EnableTransactionManagement(proxyTargetClass=true) 
@EnableTransactionManagement 
@Configuration 
class Config { 

    @Bean 
    public AnnotatedProxyExample myBean() { 
    return new AnnotatedProxyExample(); 
    } 

    @Bean 
    public PlatformTransactionManager transactionManager() { 
    DataSource ds = new EmbeddedDatabaseBuilder() 
     .setType(EmbeddedDatabaseType.HSQL).build(); 
    return new DataSourceTransactionManager(ds); 
    } 
} 

Use of proxies in Spring AOP

このすべてを説明するためのプログラム例:も参照してください。 をthirdMethod()から削除してから再度実行すると、プロキシは作成されなくなるので、Beanのクラスはプロキシではなく実際のものになります。

 
    myBean is not an AOP proxy 
    Using myBean instance of class com.example.AnnotatedProxyExample returned from Spring context 
    Found MyAnnotation on secondMethod(), value=roti prata 
    object to invoke method on is not an AOP proxy 
    The object's class has the MyAnnotatedBean annotation 
    Invoking secondMethod() on object using annotated Method from the target's class 
    CALLED! secondMethod(), tx=false 
    (The target and object methods are the same here) 

    ... 

インターフェイスを実装して、動作にどのように影響するかを試すことができます。たとえば、AnnotatedProxyExampleMyInterface2を実装すると、SpringはJDKプロキシを使用しますが、thirdMethod()を呼び出すことはできませんが、代わりに/そのメソッドを宣言するMyInterface3を実装するとできます。

 
    ... 

    Found MyAnnotation on thirdMethod(), value=economy bee hoon 
    object to invoke method on is a JDK proxy 
    ***The object's class does not have the MyAnnotatedBean annotation 
    Invoking thirdMethod() on object using annotated Method from the target's class 
    *** Couldn't call thirdMethod() on instance of class com.example.$Proxy17 because object is not an instance of declaring class 
    Invoking thirdMethod() on object using a matching Method from object's class 
    CALLED! thirdMethod(), tx=true 
関連する問題