Как вызвать метод на прокси (Spring AOP) путем отражения в Java?

Интерфейс:

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 вводит переменную менеджера в созданный прокси-сервер контроллера.
Когда метод вызывается напрямую:

manager.read(1L)  

вызывается аспект.

Однако, когда я пытаюсь сделать так (см. readViaReflection),

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

got Объект java.lang.reflect.InvocationTargetException не является экземпляром объявленного класса.
Что разумно.

Вопрос в том, как я могу вызвать метод через отражение на прокси-объекте, созданном Spring (у меня есть метод, извлеченный из целевого объекта, и у меня есть экземпляр proxy, созданный Spring ).

Невозможно вызвать цель, потому что тогда аспект не будет вызываться.


person Radon    schedule 20.07.2015    source источник
comment
Зачем тебе эта уродливая штуковина. Если вам нужно вызывать методы не в интерфейсе, вы делаете неправильные вещи или ваши интерфейсы неверны. Если вы действительно хотите это сделать (чего я бы настоятельно не советовал), вам нужно будет переключиться на прокси на основе классов вместо прокси на основе интерфейса.   -  person M. Deinum    schedule 13.10.2017


Ответы (4)


Как вы заметили, вы не можете вызвать метод ManagerImpl для компонента, потому что компонент фактически реализуется прокси.

Для меня решение состояло в том, чтобы получить обработчик вызова прокси и вызвать метод.

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

Часть else необходима, когда Bean не является прокси, а является либо голым классом ManagerImpl, либо прокси-классом CGLib (который в вашем случае будет подклассом ManagerImpl).

person Martin Nyolt    schedule 13.10.2017

Вы должны вызвать метод из класса прокси. Попробуй это:

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

person Fred Porciúncula    schedule 20.07.2015
comment
Пробовал это перед созданием поста. Результат: выдает java.lang.NoSuchMethodException: java.lang.Class.read(java.lang.Long) - person Radon; 20.07.2015
comment
Это сработало в тесте, который я сделал. Предполагается, что прокси-класс имеет метод интерфейса. Странно то, что он пытается искать метод не в том месте (java.lang.Class). Вы действительно использовали manager.getClass()? - person Fred Porciúncula; 20.07.2015
comment
Да, это действительно менеджер. Результат: className = com.sun.proxy.$Proxy46 методы: 71 элемент интерфейсы: интерфейс local.aspect.manager.Manager interface org.springframework.aop.SpringProxy interface org.springframework.aop.framework.Рекомендуемые методы для Manager: public abstract java.lang.Long manager.Manager.read(java.lang.Long) Итак, у Proxy нет метода read(java.lang.Long), но есть диспетчер интерфейса, который реализует read(java.lang.Long) end this метод может быть извлечен путем отражения от интерфейса. И этот метод нельзя вызывать на прокси. Реале не типичен. - person Radon; 20.07.2015

Я не думаю, что отражение Java подойдет, вам нужно использовать выражение точки if()

Чтобы реализовать это, вы можете определить другой логический аргумент (с именем invokeAOP), когда вы вызываете manager с invokeAOP = true, тогда вы получите свой Aspect. В противном случае ваш Аспект будет опущен.

person Qianlong    schedule 20.07.2015

Вы можете сделать это без использования отражения - для этого просто нужно приведение:

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

person Jakub Kubrynski    schedule 20.07.2015
comment
Проблема в том, что я не могу использовать этот подход, потому что имя метода (например, чтение) доступно во время работы приложения. Этот метод чтения показан в качестве примера. - person Radon; 20.07.2015
comment
Проблема в том, что он обходит любые дополнительные функции, добавленные прокси. - person Martin Nyolt; 13.10.2017