如何在Java中通过反射调用代理(Spring AOP)上的方法?

8

接口:

public interface Manager {
  Object read(Long id);
}

实现此接口的类:
@Transactional
Public class ManagerImpl implements Manager {
  @Override  
  public Object read(Long id) {
    //  Implementation here  
  }
}

ManagerImpl 的一个方面:

@Aspect
public class Interceptor {
  @Pointcut("execution(public * manager.impl.*.*(..))")
  public void executionAsManager() {
  }

  @Around("executionAsManager()")
  public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
    //  Do some actions
    return joinPoint.proceed();
  }
}

一个控制器:
@RestController()
public class Controller {

  @Autowired
  private Manager manager;

  @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public Object read(@PathVariable Long id) {
    return manager.read(id);
  }

  @RequestMapping(value = "reflection/{id}", method = RequestMethod.GET)
  public Object readViaReflection(@PathVariable Long id) {
    return ManagerImpl.class.getMethod("read", Long.class).invoke(manager, id);
  }
}

所以,当Spring在Controller代理中注入Manager变量时。
当直接调用方法时:
manager.read(1L)  

触发了Aspect。

然而,当我尝试像这样做时(见readViaReflection

ManagerImpl.class.getMethod("read", Long.class).invoke(manager, 1L);

收到“java.lang.reflect.InvocationTargetException”对象,它不是声明类的实例。这是合理的。

问题是:如何通过反射在由Spring创建的代理对象上调用方法(我从目标对象中提取了方法,并且我有由Spring创建的代理实例)。

无法在目标上执行调用,因为切面将不会调用。


为什么你需要这个丑陋的装置。如果你需要调用接口中没有的方法,那么你在做错误的事情,或者你的接口是错误的。如果你真的想这样做(我强烈建议不要),你需要切换到基于类的代理而不是基于接口的代理。 - M. Deinum
4个回答

5

正如您所注意到的那样,您无法在Bean上调用ManagerImpl方法,因为该Bean实际上是由代理实现的。

对我来说,解决方案是获取代理的调用处理程序并调用该方法。

if (Proxy.isProxyClass(manager.getClass())) {
    Method readMethod = ManagerImpl.class.getMethod("read", Long.class);
    Proxy.getInvocationHandler(manager).invoke(manager, readMethod, parameter);
} else
    info.getMethod().invoke(serviceClass, parameter);

当Bean不是代理时,else部分是必需的,但是要么是裸的ManagerImpl类,要么是CGLib代理类(在您的情况下会子类化ManagerImpl)。


1

您必须从代理类中调用该方法。请尝试以下代码:

manager.getClass().getMethod("read", Long.class).invoke(manager, 1L);


在发布帖子之前尝试过了。结果:它抛出 java.lang.NoSuchMethodException:java.lang.Class.read(java.lang.Long)。 - Radon
这在我做的测试中有效。代理类应该有接口方法。奇怪的是,它似乎试图在错误的位置(java.lang.Class)查找该方法。你真的使用了 manager.getClass() 吗? - Fred Porciúncula
是的,这确实是Manager。结果:className = com.sun.proxy.$Proxy46
方法:71个项目接口:
interface local.aspect.manager.Manager
interface org.springframework.aop.SpringProxy
interface org.springframework.aop.framework.Advised Manager的方法:
“public abstract java.lang.Long manager.Manager.read(java.lang.Long)” 因此,代理没有read(java.lang.Long)方法,但具有实现read(java.lang.Long)的Manager接口,可以通过反射从接口中提取此方法。并且此方法无法在代理上调用。真实而非典型。
- Radon

0

我认为Java反射不够用,你需要使用if()切入点表达式

要实现它,你可以定义另一个布尔型参数(命名为invokeAOP),当你使用invokeAOP = true调用manager时,你的Aspect将被执行。否则,你的Aspect将被省略。


0

你可以不使用反射来实现 - 只需要进行一些类型转换:

((Manager) ((Advised)manager).getTargetSource().getTarget()).read(1L);

很酷的是,它适用于JDK和CGLIB代理。

如果必须使用反射,请使用此解决方案的一部分:

Manager managerBean = ((Manager) ((Advised)manager).getTargetSource().getTarget()); managerBean.getClass().getMethod("read", Long.class).invoke(managerBean, id)


问题在于我无法使用这种方法,因为当应用程序运行时,方法名称(例如“read”)是可用的。该方法“read”仅作为示例显示。 - Radon
这会绕过代理添加的任何附加功能,从而导致问题。 - Martin Nyolt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接