带有自定义注释的Spring bean

时间:2016-03-27 15:29:03

标签: spring annotations spring-bean

我已经使用自定义注释注释了一个Spring bean,但是在创建bean之后,Spring似乎删除了我的自定义注释。

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

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

我尝试了什么但没有工作 AopProxyUtils.ultimateTargetClass(豆)

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

1 个答案:

答案 0 :(得分:2)

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

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

您无法看到注释的原因确实是因为bean已被代理(如果您对其任何方法使用@Transactional,或者您使用基于Spring的代理AOP,则会发生这种情况)增强bean的功能。)

代理将在目标类上包含重复的方法,但它不会继承或复制其注释 - 因此当您检查代理中的Method时> Class你不会看到原始bean方法上的注释。

因此,您需要检查目标 bean的Class(即:代理对象包装的实际bean实例,并在执行其操作后委托调用),以及AopProxyUtils.ultimateTargetClass()应该给你这个。它将返回原始bean的Class,您可以查看其方法并查看注释。

(您也可以使用AopUtils.getTargetClass(),但代理对象本身可能是另一个代理。使用ultimateTargetClass()应该一直跟随代理链而{{1}只下降一级)。

你没有详细说明getTargetClass()"没有工作的方式"对你而言,代理对于在找到方法后调用该方法有一些影响。

首先,既然您正在扫描目标类中的方法,那么您所拥有的是来自目标类的ultimateTargetClass(),而不是< strong>代理类。

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

如果它是CGLib代理,那么代理对象扩展了目标的类,你无论如何只能使用目标类中的Method来在代理对象上调用它。即:

Method

但是如果它是一个不起作用的JDK代理。你会得到一个异常&#34;对象不是声明类的实例&#34;因为JDK代理不扩展bean的类,所以它只实现了它的所有接口。为了解决这个问题,你需要在代理上找到方法的邪恶双胞胎并用它调用它:

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

这将起作用 - 但前提是您尝试调用的方法是由类实现的其中一个接口声明的,因为JDK仅代理在bean类实现的接口中声明的代理方法。

你可以为你注释的所有方法创建接口并声明bean来实现它们,但这就引出了一个问题:当我们可以使用接口调用它们时,为什么我们试图通过反射来查找和调用它们 - 而且回到我们为什么首先使用注释而不是这些方法的接口。

在我自己的案例中(导致我研究这个问题的那个案例),我的目标是摆脱某些生命周期&#39;在接口中声明的某些类型的bean中的方法,而是用注释标记它们 - 考虑像onLoad,onHidden,onDisplayed,onRemove等方法。我开始得到很多这样的方法,并且对于其中许多方法,实现通常都是空的。所以我想转向一种风格,就像Spring控制器如何声明他们的@RequestMapping方法或单元测试而不是测试方法的@Test一样。

JDK代理也会干扰TYPE注释(您在类级别应用的注释)。通常,您可以使用上下文方法Method methodOnTheProxy = theProxyObject.getClass().getMethod(theAnnotatedMethod.getName(), theAnnotatedMethod.getParameterTypes()); methodOnTheProxy.invoke(theProxyObject, args...); 来查找其类使用指定的注释进行批注的任何bean,但是,如果bean使用JDK代理进行代理,则将由这个方法,如果它是用CGLib代理的,它仍然会被找到。 (有趣的是,在代理的getBeansWithAnnotation()上调用isAnnotationPresent()的两种情况都返回false。当bean具有JDK代理时,您还会看到与Class相同的问题。

它表明我们可能更喜欢Spring将CGLib用于这些bean。

默认情况下,如果bean实现任何声明方法的接口,Spring将使用JDK代理(即使你将@Transactional放在一个实际上不在其中一个接口的方法中!)。如果没有实现的接口或者没有声明的接口声明方法,那么它将使用CGLib。请参阅:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies

可以在一些地方覆盖此行为。您可以在配置类上使用getBeansOfType(),这将使CGLib用于代理bean。与命名空前XML配置一起使用的古代@EnableTransactionManagement(proxyTargetClass=false)允许您将proxyTargetClass指定为属性。 XML命名空间配置中还有TransactionPropertyFactoryBean。另见:Use of proxies in Spring AOP

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

<aop:config proxy-target-class="true">

声明事务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); } } 而不实现接口,它提供以下输出:

    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()移除@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)

    ...

您可以尝试尝试实现接口,以了解它如何影响行为。例如,如果您使thirdMethod()实现AnnotatedProxyExample,那么Spring将使用JDK代理,但无法调用MyInterface2,而如果您改为/ thirdMethod()声明该方法,然后就可以。

    ...

    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
相关问题