java8 преобразование типа лямбда-аргумента в объект - PullRequest
0 голосов
/ 13 января 2020

Я пытаюсь привести тип лямбда-аргумента к типу класса, однако всегда получаю исключение classcast. Вот пример использования, которого я пытаюсь достичь.

  1. В классе A есть метод public void foo(Supplier<?> msg)
  2. Я положил Apsect в метод foo () захватить аргумент (в данном случае фактическое значение msg)
  3. класс B вызывает метод foo (), используя экземпляр класса A, используя лямбда-выражение
class B{
  public void bar(){
     A a=new A()
     a.foo(()->{ new MyCustomObject()});
  }
}

во время выполнения в моем классе AOP я всегда получаю аргумент типа foo () как B $$ lambda $ 0 / 13qdwqd

Вопрос Как получить фактический тип класса Метод аргумент поставщика (В данном случае MyCustomObject ?

Код аспекта

Class MyAspect{

@Around("call(public void com.mypkg.A.foo(..))")
public Object captureLambdaType(final ProceedingJoinPoint pjp) throws Throwable {

  System.out.println("lambda called: [" + pjp.getSignature() + "] "+
      "with parameter [" + pjp.getArgs()[0] + "]");

  Supplier<MyCustomObject> obj= (Supplier<MyCustomObject> )(pjp.getArgs()[0]);
 }

}

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 14 января 2020

Почему ты делаешь простую вещь сложной? Пользователь JB Nizet уже сказал вам:

Единственный способ узнать тип возвращаемого поставщиком объекта - позвонить поставщику.

Ваш собственный код делает это очень надуманным способом (после того, как я исправил его, чтобы он компилировался, потому что он глючит), используя рефлексию. Просто используйте привязку параметра аргумента AspectJ через args() и сделайте его безопасным для типов. Также используйте @Before вместо @Around, если вы просто хотите записать возвращаемое значение Supplier и никак не повлиять на выполнение метода. Таким образом, вы можете избежать вызова ProceedingJoinPoint.proceed(), чего-то необходимого, но полностью отсутствующего в вашем примере кода «решения».

Как насчет этого маленького MCVE ?

package de.scrum_master.app;

public class MyCustomClass {}
package de.scrum_master.app;

import java.util.function.Supplier;

public class A {
  public void foo(Supplier<?> msg) {}
}
package de.scrum_master.app;

public class B {
  public void bar() {
    A a = new A();
    a.foo(() -> new MyCustomClass());
  }

  public static void main(String[] args) {
    new B().bar();
  }
}
package de.scrum_master.aspect;

import java.util.function.Supplier;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
  @Before("execution(* *(*)) && args(supplier)")
  public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
    System.out.println(thisJoinPoint + " -> " + supplier.get());
  }
}

Журнал консоли при запуске B.main(..):

execution(void de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.MyCustomClass@66a29884

Это то же, что пытается сделать ваш аспект, только более аккуратно. Я думаю, что он определенно более читабелен.

Предостережение: Пожалуйста, дважды подумайте, прежде чем звонить get() поставщику, если у поставщика есть побочный эффект или его дорого рассчитать. Я знаю, что чистые функции (т.е. код, реализующий функциональные интерфейсы на Java говорят) никогда не должны иметь побочных эффектов, но если они написаны в плохом стиле, они легко могут. Так что будьте осторожны.


Обновление: Говоря об оговорке с побочным эффектом, позвольте мне показать вам кое-что. Просто немного расширьте код приложения, чтобы реально оценить поставщика и (опционально) вернуть его результат:

package de.scrum_master.app;

import java.util.function.Supplier;

public class A {
  public Object foo(Supplier<?> msg) {
    return msg.get();
  }
}

А теперь также давайте расширим аспект, чтобы фактически запускать ведение журнала всякий раз, когда get() поставщика метод называется:

package de.scrum_master.aspect;

import java.util.function.Supplier;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
  @Before("execution(* *(*)) && args(supplier)")
  public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
    System.out.println(thisJoinPoint + " -> " + supplier.get());
  }

  @AfterReturning(pointcut = "call(public * java.util.function.Supplier+.get())", returning = "result")
  public void supplierEvaluated(JoinPoint thisJoinPoint, Object result) throws Exception {
    System.out.println(thisJoinPoint + " -> " + result);
  }
}

Теперь журнал консоли будет:

call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@66a29884
execution(Object de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.MyCustomClass@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b

Вы можете видеть, как Supplier.get() вызывается дважды, возвращая два разных объекта MyCustomClass, а именно MyCustomClass@66a29884 и MyCustomClass@4769b07b? Это связано с тем, что и приложение, и первая консультация по аспекту звонят get(). Последний на самом деле не регистрирует тот же объект, что и объект, созданный приложением, поэтому даже без дополнительных побочных эффектов вы регистрируете неправильную вещь и выполняете метод поставщика дважды, а не один раз.

Так что давайте очистим это больше не вызывая get() из первого метода извещения:

  @Before("execution(* *(*)) && args(supplier)")
  public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
    System.out.println(thisJoinPoint + " -> " + supplier);  // no 'get()' call anymore
  }

Теперь журнал становится чистым:

execution(Object de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.B$$Lambda$1/1349393271@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b

Другое преимущество состоит в том, что теперь результат get() получает регистрируется всякий раз, когда метод действительно вызывается (может быть выполнен синхронно, асинхронно, многократно или никогда), а не тогда, когда аспект выполняет его избыточно.

PS: Если вам интересно, почему я так тщательно не выполняю get() только для целей регистрации, просто представьте, что лямбда открывает соединение с базой данных, создает файл 4 ГБ, загружает видео 4K с продолжительностью 90 минут или что-то еще. Это будет сделано дважды, чтобы вы могли записать это.

0 голосов
/ 14 января 2020

Спасибо всем за ваши комментарии. Я смог решить эту проблему, вызвав метод get() для лямбды. Пример кода приведен ниже


Class MyAspect{

@Around("call(public void com.mypkg.A.foo(..))")
public Object captureLambdaType(final ProceedingJoinPoint pjp) throws Throwable {

  System.out.println("lambda called: [" + pjp.getSignature() + "] "+
      "with parameter [" + pjp.getArgs()[0] + "]");

   //Get the lambda argument
   Object arg=pjp.getArgs()[0];

   //Get the argument class type
   Class clazz=arg.getClass();

    for (Method method : clazz.getDeclaredMethods()) {
      method.setAccessible(true);
      Object obj=method.invoke(obj,null);
      if(obj instanceof MyCustomClass){
        MyCustomClass myObject= (MyCustomClass) obj;
        System.out.println("Hurray");
      }
    }

 }

}
...