【问题标题】:Spring bean with custom annotations带有自定义注释的 Spring bean
【发布时间】:2023-04-01 23:34:01
【问题描述】:

我已经使用自定义注释对 Spring bean 进行了注释,但似乎 Spring 在创建 bean 后删除了我的自定义注释。

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

Foo.findAndDoStuffWithAnnotatedThings(bean);

第二步不行,我的自定义注解丢失了。 (可能是由于代理的东西)

我的豆

@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
    }  

我的自定义注释之一示例

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

}

findAndDoStuffWithAnnotatedThings
Bean 被传递给一个类,在该类中验证了我的自定义注释,但我的验证器找不到任何注释。 (Util 使用 isAnnotationPresent 方法)。同样,当我自己使用“新”创建我的 bean 时,没有问题。

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 之前,我的程序运行正常。

我为什么要这样做?
我的类中的方法是通过反射调用的,但在这些方法中,我想使用 Autowired DOA,它要求类是一个 bean。 :)

我尝试了什么但没有奏效
AopProxyUtils.ultimateTargetClass(bean)

在这里找到答案
https://stackoverflow.com/a/14248490/3187643
似乎没有解决方法是不可能的。答案是 3 岁,所以现在也许还有另一种方法?

【问题讨论】:

  • 向我们展示您的注释代码?我怀疑您错过了保留政策:docs.oracle.com/javase/7/docs/api/java/lang/annotation/…
  • 我的注释是 100% 正确的,因为在不使用 bean 的情况下一切正常(保留设置为运行时)。我已经从较旧的堆栈帖子中添加了答案。如果没有肮脏的解决方法,我的问题似乎无法解决:/
  • 请阅读How to Ask。我要求您提供代码,因为那是 SO 政策。如果不向我们展示您的代码,您就不能问“为什么我的代码不起作用”。我已经投票结束你的问题了。
  • 无论如何它都会被关闭,作为您找到的答案的副本
  • 我想我也做过类似的事情。你能显示 Foo.findAndDoStuffWithAnnotatedThings(bean); 吗?

标签: spring annotations spring-bean


【解决方案1】:

我在尝试做很多相同的事情时发现了这个问题,即:用我自己的注释对 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 =真

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多