简体   繁体   English

带有自定义注释的Spring bean

[英]Spring bean with custom annotations

I have annotated a Spring bean with custom annotations, but seems that Spring removes my custom annotations after the bean is created. 我已经用自定义注释对Spring bean进行了注释,但是似乎Spring在创建bean之后删除了我的自定义注释。

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

Foo.findAndDoStuffWithAnnotatedThings(bean);

The second step doesn't work, my custom annotations are lost. 第二步不起作用,我的自定义注释丢失了。 (Propably due proxy stuff) (可能是代理人的东西)

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

Example of one of my custom annotations 我的自定义注释之一的示例

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

}

Where things to wrong in findAndDoStuffWithAnnotatedThings findAndDoStuffWithAnnotatedThings中哪里出错了
Bean gets passed to a class where my custom annotations are verified, but my verifier can't find any annotations. Bean被传递到验证我的自定义批注的类,但是我的验证程序找不到任何批注。 (Util uses isAnnotationPresent method). 实用程序使用isAnnotationPresent方法)。 Again, when I would create my bean myself using 'new' there is no problem. 同样,当我自己使用“ 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()));
        }
    }
    ...

Is there a way to keep the custom annotations on a bean? 有没有办法将自定义注释保留在bean上? My program was working correctly before changing my Class to a Bean. 在将我的类更改为Bean之前,我的程序运行正常。

Why do I want to do this? 我为什么要这样做?
Methods inside my class are invoked via reflection, but in the methods I'd like to use an Autowired DOA which requires the class to be a bean. 我的类中的方法是通过反射调用的,但是在这些方法中,我想使用要求类为bean的自动装配DOA。 :) :)

What I tried but didn't work 我试过但没用的
AopProxyUtils.ultimateTargetClass(bean) AopProxyUtils.ultimateTargetClass(豆)

Found an answer here 在这里找到答案
https://stackoverflow.com/a/14248490/3187643 https://stackoverflow.com/a/14248490/3187643
Seems that it is not possible without a workaround. 似乎没有解决方法是不可能的。 Answer is 3 years old so maybe there is another way now? 答案是3岁,所以也许现在还有另一种方法?

I found this question while trying to do much the same thing, that is: annotate some methods in a bean class with my own annotations with the intent to later hunt down and execute them via reflection. 我在尝试做相同的事情时发现了这个问题,即:用我自己的注释对bean类中的某些方法进行注释,以期稍后查找并通过反射执行它们。

When the beans are proxied this introduces some complications, and these complications are also affected by whether the proxy is a CGLib proxy or a JDK Dynamic proxy. 当代理这些bean时,这会带来一些复杂性,并且代理是CGLib代理还是JDK Dynamic代理也会影响这些复杂性。 I have done some experiments using Spring 4.3.9 to observe the differences in behaviour. 我已经使用Spring 4.3.9进行了一些实验,以观察行为上的差异。 (I am unsure how much is intentional vs being a side-effect of the proxying implementation and thus liable to act differently in future versions. I also have not experimented with the effects of using AspectJ weaving). (我不确定故意是多少,而不是代理实现的副作用,因此在将来的版本中可能会有不同的作用。我也没有尝试过使用AspectJ编织的效果)。

The reason you couldn't see the annotations is indeed because the bean was proxied (which happens if you use @Transactional on any of its methods, or if you use Spring's proxy based AOP features to enhance the bean). 之所以看不到注释,确实是因为该bean被代理了(如果您在其任何方法上使用@Transactional ,或者使用基于Spring的基于代理的AOP功能来增强bean,就会发生这种情况)。

The proxy will have duplicates of methods on the target class but it won't inherit or copy their annotations - so when you inspect a Method in the proxy's Class you won't see the annotations that were on the original bean's method. 代理将在目标类上具有方法的重复项,但不会继承或复制其注释-因此,当您在代理的 Class检查Method ,将看不到原始bean的方法上的注释。

Therefore, you instead need to inspect the Class of the target bean (ie: the actual bean instance which the proxy object wraps and delegates calls to after doing its thing), and AopProxyUtils.ultimateTargetClass() is supposed give you this. 因此,您需要检查目标 Bean的Class (即,代理对象包装后将其实际包装并委托给调用的实际Bean实例),并且AopProxyUtils.ultimateTargetClass()应该可以为您提供此功能。 It will return the Class of your original bean and you can go through its methods and see the annotations. 它将返回原始bean的Class ,您可以浏览其方法并查看注释。

