当前位置: 首页 > 知识库问答 >
问题:

带有自定义注释的Spring bean

养研
2023-03-14

我已经用自定义注释注释了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方法)。同样,当我自己使用“new”创建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之前,我的程序运行正常。

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

我试过但没有成功的方法。终极目标类(bean)

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

共有1个答案

丁学
2023-03-14

我在尝试做同样的事情时发现了这个问题,也就是说:用我自己的注释注释bean类中的一些方法,以便稍后查找并通过反射执行它们。

当bean被代理时,这会带来一些复杂性,这些复杂性也会受到代理是CGLib代理还是JDK动态代理的影响。我用Spring 4.3.9做了一些实验来观察行为上的差异。(我不确定在多大程度上是有意的,而不是代理实现的副作用,因此在未来的版本中可能会有不同的行为。我也没有试验过使用ASweJ编织的效果)。

您看不到注释的原因实际上是因为bean被代理了(如果您在其任何方法上使用@Transactional,或者如果您使用Spring基于代理的AOP特性来增强bean,就会发生这种情况)。

代理将在目标类上有重复的方法,但它不会继承或复制它们的注释——因此,当您在代理的类中检查方法时,您将看不到原始bean方法上的注释。

因此,您需要检查目标bean的(即代理对象在完成任务后包装并委托调用的实际bean实例)和aopproxutils。ultimateTargetClass()应该会给你这个。它将返回原始bean的,您可以查看它的方法并查看注释。

(也可以使用AopUtils.getTargetClass(),但代理对象本身可能是另一个代理。使用ultimateTargetClass()应该沿着代理链一直向下,而getTargetClass()只向下一级)。

您没有详细说明ultimateTargetClass()对您不起作用的方式,但是一旦您找到该方法,代理就会对调用该方法产生一些影响。

首先,因为您正在扫描目标类中的方法,所以您得到的是一个方法,它来自目标类,而不是代理类。

这是否重要取决于代理是CGLib代理还是JDK代理。

如果它是一个CGLib代理,那么代理对象扩展了目标的类,你可以使用目标类中的方法来调用它。

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、OnLead、OnDead、OnEnter等方法来标记它们。我开始得到很多这样的方法,其中很多方法的实现通常都是空的。因此,我想转向一种类似Spring控制器如何声明@RequestMapping方法或单元测试的@Test on测试方法的风格。

JDK代理也会干扰TYPE注释(您在类级别应用的注释)。通常情况下,您可以使用上下文方法getBeansInvAnnoection()来查找其类被指定注释注释的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-介绍代理

这种行为在一些地方可以被忽略。您可以在配置类上使用@EnableTransactionManagement(proxyTargetClass=false),这将使其对代理bean使用CGLib。与前命名空间XML配置一起使用的TransactionPropertyFactoryBean允许您将proxyTargetClass指定为属性。还有

一个示例程序来说明所有这些:

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

如果你去删除@Transactional,然后再次运行这个例子,你会注意到它不再创建一个代理,所以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

 类似资料:
  • 我目前正在开发一个尽可能尊重六边形架构原则的应用程序。 因此,我的“域”模块(组Id: ; 工件Id:)不依赖于任何技术框架。 我的所有服务都使用自定义注释(本身是我域的一部分)进行注释: 然而,在我的“Quarkus应用”模块(groupId:< code > acme ;artifact id:< code > app-quar kus ,我需要注入我的“域”模块中定义的服务(< code>a

  • 我看了堆栈交换和Spring示例网站上的每个示例,一切似乎都应该有效。我一定错过了一些简单的东西 我有一个自定义注释,理想情况下,如果类被注释,我希望将其应用于类的所有方法,或者应用于任何注释的方法。以下是方面、测试和代码: 注释 方面 测验 后果 链接 实际来源

  • 我有一个自定义注释如下。 一个版本的条件是, 在我的豆子注释中, 也有单版本匹配的bean,喜欢 我想验证从属性文件到可用的Beans支持版本的userInput版本。不确定,我如何获取值,迭代并与userInoutVersion进行比较。值可以是8或{6,7}作为int数组。不确定,我如何迭代该值以检查是否有任何值与输入版本匹配。 最终列表apiVersions=属性。获取(“价值”)。stre

  • 问题内容: 我正在从DropWizard 0.7.1迁移到0.8.1的过程中。这包括从Jersey 1.x迁移到2.x。在使用Jersey 1.18.1的实现中,我实现了(为简单起见,更改了所有类名)。此类将创建包含自定义注入批注的对象。包含传递并由读取的各种属性。最后,在该类中,我注册的新实例,如下所示。 我已经进行了一些研究,似乎无法完全围绕如何在Jersey 2.x中创建(或替换)这样的se

  • 问题内容: 使用基于注释的配置(等)是否可以实现相同的bean继承? http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework- reference/htmlsingle/#beans-child-bean- definitions 问题答案: java config中没有抽象bean的概念,因为Java语言已经

  • 使用基于注释的配置(等)是否可以实现相同的bean继承? http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#beans-child-bean-definitions