Spring 个带有自定义注释的 bean
Spring bean with custom annotations
我已经用自定义注释注释了一个 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 被传递到 class,我的自定义注释在这里得到验证,但我的验证器找不到任何注释。 (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 上保留自定义注释?在将 Class 更改为 Bean 之前,我的程序运行正常。
我为什么要这样做?
我的 class 中的方法是通过反射调用的,但在这些方法中,我想使用一个自动装配的 DOA,它要求 class 是一个 bean。 :)
我试过但没用
AopProxyUtils.ultimateTargetClass(豆子)
在这里找到答案
似乎没有解决方法是不可能的。答案是 3 岁,所以也许现在有另一种方法?
我在尝试做很多相同的事情时发现了这个问题,即:用我自己的注释注释 bean class 中的一些方法,目的是稍后通过反射查找并执行它们。
当 bean 被代理时,这会带来一些复杂性,这些复杂性也会受到代理是 CGLib 代理还是 JDK 动态代理的影响。我使用 Spring 4.3.9 做了一些实验来观察行为上的差异。 (我不确定有多少是故意的还是代理实现的副作用,因此在未来的版本中可能会采取不同的行动。我也没有试验过使用 AspectJ 编织的效果)。
您看不到注释的原因确实是因为 bean 被代理了(如果您在其任何方法上使用 @Transactional
,或者如果您使用 Spring 的代理,就会发生这种情况基于 AOP 功能来增强 bean)。
代理将在目标 class 上有重复的方法,但它不会继承或复制它们的注释 - 所以当您检查 代理中的 Method
时 Class
您将看不到原始 bean 方法上的注释。
因此,您需要检查 target bean 的 Class
(即:代理对象包装并在执行后委托调用的实际 bean 实例它的东西),并且 AopProxyUtils.ultimateTargetClass()
应该给你这个。它将 return 您原始 bean 的 Class
,您可以浏览它的方法并查看注释。
(您也可以使用 AopUtils.getTargetClass()
,但代理对象本身可能是另一个代理。使用 ultimateTargetClass()
应该一直跟随代理链,而 getTargetClass()
只下降一层)。
您没有为您详细说明 ultimateTargetClass()
"didn't work" 的方式,但是一旦您找到代理,代理对调用该方法有一些影响。
首先,由于您正在扫描 目标的 class 的方法,因此您拥有的是来自目标 [=102= 的 Method
] 而不是 代理的 class。
这是否重要取决于代理是 CGLib 代理还是 JDK 代理。
如果它是一个 CGLib 代理,那么代理对象会扩展目标的 class,并且您无论如何都可以使用目标 class 中的 Method
来调用它代理对象。即:
theAnnotatedMethod.invoke(theProxyObject, args...);
但是如果是JDK代理就不行了。您将得到一个异常 "object is not an instance of declaring class",因为 JDK 代理没有扩展 bean 的 class,它只是实现了它的所有接口。为了解决这个问题,您需要在代理上找到该方法的邪恶双胞胎并用它调用它:
Method methodOnTheProxy = theProxyObject.getClass().getMethod(theAnnotatedMethod.getName(),
theAnnotatedMethod.getParameterTypes());
methodOnTheProxy.invoke(theProxyObject, args...);
这将起作用——但前提是您尝试调用的方法是由 class 实现的接口之一声明的,因为 JDK 仅代理在一个声明中声明的代理方法bean class.
实现的接口
您可以为您注释的所有方法创建接口并声明用于实现它们的 bean,但这引出了一个问题,即当我们只能使用接口调用它们时,我们为什么要尝试通过反射来查找和调用它们 -这又回到了为什么我们首先使用注释而不是这些方法的接口。
在我自己的情况下(导致我研究这个问题的那个),我的目标是避免在接口中声明的某些类型的 bean 中使用某些 'lifecycle' 方法,而是标记它们带有注释 - 例如考虑 onLoad、onHidden、onDisplayed、onRemove 等方法。我开始得到很多这样的方法,而其中许多方法的实现通常都是空的。所以我想按照 Spring 控制器如何声明他们的 @RequestMapping 方法或单元测试在测试方法上使用 @Test 的方式转向一种风格。
JDK 代理也会干扰 TYPE 注释(您在 class 级别应用的注释)。通常,您可以使用上下文方法 getBeansWithAnnotation()
来查找 class 被指定注释注释的任何 beans,但是,如果 bean 使用 JDK 代理代理,它将 not 不会被这种方法找到,然而,如果它被 CGLib 代理,它仍然会被找到。 (有趣的是,在这两种情况下,在代理 return 的 Class
上调用 isAnnotationPresent()
都是错误的)。当 bean 具有 JDK 代理时,您也会看到 getBeansOfType()
的相同问题。
这表明我们可能更愿意 Spring 对这些 bean 使用 CGLib。
默认情况下,如果 bean 实现了任何声明方法的接口,Spring 将使用 JDK 代理(即使您将 @Transactional 放在实际上不在其中一个方法中的方法上界面!)。如果没有已实现的接口或已声明接口的 none 声明了一个方法,那么它将使用 CGLib。参见:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies
这个行为可以在几个地方被覆盖。您可以在配置 class 上使用 @EnableTransactionManagement(proxyTargetClass=false)
,这将使它使用 CGLib 作为代理 bean。与前命名空间 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 is an AOP proxy
Using myBean instance of class com.example.AnnotatedProxyExample$$EnhancerBySpringCGLIB$7d5296 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 的 class 是真实的而不是代理:
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()
,而如果您 instead/also 实现声明该方法的 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
我已经用自定义注释注释了一个 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 被传递到 class,我的自定义注释在这里得到验证,但我的验证器找不到任何注释。 (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 上保留自定义注释?在将 Class 更改为 Bean 之前,我的程序运行正常。
我为什么要这样做?
我的 class 中的方法是通过反射调用的,但在这些方法中,我想使用一个自动装配的 DOA,它要求 class 是一个 bean。 :)
我试过但没用
AopProxyUtils.ultimateTargetClass(豆子)
在这里找到答案
似乎没有解决方法是不可能的。答案是 3 岁,所以也许现在有另一种方法?
我在尝试做很多相同的事情时发现了这个问题,即:用我自己的注释注释 bean class 中的一些方法,目的是稍后通过反射查找并执行它们。
当 bean 被代理时,这会带来一些复杂性,这些复杂性也会受到代理是 CGLib 代理还是 JDK 动态代理的影响。我使用 Spring 4.3.9 做了一些实验来观察行为上的差异。 (我不确定有多少是故意的还是代理实现的副作用,因此在未来的版本中可能会采取不同的行动。我也没有试验过使用 AspectJ 编织的效果)。
您看不到注释的原因确实是因为 bean 被代理了(如果您在其任何方法上使用 @Transactional
,或者如果您使用 Spring 的代理,就会发生这种情况基于 AOP 功能来增强 bean)。
代理将在目标 class 上有重复的方法,但它不会继承或复制它们的注释 - 所以当您检查 代理中的 Method
时 Class
您将看不到原始 bean 方法上的注释。
因此,您需要检查 target bean 的 Class
(即:代理对象包装并在执行后委托调用的实际 bean 实例它的东西),并且 AopProxyUtils.ultimateTargetClass()
应该给你这个。它将 return 您原始 bean 的 Class
,您可以浏览它的方法并查看注释。
(您也可以使用 AopUtils.getTargetClass()
,但代理对象本身可能是另一个代理。使用 ultimateTargetClass()
应该一直跟随代理链,而 getTargetClass()
只下降一层)。
您没有为您详细说明 ultimateTargetClass()
"didn't work" 的方式,但是一旦您找到代理,代理对调用该方法有一些影响。
首先,由于您正在扫描 目标的 class 的方法,因此您拥有的是来自目标 [=102= 的 Method
] 而不是 代理的 class。
这是否重要取决于代理是 CGLib 代理还是 JDK 代理。
如果它是一个 CGLib 代理,那么代理对象会扩展目标的 class,并且您无论如何都可以使用目标 class 中的 Method
来调用它代理对象。即:
theAnnotatedMethod.invoke(theProxyObject, args...);
但是如果是JDK代理就不行了。您将得到一个异常 "object is not an instance of declaring class",因为 JDK 代理没有扩展 bean 的 class,它只是实现了它的所有接口。为了解决这个问题,您需要在代理上找到该方法的邪恶双胞胎并用它调用它:
Method methodOnTheProxy = theProxyObject.getClass().getMethod(theAnnotatedMethod.getName(),
theAnnotatedMethod.getParameterTypes());
methodOnTheProxy.invoke(theProxyObject, args...);
这将起作用——但前提是您尝试调用的方法是由 class 实现的接口之一声明的,因为 JDK 仅代理在一个声明中声明的代理方法bean class.
实现的接口您可以为您注释的所有方法创建接口并声明用于实现它们的 bean,但这引出了一个问题,即当我们只能使用接口调用它们时,我们为什么要尝试通过反射来查找和调用它们 -这又回到了为什么我们首先使用注释而不是这些方法的接口。
在我自己的情况下(导致我研究这个问题的那个),我的目标是避免在接口中声明的某些类型的 bean 中使用某些 'lifecycle' 方法,而是标记它们带有注释 - 例如考虑 onLoad、onHidden、onDisplayed、onRemove 等方法。我开始得到很多这样的方法,而其中许多方法的实现通常都是空的。所以我想按照 Spring 控制器如何声明他们的 @RequestMapping 方法或单元测试在测试方法上使用 @Test 的方式转向一种风格。
JDK 代理也会干扰 TYPE 注释(您在 class 级别应用的注释)。通常,您可以使用上下文方法 getBeansWithAnnotation()
来查找 class 被指定注释注释的任何 beans,但是,如果 bean 使用 JDK 代理代理,它将 not 不会被这种方法找到,然而,如果它被 CGLib 代理,它仍然会被找到。 (有趣的是,在这两种情况下,在代理 return 的 Class
上调用 isAnnotationPresent()
都是错误的)。当 bean 具有 JDK 代理时,您也会看到 getBeansOfType()
的相同问题。
这表明我们可能更愿意 Spring 对这些 bean 使用 CGLib。
默认情况下,如果 bean 实现了任何声明方法的接口,Spring 将使用 JDK 代理(即使您将 @Transactional 放在实际上不在其中一个方法中的方法上界面!)。如果没有已实现的接口或已声明接口的 none 声明了一个方法,那么它将使用 CGLib。参见:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction-proxies
这个行为可以在几个地方被覆盖。您可以在配置 class 上使用 @EnableTransactionManagement(proxyTargetClass=false)
,这将使它使用 CGLib 作为代理 bean。与前命名空间 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 is an AOP proxy Using myBean instance of class com.example.AnnotatedProxyExample$$EnhancerBySpringCGLIB$7d5296 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 的 class 是真实的而不是代理:
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()
,而如果您 instead/also 实现声明该方法的 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