(You could also use AopUtils.getTargetClass() , but it's possible the proxied object is itself another proxy. Using ultimateTargetClass() is supposed to follow the proxy chain all the way down whereas getTargetClass() only goes down one level). (你也可以使用AopUtils.getTargetClass()但它可能被代理对象本身是另一个代理。使用ultimateTargetClass()应该遵循代理环比下降,而一路getTargetClass()只下降一个级别)。

You didn't detail in which way ultimateTargetClass() "didn't work" for you, but the proxying has some implications for invoking the method once you have found it. 您没有详细说明ultimateTargetClass()对您“无效”的方式,但是一旦找到该方法,代理就会对调用该方法产生一些影响。

Firstly, since you were scanning the target's class for methods, what you have is a Method that came from the target's class and not the proxy's class. 首先,由于您正在扫描目标类的方法,因此您拥有的是来自目标类而不是代理类的Method

Whether this matters depends on whether the proxy is a CGLib proxy or a JDK proxy. 这是否重要取决于代理是CGLib代理还是JDK代理。

If it is a CGLib proxy, then the proxy object extends the target's class, and you can anyhow just use the Method from the target class to call it on the proxy object. 如果它是CGLib代理,则代理对象将扩展目标的类,并且您可以使用目标类中的Method来在代理对象上对其进行调用。 ie: 即:

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

But if it is a JDK proxy that doesn't work. 但是,如果它是JDK代理不起作用。 You will get an exception "object is not an instance of declaring class" because the JDK proxy doesn't extend the bean's class, it just implements all its interfaces. 您将得到一个异常“对象不是声明类的实例”,因为JDK代理不会扩展bean的类,它只是实现其所有接口。 To get around this you then need to find the method's evil twin on the proxy and invoke it with that: 为了解决这个问题,您需要在代理上找到方法的邪恶双胞胎,并使用它来调用它:

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

This will work - but only if the method you are trying to invoke is declared by one of the interfaces the class implements, because the JDK proxies only proxy methods that were declared in an interface implemented by the bean class. 这将起作用-但仅当您尝试调用的方法由该类实现的接口之一声明时,因为JDK只能代理在bean类实现的接口中声明的代理方法。

You could create interfaces for all the methods you annotate and declare the bean to implement them, but this begs the question of why are we trying to find and invoke them by reflection when we can just call them using the interface - and that gets back to why we are using annotations instead of an interface for these methods in the first place. 您可以为所有注释的方法创建接口,并声明Bean来实现它们,但这引出了一个问题,为什么我们只能通过使用接口调用它们时才尝试通过反射来查找和调用它们-然后返回为什么我们首先使用注解而不是这些方法的接口。

In my own case (the one that led me to researching this question), I was aiming to move away from having certain 'lifecycle' methods in certain types of my beans declared in an interface and instead mark them with an annotation - consider methods like onLoad, onHidden, onDisplayed, onRemove for example. 就我自己的情况(导致我研究此问​​题的情况)而言,我的目标是摆脱在接口中声明的某些类型的bean中使用某些“生命周期”方法,而改为使用注释标记它们-考虑使用类似的方法例如onLoad,onHidden,onDisplayed,onRemove。 I am starting to get a lot of such methods and the implementations are often just empty for many of them. 我开始得到很多这样的方法,并且对于许多实现而言,实现常常是空的。 So I wanted to move to a style along the lines of how Spring controllers declare their @RequestMapping methods or unit tests have @Test on test methods instead. 因此,我想按照Spring控制器声明其@RequestMapping方法或单元测试在测试方法上使用@Test的方式来更改样式。

JDK proxies also interfere with TYPE annotations (the ones you apply at the class level). JDK代理还会干扰TYPE注释(您在类级别应用的注释)。 Normally, you can use the context method getBeansWithAnnotation() to find any beans whose class is annotated with the specified annotation, but, if the bean is proxied with a JDK proxy it will not be found by this method, whereas, if it is proxied with CGLib it will still be found. 通常情况下,你可以使用上下文方法getBeansWithAnnotation()找到任何这些Bean的类被注解为指定的注释,但是,如果Bean与JDK代理代理就不会用这种方法发现的,然而,如果代理使用CGLib仍将找到它。 (Interestingly in both cases calling isAnnotationPresent() on the Class of the proxy returns false). (有趣的是,在两种情况下,对代理的Class调用isAnnotationPresent()返回false)。 You will also see the same problem with getBeansOfType() when the beans have a JDK proxy. 当Bean具有JDK代理时,您还将看到与getBeansOfType()相同的问题。

It suggests we might prefer Spring to use CGLib for these beans. 这表明我们可能更喜欢Spring对这些bean使用CGLib。

By default, Spring will use a JDK Proxy if the bean implements any interfaces that declare methods (even if you put @Transactional on a method that isn't actually in one of those interfaces!). 默认情况下,如果bean实现了任何声明方法的接口(即使您将@Transactional放在实际上不在那些接口之一中的方法上),Spring也会使用JDK代理。 If there are no implemented interfaces or none of the declared interfaces declare a method then it will use CGLib. 如果没有实现的接口或没有声明的接口声明任何方法,则它将使用CGLib。 See: https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies 参见: https : //docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies

This behaviour can be overridden in a few places. 在某些地方可以忽略此行为。 You can use @EnableTransactionManagement(proxyTargetClass=false) on a configuration class which will make it use CGLib for proxied beans. 您可以在配置类上使用@EnableTransactionManagement(proxyTargetClass=false) ,这将使它对代理bean使用CGLib。 The ancient TransactionPropertyFactoryBean used with the pre-namespace XML configuration lets you specify proxyTargetClass as a property. 与预命名空间XML配置一起使用的古老TransactionPropertyFactoryBean使您可以将proxyTargetClass指定为属性。 There is also <aop:config proxy-target-class="true"> in XML namespace configuration. XML名称空间配置中还包含<aop:config proxy-target-class="true"> See also: Use of proxies in Spring AOP 另请参阅: 在Spring AOP中使用代理

An example program to illustrate all of this: 一个示例程序来说明所有这些:

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);
  }
}

Declaring a transactional thirdMethod() and not implementing an interface it gives the following output: 声明一个事务性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

If you go and remove the @Transactional from thirdMethod() and then run the example again, you will notice that it no longer creates a proxy so the bean's class is the real one and not a proxy: 如果您从thirdMethod()删除@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)

    ...

You can try experimenting with implementing the interfaces to see how it affects the behaviour. 您可以尝试实施接口,以查看其如何影响行为。 For example, if you make AnnotatedProxyExample implement MyInterface2 then Spring will use a JDK proxy but won't be able to call thirdMethod() , whereas if you instead/also implement MyInterface3 which declares that method, then you can. 例如,如果您使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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM