Related
When using Spring Cglib proxy, we need to implement a MethodInterceptor callback, I have some problems about this callback. To make it clearer, let's use a simple example.
Here is my target class MyPlay.java
public class MyPlay {
public void play() {
System.out.println("MyPlay test...");
}
}
And I created a callback:
public class CglibMethodInterceptor implements MethodInterceptor {
private Object target;
public CglibMethodInterceptor(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
#Override
public Object intercept(
Object o,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB prep work...");
Object obj = method.invoke(target, objects);
System.out.println("CGLIB post work...");
return obj;
}
}
In my Main class:
MyPlay myPlay = new MyPlay();
cglibMethodInterceptor = new CglibMethodInterceptor(myPlay);
Play myPlayProxy = (Play) cglibMethodInterceptor.getProxy();
myPlay.play();
myPlayProxy.play();
I'm confused about the meaning of the parameters of the intercept method:
#Override
public Object intercept(
Object o,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
}
So, I set up a breakpoint to at the myPlayProxy.play() and step into it. I took a screenshot:
Problem: What are the method and methodProxy parameters? What is the difference between them? When I use the methodProxy to invoke, it also works, which confuses me.
Object obj = method.invoke(target, objects);
// This also works, why?
// Object obj = methodProxy.invoke(target, objects);
The Javadoc says:
The original method may either be invoked by normal reflection using the Method object, or by using the MethodProxy (faster).
I don't know what makes it faster.
I am attempting to change some third party class definitions, before each test, to simulate different results. I have to use something like javassist because extending the classes, sometimes, is just not possible due to the access modifiers. Here is an example of what I am attempting to do with javassist and junit combined:
public class SimulatedSession extends SomeThirdParty {
private boolean isJoe = false;
public SimulatedSession(final boolean isJoe) {
this.isJoe = isJoe;
}
#Override
public void performThis() {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("some.package.Class");
final CtMethod callMethod = internalClass.getDeclaredMethod("doThis");
if (isJoe) {
callMethod.setBody("{System.out.println(\"Joe\");}");
} else {
callMethod.setBody("{System.out.println(\"mik\");}");
}
internalClass.toClass();
}
}
#Test
public void firstTest() {
SimulatedSession toUse = new SimulatedSession(false);
// do something with this object and this flow
}
#Test
public void nextTest() {
SimulatedSession toUse = new SimulatedSession(true);
// do something with this object and this flow
}
if I run each test individually, I can run the code just fine. When I run them using the unit suite, one test after the other, I get a "frozen class issue". To get around this, I am looking at this post, however, I must admit I am unsure as to how one can use a different class pool to solve the issue.
Your current code will try to load twice the same class into the same ClassLoader which is forbidden, you can only load once a class for a given ClassLoader.
To make your unit tests pass, I had to:
Create my own temporary ClassLoader that will be able to load some.package.Class (that I replaced by javassist.MyClass for testing purpose) and that will be implemented in such way that it will first try to load the class from it before the parent's CL.
Set my own ClassLoader as context ClassLoader.
Change the code of SimulatedSession#performThis() to be able to get the class instance created by this method and to call internalClass.defrost() to prevent the "frozen class issue".
Invoke by reflection the method doThis() to make sure that I have different output by using the class instance returned by SimulatedSession#performThis() to make sure that the class used has been loaded with my ClassLoader.
So assuming that my class javassist.MyClass is:
package javassist;
public class MyClass {
public void doThis() {
}
}
The method SimulatedSession#performThis() with the modifications:
public Class<?> performThis() throws Exception {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("javassist.MyClass");
// Prevent the "frozen class issue"
internalClass.defrost();
...
return internalClass.toClass();
}
The unit tests:
// The custom CL
private URLClassLoader cl;
// The previous context CL
private ClassLoader old;
#Before
public void init() throws Exception {
// Provide the URL corresponding to the folder that contains the class
// `javassist.MyClass`
this.cl = new URLClassLoader(new URL[]{new File("target/classes").toURI().toURL()}){
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
// Try to find the class for this CL
return findClass(name);
} catch( ClassNotFoundException e ) {
// Could not find the class so load it from the parent
return super.loadClass(name, resolve);
}
}
};
// Get the current context CL and store it into old
this.old = Thread.currentThread().getContextClassLoader();
// Set the custom CL as new context CL
Thread.currentThread().setContextClassLoader(cl);
}
#After
public void restore() throws Exception {
// Restore the context CL
Thread.currentThread().setContextClassLoader(old);
// Close the custom CL
cl.close();
}
#Test
public void firstTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(false);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
#Test
public void nextTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(true);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
Output:
mik
Joe
Take a look at retransformer. It's a Javassist based lib I wrote for running tests just like this. It's a bit more terse than using raw Javassist.
Maybe another approach. We had a similar problem as we once mocked a dependency - we could not reset it. So we did the following: Before each test we replace the 'live' instance with our mock. After the tests, we restore the live instance. So I propose that you replace the modified instance of your third party code for each test.
#Before
public void setup()
{
this.liveBeanImpl = (LiveBean) ReflectionTools.getFieldValue(this.beanToTest, "liveBean");
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", new TestStub());
}
#After
public void cleanup()
{
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", his.liveBeanImpl);
}
The setFieldValue looks like this:
public static void setFieldValue(Object instanceToModify, String fieldName, Object valueToSet)
{
try
{
Field declaredFieldToSet = instanceToModify.getClass().getDeclaredField(fieldName);
declaredFieldToSet.setAccessible(true);
declaredFieldToSet.set(instanceToModify, valueToSet);
declaredFieldToSet.setAccessible(false);
}
catch (Exception exception)
{
String className = exception.getClass().getCanonicalName();
String message = exception.getMessage();
String errorFormat = "\n\t'%s' caught when setting value of field '%s': %s";
String error = String.format(errorFormat, className, fieldName, message);
Assert.fail(error);
}
}
So maybe your tests will pass if you reset your implementation for each test. Do you get the idea?
I would like to create a lambda function in Java 8, get it's classname and then later instantiate the function again from its classname.
This is what I try:
import java.util.function.Consumer;
public class SimpleLambda
{
public static void call(String aLambdaClassName, String aArg) throws Exception
{
Class<Consumer<String>> lClass = (Class<Consumer<String>>) Class.forName(aLambdaClassName);
Consumer<String> newlamba = lClass.newInstance();
newlamba.accept(aArg);
}
public static void main(String[] args) throws Exception
{
{
// Attempt with a static method as lambda
Consumer<String> lambda = Host::action;
String classname = lambda.getClass().getName();
call(classname, "Hello world");
}
{
// Attempt with a locally defined lambda
Consumer<String> lambda = (s) -> { System.out.println(s); };
String classname = lambda.getClass().getName();
call(classname, "Hello world");
}
}
}
class Host {
public static void action(String aMessage) {
System.out.println(aMessage);
}
}
However, with this code (in both variants, using the static method reference and using the locally declared lambda), I get an exception:
Exception in thread "main" java.lang.ClassNotFoundException: mypackage.SimpleLambda$$Lambda$1/471910020
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at mypackage.SimpleLambda.main(SimpleLambda.java:12)
I would have expected that at I can at least re-instantiate the static method reference... nope, apparently not.
I have been using a similar approach with Groovy Closures and that worked nicely. So am I just doing something wrong with the Java 8 lambdas, or is it not possible to instantiate lambdas by name? I found some hints on the net that lambdas can be (de)serialized, so I would expect it should also be possible to instantiate them by name.
Well, it is a special property of Oracle’s JRE/OpenJDK to use “anonymous classes”, which can’t be accessed by name at all. But even without this, there is no reason why this ought to work:
Class.forName(String) tries to resolve the class via the caller’s ClassLoader. So even if lambda expressions were implemented using ordinary classes, there were not accessible if loaded via a different ClassLoader
Class.newInstance() only works if there is a public no-arg constructor. You can’t assume that there is a no-arg constructor nor that it is public
The assumption that the entire function’s logic has to reside in a single class is wrong. A counter-example would be java.lang.reflect.Proxy which generates interface implementations delegating to an InvocationHandler. Trying to re-instantiate such a proxy via its class name would fail, because you need the to pass the actual InvocationHandler instance to the proxy’s constructor. In principle, the JRE specific lambda expression implementation could use a similar pattern
Considering the points above, it should be clear that you can’t say that it worked with inner classes in general. There are a lot of constraints you have to fulfill for that.
Regarding Serialization, it works for serializable lambda expressions, because the persistent form is completely detached from the runtime implementation class, as described in this answer. So the name of the generated class is not contained in the serialized form and the deserializing end could have an entirely different runtime implementation.
Store the lambda instances in Map, keyed on the instance name. You can make the map globally available trough a singleton wrapper class (just watch out for synchronization issues).
class LambdaMap {
private HashMap<String, Consumer<String>> theMap;
private LambdaMap() {
theMap = new HashMap<>();
}
private static class INSTANCE_HOLDER {
private static LambdaMap INSTANCE = new LambdaMap();
}
public static LambdaMap getInstance() {
return INSTANCE_HOLDER.INSTANCE;
}
public Consumer<String> put(String key, Consumer<String> value) {
return theMap.put(key, value);
}
public static void Call(String aLambdaClassName, String aArg) {
Consumer<String> func = getInstance().theMap.get(aLambdaClassName);
if (func != null) {
func.accept(aArg);
}
}
}
class Host {
public static void action(String aMessage) {
System.out.println("Goodbye, " + aMessage);
}
}
public class GlobalLambdas {
public static void main(String[] args) {
LambdaMap.getInstance().put("print greeting", s -> {
System.out.println("Hello, " + s);
});
LambdaMap.getInstance().put("print goodbye", Host::action);
LambdaMap.Call("print greeting", "John");
LambdaMap.Call("print goodbye", "John");
}
}
run:
Hello, John
Goodbye, John
Please have a look at the following code:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
printMethodName(MyClass::myMethod);
Full example:
#FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
In other words I would like to have a code which is the equivalent of the following C# code:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
In my case I was looking for a way to get rid of this in unit tests:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPoint and checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
From the ideas by #ddan I built a proxy solution using Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
final String methodName = getMethodName(object.getClass(), getter);
assertEquals(getter.apply(object), expected, methodName);
}
#SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
final Method[] method = new Method[1];
getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
})));
return method[0].getName();
}
No I can simply use
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
Downside:
Will be slightly slower than above
Needs Mockito to work
Hardly useful to anything but the usecase above.
However it does show a way how it could be done.
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
You can add safety-mirror to your classpath and do like this:
Method m1 = Types.createMethod(Thread::isAlive) // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
The library also offers a getName(...) method:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of.
See fx Holger's comment here: How to get the name of the method resulting from a lambda
There may not be a reliable way, but under some circumstances:
your MyClass is not final, and has an accessible constructor (limitation of cglib)
your myMethod is not overloaded, and not static
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.
Example code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
public class MethodReferenceUtils {
#FunctionalInterface
public static interface MethodRefWith1Arg<T, A1> {
void call(T t, A1 a1);
}
public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
return findReferencedMethod(clazz, t -> methodRef.call(t, null));
}
#SuppressWarnings("unchecked")
private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
AtomicReference<Method> ref = new AtomicReference<>();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
#Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ref.set(method);
return null;
}
});
try {
invoker.accept((T) enhancer.create());
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
Method method = ref.get();
if (method == null) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
return method;
}
}
In the above code, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.
If you can make the interface Action extend Serializable, then this answer from another question seems to provide a solution (at least on some compilers and runtimes).
We have published the small library reflection-util that can be used to capture a method name.
Example:
class MyClass {
private int value;
public void myMethod() {
}
public int getValue() {
return value;
}
}
String methodName = ClassUtils.getMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"
Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name.
ClassUtils caches the information such that we do not need to create a new proxy on every invocation.
Please note that this approach is no silver bullet and there are some known cases that don’t work:
It doesn’t work for static methods.
It doesn’t work if the class is final.
We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
You can use my library Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Another solution using Mockito:
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>compile</scope>
</dependency>
Test code:
public class MethodUtilTest {
#Test
void testMethodNameGetter() {
final Method method = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
Assertions.assertEquals("getTestString", method.getName());
}
#Test
void testMethodNameSetter() {
final Method method = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
Assertions.assertEquals("setTestString", method.getName());
}
}
java code:
public class MethodUtil {
public static <T> Method getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
return captureMethodOnInvocation(clazz, getter::apply);
}
public static <T, V> Method getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
return captureMethodOnInvocation(clazz, (T mock) -> setter.accept(mock, ArgumentMatchers.any()));
}
private static <T> Method captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
try {
final AtomicReference<Method> methodReference = new AtomicReference<>();
final InvocationListener invocationListener = new InvocationListener() {
#Override
public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
final Method method = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
methodReference.set(method);
}
};
final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
final T mock = Mockito.mock(clazz, mockSettings);
invokeMock.accept(mock);
return methodReference.get();
} catch (final Exception e) {
throw new RuntimeException("Method could not be captured at runtime.", e);
}
}
}
So, I play with this code
import sun.reflect.ConstantPool;
import java.lang.reflect.Method;
import java.util.function.Consumer;
public class Main {
private Consumer<String> consumer;
Main() {
consumer = this::test;
}
public void test(String val) {
System.out.println("val = " + val);
}
public void run() throws Exception {
ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
for (int i = 0; i < oa.getSize(); i++) {
try {
Object v = oa.getMethodAt(i);
if (v instanceof Method) {
System.out.println("index = " + i + ", method = " + v);
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
output of this code is:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30.
Final code may look like
public Method unreference(Object methodRef) {
ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
try {
Object method = constantPool.getMethodAt(30);
if (method instanceof Method) {
return (Method) method;
}
}catch (Exception ignored) {
}
throw new IllegalArgumentException("Not a method reference.");
}
Be careful with this code in production!
Try this
Thread.currentThread().getStackTrace()[2].getMethodName();
Please have a look at the following code:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
printMethodName(MyClass::myMethod);
Full example:
#FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
In other words I would like to have a code which is the equivalent of the following C# code:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
In my case I was looking for a way to get rid of this in unit tests:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPoint and checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
From the ideas by #ddan I built a proxy solution using Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
final String methodName = getMethodName(object.getClass(), getter);
assertEquals(getter.apply(object), expected, methodName);
}
#SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
final Method[] method = new Method[1];
getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
})));
return method[0].getName();
}
No I can simply use
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
Downside:
Will be slightly slower than above
Needs Mockito to work
Hardly useful to anything but the usecase above.
However it does show a way how it could be done.
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
You can add safety-mirror to your classpath and do like this:
Method m1 = Types.createMethod(Thread::isAlive) // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
The library also offers a getName(...) method:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of.
See fx Holger's comment here: How to get the name of the method resulting from a lambda
There may not be a reliable way, but under some circumstances:
your MyClass is not final, and has an accessible constructor (limitation of cglib)
your myMethod is not overloaded, and not static
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.
Example code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
public class MethodReferenceUtils {
#FunctionalInterface
public static interface MethodRefWith1Arg<T, A1> {
void call(T t, A1 a1);
}
public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
return findReferencedMethod(clazz, t -> methodRef.call(t, null));
}
#SuppressWarnings("unchecked")
private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
AtomicReference<Method> ref = new AtomicReference<>();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
#Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ref.set(method);
return null;
}
});
try {
invoker.accept((T) enhancer.create());
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
Method method = ref.get();
if (method == null) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
return method;
}
}
In the above code, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.
If you can make the interface Action extend Serializable, then this answer from another question seems to provide a solution (at least on some compilers and runtimes).
We have published the small library reflection-util that can be used to capture a method name.
Example:
class MyClass {
private int value;
public void myMethod() {
}
public int getValue() {
return value;
}
}
String methodName = ClassUtils.getMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"
Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name.
ClassUtils caches the information such that we do not need to create a new proxy on every invocation.
Please note that this approach is no silver bullet and there are some known cases that don’t work:
It doesn’t work for static methods.
It doesn’t work if the class is final.
We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
You can use my library Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Another solution using Mockito:
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>compile</scope>
</dependency>
Test code:
public class MethodUtilTest {
#Test
void testMethodNameGetter() {
final Method method = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
Assertions.assertEquals("getTestString", method.getName());
}
#Test
void testMethodNameSetter() {
final Method method = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
Assertions.assertEquals("setTestString", method.getName());
}
}
java code:
public class MethodUtil {
public static <T> Method getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
return captureMethodOnInvocation(clazz, getter::apply);
}
public static <T, V> Method getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
return captureMethodOnInvocation(clazz, (T mock) -> setter.accept(mock, ArgumentMatchers.any()));
}
private static <T> Method captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
try {
final AtomicReference<Method> methodReference = new AtomicReference<>();
final InvocationListener invocationListener = new InvocationListener() {
#Override
public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
final Method method = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
methodReference.set(method);
}
};
final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
final T mock = Mockito.mock(clazz, mockSettings);
invokeMock.accept(mock);
return methodReference.get();
} catch (final Exception e) {
throw new RuntimeException("Method could not be captured at runtime.", e);
}
}
}
So, I play with this code
import sun.reflect.ConstantPool;
import java.lang.reflect.Method;
import java.util.function.Consumer;
public class Main {
private Consumer<String> consumer;
Main() {
consumer = this::test;
}
public void test(String val) {
System.out.println("val = " + val);
}
public void run() throws Exception {
ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
for (int i = 0; i < oa.getSize(); i++) {
try {
Object v = oa.getMethodAt(i);
if (v instanceof Method) {
System.out.println("index = " + i + ", method = " + v);
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
output of this code is:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30.
Final code may look like
public Method unreference(Object methodRef) {
ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
try {
Object method = constantPool.getMethodAt(30);
if (method instanceof Method) {
return (Method) method;
}
}catch (Exception ignored) {
}
throw new IllegalArgumentException("Not a method reference.");
}
Be careful with this code in production!
Try this
Thread.currentThread().getStackTrace()[2].getMethodName();