6

An interface:

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

A class which implements this interface:

@Transactional
Public class ManagerImpl implements Manager {
  @Override  
  public Object read(Long id) {
    //  Implementation here  
  }
}

An aspect for 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();
  }
}

A controller:

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

So, when spring injects manager variable within controller proxy created.
When method invoked directly:

manager.read(1L)  

aspect is invoked.

However, when I try to do like this (see readViaReflection)

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

got java.lang.reflect.InvocationTargetException object is not an instance of declaring class.
Which is reasonable.

The question is: how can I invoke method via reflection on proxy-object created by spring (I have method extracted from target-object and I have instance of proxy created by spring).

Can not do invocation on target because then aspect will not invoke.

Radon
  • 61
  • 1
  • 2
  • Why would you need this ugly contraption. I you need to call methods not in the interface you are doing the wrong things or your interfaces are wrong. If you really want to do this (which I would strongly advice against) you will need to switch to class based proxies instead of interface based proxies. – M. Deinum Oct 13 '17 at 09:57

4 Answers4

3

As you have noticed, you cannot invoke the method of ManagerImpl on the bean, beacause the Bean is actually implemented by a Proxy.

For me, the solution was to get the invocation handler of the Proxy and call the method.

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

The else part is necessary when the Bean is not a Proxy, but either the bare ManagerImpl class or a CGLib proxy class (which would subclass ManagerImpl in your case).

Martin Nyolt
  • 3,706
  • 2
  • 22
  • 33
1

You must invoke the method from the proxy's class. Try this:

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

Fred Porciúncula
  • 7,466
  • 2
  • 36
  • 52
  • Tried this before creating post. Result: It throws java.lang.NoSuchMethodException: java.lang.Class.read(java.lang.Long) – Radon Jul 20 '15 at 15:38
  • This worked on a test I did. The proxy class is supposed to have the interface method. The weird thing is that it's like it's trying to look for the method in the wrong place (`java.lang.Class`). Did you really use `manager.getClass()`? – Fred Porciúncula Jul 20 '15 at 15:45
  • Yes, This is really Manager. Result: className = com.sun.proxy.$Proxy46 methods: 71 items interfaces: interface local.aspect.manager.Manager interface org.springframework.aop.SpringProxy interface org.springframework.aop.framework.Advised methods for Manager: "public abstract java.lang.Long manager.Manager.read(java.lang.Long)" So, Proxy does not have method read(java.lang.Long), but has an interface Manager which implements read(java.lang.Long) end this method can be extracted by reflection from interface. And this method cannot be invoked on proxy. Reale not tipical. – Radon Jul 20 '15 at 16:23
0

I don't think java reflection will do the trip, you need to use if() pointcut expression

To do implement it, you can define another boolean argument (named invokeAOP), when your invoke manager with invokeAOP = true, then you'll get your Aspect exectued. Otherwise your Aspect will be omitted.

Qianlong
  • 240
  • 2
  • 12
0

You can do it without using reflection - it just needs some casting:

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

The cool thing is that it works with JDK and CGLIB proxies.

If you have to use reflection just use part of this solution:

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

Jakub Kubrynski
  • 12,678
  • 4
  • 56
  • 80
  • The problem is that I can not use this approach, because method name (like "read") is available when application is running. This method read is shown as example. – Radon Jul 20 '15 at 15:57
  • This has the issue that it bypasses any additional functionality added by the proxy. – Martin Nyolt Oct 13 '17 at 08:35