我在尝试做很多相同的事情时发现了这个问题,即:用我自己的注释对 bean 类中的一些方法进行注释,以便以后通过反射查找并执行它们。
当 bean 被代理时,这会引入一些复杂性,并且这些复杂性还会受到代理是 CGLib 代理还是 JDK 动态代理的影响。我使用 Spring 4.3.9 做了一些实验来观察行为的差异。 (我不确定多少是有意的还是代理实现的副作用,因此在未来的版本中可能会采取不同的行动。我也没有尝试过使用 AspectJ 编织的效果)。
您看不到注释的原因确实是因为 bean 被代理了(如果您在其任何方法上使用 @Transactional,或者如果您使用 Spring 的基于代理的 AOP 功能来增强 bean,则会发生这种情况)。
代理将在目标类上具有重复的方法,但它不会继承或复制它们的注释 - 因此,当您检查 代理 Class 中的 Method 时,您不会查看原始 bean 方法上的注释。
因此,您需要检查 target bean 的 Class(即:代理对象包装并委托调用的实际 bean 实例)和 @987654327 @应该给你这个。它将返回原始 bean 的 Class,您可以通过它的方法查看注释。
(您也可以使用AopUtils.getTargetClass(),但代理对象本身可能是另一个代理。使用ultimateTargetClass() 应该一直跟随代理链,而getTargetClass() 只下降一级)。
您没有详细说明 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() 出现相同的问题。
这表明我们可能更喜欢 Spring 为这些 bean 使用 CGLib。
默认情况下,如果 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),这将使其使用 CGLib 代理 bean。与 pre-namespace XML 配置一起使用的古老 TransactionPropertyFactoryBean 允许您将 proxyTargetClass 指定为属性。 XML 命名空间配置中还有<aop:config proxy-target-class="true">。另见:Use of proxies in Spring AOP
一个示例程序来说明所有这些:
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);
}
}
声明一个事务性thirdMethod() 并且不实现接口它会给出以下输出:
myBean 是一个 AOP 代理
使用从 Spring 上下文返回的 com.example.AnnotatedProxyExample$$EnhancerBySpringCGLIB$$367d5296 类的 myBean 实例
***没有找到任何使用 MyAnnotation 的方法
使用 AopProxyUtils.ultimateTargetClass(MyBean) 返回的 com.example.AnnotatedProxyExample 类的实例:
在 secondMethod() 上找到 MyAnnotation,value=roti prata
调用方法的对象是一个 CGLib 代理
***对象的类没有 MyAnnotatedBean 注解
使用目标类中的注释方法在对象上调用 secondMethod()
呼叫! secondMethod(), tx=false
使用对象类中的匹配方法在对象上调用 secondMethod()
呼叫! secondMethod(), tx=false
在 thirdMethod() 上找到 MyAnnotation,value=economy bee hoon
调用方法的对象是一个 CGLib 代理
***对象的类没有 MyAnnotatedBean 注解
使用目标类中的注释方法在对象上调用 thirdMethod()
呼叫!第三方法(),tx =真
使用对象类中的匹配方法在对象上调用 thirdMethod()
呼叫!第三方法(),tx =真
如果您从thirdMethod() 中删除@Transactional,然后再次运行该示例,您会注意到它不再创建代理,因此bean 的类是真实的,而不是代理:
myBean 不是 AOP 代理
使用从 Spring 上下文返回的 com.example.AnnotatedProxyExample 类的 myBean 实例
在 secondMethod() 上找到 MyAnnotation,value=roti prata
调用方法的对象不是 AOP 代理
对象的类具有 MyAnnotatedBean 注释
使用目标类中的注释方法在对象上调用 secondMethod()
呼叫! secondMethod(), tx=false
(这里的target和object方法是一样的)
...
您可以尝试实现接口,看看它如何影响行为。例如,如果您让AnnotatedProxyExample 实现MyInterface2,那么Spring 将使用JDK 代理,但无法调用thirdMethod(),而如果您改为/还实现声明该方法的MyInterface3,那么您可以.
...
在 thirdMethod() 上找到 MyAnnotation,value=economy bee hoon
调用方法的对象是 JDK 代理
***对象的类没有 MyAnnotatedBean 注解
使用目标类中的注释方法在对象上调用 thirdMethod()
*** 无法在类 com.example.$Proxy17 的实例上调用 thirdMethod(),因为对象不是声明类的实例
使用对象类中的匹配方法在对象上调用 thirdMethod()
呼叫!第三方法(),tx =真