これは、同じことをやろうとしている間にこの問題を発見しました。つまり、Beanクラスのいくつかのメソッドに、自分の注釈を付けて後でハントし、リフレクションでそれらを実行するという意図で注釈を付けます。
豆がプロキシされると、いくつかの複雑さが生じ、これらの問題は、プロキシがCGLibプロキシかJDK動的プロキシのどちらであるかによっても影響を受けます。私はSpring 4.3.9を使用していくつかの実験を行い、動作の違いを観察しました。 (私はプロキシー実装の副作用にどれだけの意図があるのか不明であるため、将来のバージョンでは異なる動作をする可能性があります。また、AspectJ製織の使用効果も試していません)。
その理由は、Beanがプロキシされているためです(いずれのメソッドでも@Transactional
を使用した場合や、Beanを拡張するためにSpringのプロキシベースのAOP機能を使用した場合)。
プロキシがターゲットクラスにメソッドの重複を持っていますが、それは彼らの注釈を継承するか、コピーしません - あなたはプロキシのClass
でMethod
を調べるときに、あなたは、元にあった注釈は表示されません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)
...
インターフェイスを実装して、動作にどのように影響するかを試すことができます。たとえば、AnnotatedProxyExample
にMyInterface2
を実装すると、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
注釈コードを表示しますか? https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicy.html –
私の注釈は100%正確です。すべての機能が動作しないためですBeanを使用して(保持は実行時に設定されます)。古いスタックポストから回答を追加しました。私の問題のようなものは、解決策がないと思われます。/ –
[ask]をお読みください。私はあなたのコードを頼んだので、それはSOの方針です。自分のコードを表示せずに「なぜ私のコードは機能しないのですか」と尋ねることはできません。私は今あなたの質問を閉じるために投票しました。 –