I'm having problems with an MBean that takes a Map<String, Object> as a parameter. If I try to execute it via JMX using a proxy object, I get an Exception:
Caused by: javax.management.ReflectionException
at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:231)
at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:668)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
Caused by: java.lang.IllegalArgumentException: Unable to find operation updateProperties(java.util.HashMap)
It appears that it attempts to use the actual implementation class rather than the interface, and doesn't check if this is a child of the required interface. The same thing happens for extended classes (for example declare HashMap, pass in LinkedHashMap). Does this mean it's impossible to use an interface for such methods? At the moment I'm getting around it by changing the method signature to accept a HashMap, but it seems odd that I wouldn't be able to use interfaces (or extended classes) in my MBeans.
Edit: The proxy object is being created by an in-house utility class called JmxInvocationHandler. The (hopefully) relevant parts of it are as follows:
public class JmxInvocationHandler implements InvocationHandler
{
...
public static <T> T createMBean(final Class<T> iface, SFSTestProperties properties, String mbean, int shHostID)
{
T newProxyInstance = (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, (InvocationHandler) new JmxInvocationHandler(properties, mbean, shHostID));
return newProxyInstance;
}
...
private JmxInvocationHandler(SFSTestProperties properties, String mbean, int shHostID)
{
this.mbeanName = mbean + MBEAN_SUFFIX + shHostID;
msConfig = new MsConfiguration(properties.getHost(0), properties.getMSAdminPort(), properties.getMSUser(), properties.getMSPassword());
}
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (management == null)
{
management = ManagementClientStore.getInstance().getManagementClient(msConfig.getHost(),
msConfig.getAdminPort(), msConfig.getUser(), msConfig.getPassword(), false);
}
final Object result = management.methodCall(mbeanName, method.getName(), args == null? new Object[] {} : args);
return result;
}
}
Got it. JMX invocations sometimes make cannon-fodder of the best intended utility classes .... :)
This guy, I suspect, is a problem:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (management == null)
{
management = ManagementClientStore.getInstance().getManagementClient(msConfig.getHost(),
msConfig.getAdminPort(), msConfig.getUser(), msConfig.getPassword(), false);
}
final Object result = management.methodCall(mbeanName, method.getName(), args == null? new Object[] {} : args);
return result;
}
because the MBean's operation signature (which cares not a whit about inheritance) is determined from the classes of the passed arguments. Since you cannot pass an actual concrete object for which getClass() will return java.util.Map, you will never make a match using the direct types of the arguments themselves. (Similar problems occur with primitives for the same reason).
See this blog post starting with the paragraph opening with "One of the tricky parts of making MetaMBean", as it explains this problem (or the problem I think you're having) in a bit more detail, but the invoke method of the MBeanServer[Connection] is:
invoke(ObjectName name, String operationName, Object[] params, String[] signature)
The first 2 and the last arguments are navigational in that they specify exactly which operation amongst all the ops published in the server should be invoked. The best way to sidestep this issue is to avoid having to "guess" the signature and only rely on the ObjectName and the operation name, which in turn can be done by interrogating (and possibly caching) the MBeanInfo and MBeanOperationInfos of the target MBean. The MBeanOperationInfos will provide you the signature so you don't have to guess.
If this is indeed your issue, there's a couple of ways you can address it:
If the MBean's operation names are unique (i.e. there's no overloading) then you can just use the op name to retrieve the MBeanInfo.
If the MBean's operation is overloaded (i.e. there are multiple operations with the same name but different parameters)... but they all have different parameter counts, then you can easilly determine the correct signature by iterating all the matching op names in the MBeanOperationInfos and matching by param count.
If #1 and #2 do not apply.... then it's tricky and I would re-evaluate the method signatures of your MBean's code.
If #1 and #2 do not apply and #3 will not comply, take a look at this class in Gmx called MetaMBean. In the latest revision, it uses Groovy to create a compiled runtime interface using the MBean's MBeanInfo to make inheritance (and autoboxing) work in method invocation. The same method could be implemented in JavaScript (which has the virtue of being built into Java 6+) or several other JVM scripting languages. Alternatively, look at the previous version which attempted to pattern match against known operation signatures (and worked pretty well actually, but since I was working with Groovy anyways......)
I hope this is helpful. If this turns out not to be the root cause, then forget I said anything....
Related
Is there a way to detect usages of java method reference (double colon) operator inside the code?
I need to discover all instance/static method references used in a given class in order to be able to detect some errors (must verify that the target method has a particular annotation - #Good in the below example) during build time. As by convention a method reference should be used only to some of the methods when it is passed to a constructor of some helper class (Info in the below example).
class X {
Info init() {
return new Info(X::beta); // good code: target method has #Good annotation
return new Info(X::alpha); // bad code: target method has no #Good annotation
}
void alpha() {
}
#Good
void beta() {
}
}
The intention is to be able to click on the method reference as this makes it easy to follow as otherwise if just passing Method instance or just method name it would lack this ability.
(The example is not very good but I'm now allowed to share more details, sorry about that!)
I can see IntelliJ IDEA "knows" about them - when you ctrl+click on them it navigates to the target method so there should be some form of a static analysis used there.
I'm already using ObjectWeb ASM to detect invocations to certain methods but it seems it lacks the ability to detect method references (::)
EDIT:
Just a note that you can also pass new Info(x -> x.alpha()) as #Thomas below mentioned in the comments but this would not pass our review process but I guess the additional ability to detect it would not hurt.
EDIT2: What exactly are you trying to achieve with these checks? What makes beta worthy of receiving the annotation?
Answer:
When the init() method is called we obtain the Info instance and from it obtain the lambda which must be a method reference. Then we use javassist ProxyFactory and create a sub-class of class X then instantiate it and intercept all its methods via setting a method handler. So now it is safe to execute the lambda without allowing it to make any side effects - the method body is skipped and the only thing we do is to capture which is the X method that the lambda actually is calling - in the example this will lead to a java.lang.Method instance pointing to X.beta or X.alpha method. Then we can check if it has the #Good annotation and proceed accordingly - which is to call the lambda without any proxying, but that call might happen later, like a millisecond later or an hour later. If there is no #Good annotation we cannot proceed - it is a bug.
So the problem is that this will happen at runtime later and there might be a bug not caught early enough and that is the reason I would like to inspect the X class at build time and catch all the bugs :)
This is a bit of a shot in the dark, as I'm neither very proficient with ASM nor sure if this approach addresses your problem. Having said that, I found that, in a similar setting, asm.MethodVisitor calls MethodVisitor.visitInvokeDynamicInsn(...) for (some? all?) method references.
E.g., if I compile this variant of your class X along with an Info:
class Info {
public Info(Runnable alpha) {}
}
class X {
Info init() { return new Info(this::alpha); }
void alpha() {}
}
... and I then feed the resulting X.class into a mini ClassVisitor + printing MethodVisitor (Groovy for brevity):
class MyMethodVisitor extends MethodVisitor {
MyMethodVisitor(MethodVisitor parent) { super(Opcodes.ASM8, parent) }
#Override
void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
println "visitInvokeDynamicInsn($name, $descriptor, $bootstrapMethodHandle, $bootstrapMethodArguments)"
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
}
}
class MyClassVisitor extends ClassVisitor {
MyClassVisitor() { super(Opcodes.ASM8) }
#Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
println "Starting method '$name'"
new MyMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions))
}
}
def clr = new ClassReader(new File("./X.class").bytes)
clr.accept(new MyClassVisitor(), ClassReader.SKIP_FRAMES)
Then the method visitor prints, amongst other details, a call to visitInvokeDynamicInsn from within the method visitation of X::init with the desired X::alpha among the arguments (the xyz being my local package):
Visiting method '<init>'
Visiting method 'init'
visitInvokeDynamicInsn(run, (xyz/X;)Ljava/lang/Runnable;,
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; (6),
[()V, xyz/X.alpha()V (5), ()V])
Visiting method 'alpha'
So it would seem possible to peel the method out of those arguments. I am not sure if this reliable (e.g., whether this bytecode is guaranteed by specification, or whether it can depend on compilation/optimization details).
Based on this stackoverflow answer, I am attempting to instantiate a class using reflection and then invoke a one-argument method on it using LambdaMetafactory::metafactory (I tried using reflection, but it was rather slow).
More concretely, I want to create an instance of com.google.googlejavaformat.java.Formatter, and invoke its formatSource() method with the following signature: String formatSource(String input) throws FormatterException.
I have defined the following functional interface:
#FunctionalInterface
public interface FormatInvoker {
String invoke(String text) throws FormatterException;
}
and am attempting to execute the following code:
try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[urls.size()]))) {
Thread.currentThread().setContextClassLoader(cl);
Class<?> formatterClass =
cl.loadClass("com.google.googlejavaformat.java.Formatter");
Object formatInstance = formatterClass.getConstructor().newInstance();
Method method = formatterClass.getMethod("formatSource", String.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.unreflect(method);
MethodType type = methodHandle.type();
MethodType factoryType =
MethodType.methodType(FormatInvoker.class, type.parameterType(0));
type = type.dropParameterTypes(0, 1);
FormatInvoker formatInvoker = (FormatInvoker)
LambdaMetafactory
.metafactory(
lookup,
"invoke",
factoryType,
type,
methodHandle,
type)
.getTarget()
.invoke(formatInstance);
String text = (String) formatInvoker.invoke(sourceText);
} finally {
Thread.currentThread().setContextClassLoader(originalClassloader);
}
When I run this code, the call to LambdaMetafactory::metafactory fails with the following exception:
Caused by: java.lang.invoke.LambdaConversionException: Exception finding constructor
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:229)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:304)
at com.mycompany.gradle.javaformat.tasks.JavaFormatter.formatSource(JavaFormatter.java:153)
... 51 more
Caused by: java.lang.IllegalAccessException: no such method: com.delphix.gradle.javaformat.tasks.JavaFormatter$$Lambda$20/21898248.get$Lambda(Formatter)FormatInvoker/invokeStatic
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:867)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1003)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1386)
at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:780)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:226)
... 53 more
Caused by: java.lang.LinkageError: bad method type alias: (Formatter)FormatInvoker not visible from class com.delphix.gradle.javaformat.tasks.JavaFormatter$$Lambda$20/21898248
at java.lang.invoke.MemberName.checkForTypeAlias(MemberName.java:793)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:976)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
... 56 more
I've read through a number of stackoverflow answers about LambdaMetafactory and read the LambdaMetafactory documentation, but have not been able to figure out what I am doing wrong. I am hoping that somebody else will be able to.
Thank you in advance for your help.
The MethodHandles.Lookup instance returned by MethodHandles.lookup() encapsulates the caller’s context, that is, the context of your class which creates the new class loader. As the exception says, the type Formatter is not visible from this context. You can see this as an attempt to mimic the compile-time semantics of the operation; if you placed the statement Formatter.formatSource(sourceText) in your code, it wouldn’t work as well, due to the fact that the type is not in scope.
You can change the context class of the lookup object using in(Class), but when using MethodHandles.lookup().in(formatterClass), you’ll run into a different problem. Changing the context class of a lookup object will reduce the access level to align it with the Java access rules, i.e. you can only access public members of the class Formatter. But the LambdaMetafactory only accepts lookup objects having private access to their lookup class, i.e. lookup objects directly produced by the caller itself. The only exception would be changing between nested classes.
Therefore using MethodHandles.lookup().in(formatterClass) results in Invalid caller: com.google.googlejavaformat.java.Formatter, as you (the caller) are not that Formatter class. Or technically, the lookup object has not the private access mode.
The Java API doesn’t offer any (simple) way to get a lookup object to be in a different class loading context and having the private access (prior to Java 9). All regular mechanisms would involve the cooperation of the code residing in that context. That’s the point where developers often go the route of doing Reflection with access override to manipulate the lookup object, to have the desired properties. Unfortunately, the new module system is expected to become more restrictive in the future, likely breaking these solutions.
Java 9 offers a way to get such a lookup object, privateLookupIn, which requires the target class to be in the same module or its module to be opened to the caller’s module to permit such an access.
Since you are creating a new ClassLoader, you have hands on the class loading context. So, one way to solve the problem, is to add another class to it, which creates the lookup object and allows your calling code to retrieve it:
try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[0])) {
{ byte[] code = gimmeLookupClassDef();
defineClass("GimmeLookup", code, 0, code.length); } }) {
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
cl.loadClass("GimmeLookup").getField("lookup").get(null);
Class<?> formatterClass =
cl.loadClass("com.google.googlejavaformat.java.Formatter");
Object formatInstance = formatterClass.getConstructor().newInstance();
Method method = formatterClass.getMethod("formatSource", String.class);
MethodHandle methodHandle = lookup.unreflect(method);
MethodType type = methodHandle.type();
MethodType factoryType =
MethodType.methodType(FormatInvoker.class, type.parameterType(0));
type = type.dropParameterTypes(0, 1);
FormatInvoker formatInvoker = (FormatInvoker)
LambdaMetafactory.metafactory(
lookup, "invoke", factoryType, type, methodHandle, type)
.getTarget().invoke(formatInstance);
String text = (String) formatInvoker.invoke(sourceText);
System.out.println(text);
}
static byte[] gimmeLookupClassDef() {
return ( "\u00CA\u00FE\u00BA\u00BE\0\0\0001\0\21\1\0\13GimmeLookup\7\0\1\1\0\20"
+"java/lang/Object\7\0\3\1\0\10<clinit>\1\0\3()V\1\0\4Code\1\0\6lookup\1\0'Ljav"
+"a/lang/invoke/MethodHandles$Lookup;\14\0\10\0\11\11\0\2\0\12\1\0)()Ljava/lang"
+"/invoke/MethodHandles$Lookup;\1\0\36java/lang/invoke/MethodHandles\7\0\15\14\0"
+"\10\0\14\12\0\16\0\17\26\1\0\2\0\4\0\0\0\1\20\31\0\10\0\11\0\0\0\1\20\11\0\5\0"
+"\6\0\1\0\7\0\0\0\23\0\3\0\3\0\0\0\7\u00B8\0\20\u00B3\0\13\u00B1\0\0\0\0\0\0" )
.getBytes(StandardCharsets.ISO_8859_1);
}
This subclasses URLClassLoader to call defineClass once in the constructor to add a class being equivalent to
public interface GimmeLookup {
MethodHandles.Lookup lookup = MethodHandles.lookup();
}
Then, the code reads the lookup field via Reflection. The lookup object encapsulates the context of GimmeLookup, which is defined within the new URLClassLoader, and is sufficient to access the public method formatSource of the public com.google.googlejavaformat.java.Formatter.
The interface FormatInvoker will be accessible for that context, as your code’s class loader will become the parent of the created URLClassLoader.
Some additional notes:
Of course, this can only become more efficient than any other reflective access, if you use the generated FormatInvoker instance sufficiently often to compensate for the costs of creating it.
I removed the Thread.currentThread().setContextClassLoader(cl); statement, as it has no meaning in this operation, but is, in fact, quiet dangerous as you didn’t set it back, so the thread kept a reference to the closed URLClassLoader afterwards.
I simplified the toArray call to urls.toArray(new URL[0]). This article provides a really interesting view on the usefulness of specifying the collection’s size to the array.
In the interface I have an abstract method
Server launchInstance(
Instance instance,
String name,
Set<String> network,
String userData)
throws Exception;
Now in my class that implements the previous interface, I am overriding this method but I do not need all the parameters because that will cause a lot of unnecessary tasks. In my implemented class I want to do something like-
#override
Server launchInstance(Instance instance, String name) throws Exception;
How can I remove some unnecessary parameters in my implemented(from Interface) class while overriding?
That's not possible with Java.
An interface defines method that all implementing classes must support, in order to have a unifying API.
One purpose is to be able to exchange implementations.
I see a couple of options:
Add a second method to the interface with fewer parameters.
But this requires, of course, that all implementations support this.
This may therefore not be viable for you.
Implement an additional second interface, which defines the method with two parameters.
if (x instanceof Server2)
// short-cut: do not need to compute network and userData
((Server2) x).launchInstance(instance, name)
else {
Set<String> network = …;
x.launchInstance(instance, name, network, userData)
}
Simply ignore the additional parameters.
If you desperately need a unified interface and want to avoid computation costs of the additional arguments, wrap the optional arguments of type T using lazy evaluation (e.g. in a Callable<T>). If you do not need the values, simply never call the Callable.
Interface is a common API for number of classes. By design you don't want interface implementations to change API.
However, you can omit unused parameters:
#Override
Server launchInstance(Instance instance, String name, Set<String> network, String userData) throws Exception {
launch(instance, name);
}
private Server launch(Instance instance, String name) throws Exception {
...
}
or provide Data object:
class Data {
private Instance instance;
private String name;
private Set<String> network;
private String userData;
}
#Override
Server launchInstance(Data data) {
...
}
Also interface(read data transfer) could be simlified using Dependency Injection.
nope, you just can break the compromisse behind the override... you will need to redesign the method signature for something a little bit more abstract...
Yes, I know there are plenty of articles on the topic, but it's not that simple.
I've got a wrapper class which is used for generically passing arguments to methods and subsequently retrieving values. This wrapper class (called IDVariant) has a default type which indicates the principal type of variable stored.
Hence, I can have:
IDVariant v = new IDVariant(1);
boolean b = v.booleanValue();
String s = v.stringValue();
int i = v.integerValue();
and so on, but the default type would be int, as per the type of the parameter used to create the instance.
Now, this class, as mentioned, is used as a general placeholder in much bigger and more complex classes. I need to create a tool which parses a super-structure of classes recursively and recreate this structure in XML. Obviously, the way to go is Reflection, and I have been successful so far in recreating the structure of the whole thing. The only problem is, I have not come up with a way to find out the default type of the IDVariant, and thus having the correct type for each variable of the XML tree.
What I have tried doing is retrieving the get method for each property of each class, then invoking it and checking the default type of each IDVariant. It seemed like a good idea, but it's not working: I am getting an InvocationTargetException which, I presume, is due to the fact that the instance of the class I create in order to call the method, is in fact not populated with data. It's the only logical explanation I have been able to give.
If anyone has any idea, I would be most appreciative! :)
Following is a sample code fragment:
// Edit of the code, as correctly indicated by cyon. Tha variable className is given.
Class<?> toParse = Class.forName(className);
Object o = toParse.newInstance(); // There is a default constructor with no values, the object is not null
Class cl = ... // given class
Class[] noparams = {};
String s = "getSomeValue";
Method method = null;
for(Method m : cl.getMethods()) {
if (m.getName().equals(s)) {
method = m;
break;
}
}
if (method != null) {
try {
Object idv = method.invoke(o, noparams); // exception occurs at this point
String type = decodeType((IDVariant)idv); // function which maps internal codes of IDVariant default type to primitive and internal types
return type;
} catch (InvocationTargetException ex) {
System.out.println(ex.getCause());
}
}
NOTE: The function returns indeed a variable of type IDVariant, I have checked the source code out. I cannot change that code, however, as it is coming from an external tool which generates it automatically.
Thank you in advance :)
EDIT: Okay, so I've had a better look at the source produced by the external tool, and this is what I gather:
I am invoking a sample method, say getSomeValue. In the source of the class where the method is declared, here is the declaration of the method:
public IDVariant getSomeValue() { return GetPropDirect(externalValue); }
So, perhaps it's internally trying to invoke another method which belongs to a superclass, and I do not instantiate the superclass. Is it possible that this is the issue? And if yes, is there a workaround?
EDIT 2: As requested, here is the Stack Trace for the exception I am getting:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at prove.Reflector.getImplicitType(Reflector.java:275)
at prove.Reflector.recurr(Reflector.java:134)
at prove.Reflector.ParseToXml(Reflector.java:69)
at prove.Main.main(Main.java:12)
Caused by: java.lang.NullPointerException
at com.progamma.doc.IDDocument.GetPropDirect(IDDocument.java:739)
at it.zerounoesse.Calcolo7302013.Contribuente.getSomeValue(Contribuente.java:70)
... 8 more
Your IDVariant class is of course instantiated, thats what you do with toParse.newInstance() method, but since you do not cast the instantiated object to its original class type and then initialize it, there is a big probability of exception, for example NullPointerEx.
Update: Oracle has confirmed this as a bug.
Summary: Certain custom BeanInfos and PropertyDescriptors that work in JDK 1.6 fail in JDK 1.7, and some only fail after Garbage Collection has run and cleared certain SoftReferences.
Edit: This will also break the ExtendedBeanInfo in Spring 3.1 as noted at the bottom of the post.
Edit: If you invoke sections 7.1 or 8.3 of the JavaBeans spec, explain
exactly where those parts of the spec require anything. The
language is not imperative or normative in those sections. The
language in those sections is that of examples, which are at best
ambiguous as a specification. Furthermore, the BeanInfo API
specifically allows one to change the default behavior, and it is
clearly broken in the second example below.
The Java Beans specification looks for default setter methods with a void return type, but it allows customization of the getter and setter methods through a java.beans.PropertyDescriptor. The simplest way to use it has been to specify the names of the getter and setter.
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
This has worked in JDK 1.5 and JDK 1.6 to specify the setter name even when its return type is not void as in the test case below:
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import org.testng.annotations.*;
/**
* Shows what has worked up until JDK 1.7.
*/
public class PropertyDescriptorTest
{
private int i;
public int getI() { return i; }
// A setter that my people call "fluent".
public PropertyDescriptorTest setI(final int i) { this.i = i; return this; }
#Test
public void fluentBeans() throws IntrospectionException
{
// This throws an exception only in JDK 1.7.
final PropertyDescriptor pd = new PropertyDescriptor("i",
PropertyDescriptorTest.class, "getI", "setI");
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
}
The example of custom BeanInfos, which allow the programmatic control of PropertyDescriptors in the Java Beans specification all use void return types for their setters, but nothing in the specification indicates that those examples are normative, and now the behavior of this low-level utility has changed in the new Java classes, which happens to have broken some code on which I am working.
There are numerous changes in in the java.beans package between JDK 1.6 and 1.7, but the one that causes this test to fail appears to be in this diff:
## -240,11 +289,16 ##
}
if (writeMethodName == null) {
- writeMethodName = "set" + getBaseName();
+ writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
- writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
- (type == null) ? null : new Class[] { type });
+ Class[] args = (type == null) ? null : new Class[] { type };
+ writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
+ if (writeMethod != null) {
+ if (!writeMethod.getReturnType().equals(void.class)) {
+ writeMethod = null;
+ }
+ }
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
Instead of simply accepting the method with the correct name and parameters, the PropertyDescriptor is now also checking the return type to see whether it is null, so the fluent setter no longer gets used. The PropertyDescriptor throws an IntrospectionException in this case: "Method not found: setI".
However, the problem is much more insidious than the simple test above. Another way to specify the getter and setter methods in the PropertyDescriptor for a custom BeanInfo is to use the actual Method objects:
#Test
public void fluentBeansByMethod()
throws IntrospectionException, NoSuchMethodException
{
final Method readMethod = PropertyDescriptorTest.class.getMethod("getI");
final Method writeMethod = PropertyDescriptorTest.class.getMethod("setI",
Integer.TYPE);
final PropertyDescriptor pd = new PropertyDescriptor("i", readMethod,
writeMethod);
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
Now the above code will pass a unit test in both 1.6 and in 1.7, but the code will begin to fail at some point in time during the life of the JVM instance owing to the very same change that causes the first example to fail immediately. In the second example the only indication that anything has gone wrong comes when trying to use the custom PropertyDescriptor. The setter is null, and most utility code takes that to mean that the property is read-only.
The code in the diff is inside PropertyDescriptor.getWriteMethod(). It executes when the SoftReference holding the actual setter Method is empty. This code is invoked by the PropertyDescriptor constructor in the first example that takes the accessor method names above because initially there is no Method saved in the SoftReferences holding the actual getter and setter.
In the second example the read method and write method are stored in SoftReference objects in the PropertyDescriptor by the constructor, and at first these will contain references to the readMethod and writeMethod getter and setter Methods given to the constructor. If at some point those Soft references are cleared as the Garbage Collector is allowed to do (and it will do), then the getWriteMethod() code will see that the SoftReference gives back null, and it will try to discover the setter. This time, using the same code path inside PropertyDescriptor that causes the first example to fail in JDK 1.7, it will set the write Method to null because the return type is not void. (The return type is not part of a Java method signature.)
Having the behavior change like this over time when using a custom BeanInfo can be extremely confusing. Trying to duplicate the conditions that cause the Garbage Collector to clear those particular SoftReferences is also tedious (though maybe some instrumenting mocking may help.)
The Spring ExtendedBeanInfo class has tests similar to those above. Here is an actual Spring 3.1.1 unit test from ExtendedBeanInfoTest that will pass in unit test mode, but the code being tested will fail in the post-GC insidious mode::
#Test
public void nonStandardWriteMethodOnly() throws IntrospectionException {
#SuppressWarnings("unused") class C {
public C setFoo(String foo) { return this; }
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
}
One suggestion is that we can keep the current code working with the non-void setters by preventing the setter methods from being only softly reachable. That seems like it would work, but that is rather a hack around the changed behavior in JDK 1.7.
Q: Is there some definitive specification stating that non-void setters should be anathema? I've found nothing, and I currently consider this a bug in the JDK 1.7 libraries.
Am I wrong, and why?
Looks like the specification hasn't changed (it requires void setter) but the implementation has been updated to only allow void setters.
Specification:
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html
More specifically see section 7.1 (accessor methods) and 8.3 (design patterns for simple properties)
See some of the later answers in this stackoverflow question:
Does Java bean's setter permit return this?
Section 8.2 specifies:
However, within Java Beans the use of method and type names that match design patterns is entirely optional. If a programmer is prepared to explicitly specify their properties, methods, and events using the BeanInfo interface then they can call their methods and types whatever they like. However, these methods and types will still have to match the required type signatures, as this is essential to their operation.
(emphasis added)
Also, I beleive the method signatures shown in 7.1 and 8.3 are, in fact, normative. They are examples only in the sense that they use "foo" as an example property name.
I would also opt to say disallowing non-void setters is an error. It simply makes fluent programming impossible. Thats why it needs to be be changed.
Since I found Spring 3.1.1 ExtendedBeanInfo unit tests that expect the code not to be broken, and because having the behavior change after garbage collection is obviously a bug, I shall answer this and note the Java bug numbers. The bugs are still not visible on the external database of Java bugs, but I hope that they will become visible at some point:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172854 (Oracle closed this as a duplicate of the bug below since they have the same cause despite different manifestations.)
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172865
(The bugs were submitted on May 30, 2012.)
As of June 20, 2012, the bugs are visible in the external database via the links above.