Reputation: 13507
I have a class with methods that return a class only if that class exists, calling the method would generate a NoClassDefFoundError caused by ClassNotFoundException
exception if the classpath is not loaded (rightfully so, because it's not mandatory).
How can I still get the methods from Class#getMethods() when some of the methods (or fields) may not be loaded in the classpath? Is it possible to ignore the methods/fields when retrieving them?
Note that using try-catch will prevent the entire process from working at all, so it is NOT the solution!
Here is how I am loading the class:
try {
String name = StringUtil.format("Nifty{0}", (isBungee ? "Bungee" : "Bukkit"));
Reflection main = new Reflection(name, StringUtil.format("net.netcoding.{0}", name.toLowerCase())); // net.netcoding.niftybukkit.NiftyBukkit
Object mainObj = main.invokeMethod("getPlugin", null); // See below
logger = (Logger)main.invokeMethod("getLogger", mainObj);
} catch (Exception ex) { }
Here is the two bits that try to locate the method in Reflection:
public class Reflection {
private static final transient ConcurrentHashMap<Class<?>, Class<?>> CORRESPONDING_TYPES = new ConcurrentHashMap<>();
private final String className;
private final String subPackage;
private final String packagePath;
static {
CORRESPONDING_TYPES.put(Byte.class, byte.class);
CORRESPONDING_TYPES.put(Short.class, short.class);
CORRESPONDING_TYPES.put(Integer.class, int.class);
CORRESPONDING_TYPES.put(Long.class, long.class);
CORRESPONDING_TYPES.put(Character.class, char.class);
CORRESPONDING_TYPES.put(Float.class, float.class);
CORRESPONDING_TYPES.put(Double.class, double.class);
CORRESPONDING_TYPES.put(Boolean.class, boolean.class);
}
public Reflection(String className, String packagePath) {
this(className, "", packagePath);
}
public Reflection(String className, String subPackage, String packagePath) {
this.className = className;
this.subPackage = StringUtil.stripNull(subPackage).replaceAll("\\.$", "").replaceAll("^\\.", "");
this.packagePath = packagePath;
}
public String getClassName() {
return this.className;
}
public String getClassPath() {
return this.getPackagePath() + (StringUtil.notEmpty(this.subPackage) ? "." + this.subPackage : "") + "." + this.getClassName();
}
public Class<?> getClazz() throws Exception {
return Class.forName(this.getClassPath());
}
public Object invokeMethod(String name, Object obj, Object... args) throws Exception {
return this.getMethod(name, toPrimitiveTypeArray(args)).invoke(obj, args);
}
private static Class<?> getPrimitiveType(Class<?> clazz) {
return clazz != null ? CORRESPONDING_TYPES.containsKey(clazz) ? CORRESPONDING_TYPES.get(clazz) : clazz : null;
}
public Method getMethod(String name, Class<?>... paramTypes) throws Exception {
Class<?>[] types = toPrimitiveTypeArray(paramTypes);
// In this example, this.getClazz() simply returns
// a Class of net.netcoding.niftybukkit.NiftyBukkit
// this.getClazz().getMethods() throws the ClassNotFOundException
// I want to still access the methods, even if one of them is not available
for (Method method : this.getClazz().getMethods()) {
Class<?>[] methodTypes = toPrimitiveTypeArray(method.getParameterTypes());
if (method.getName().equals(name) && isEqualsTypeArray(methodTypes, types)) {
method.setAccessible(true);
return method;
}
}
System.out.println(StringUtil.format("The method {0} was not found with parameters {1}!", name, Arrays.asList(types)));
return null;
}
Upvotes: 2
Views: 2336
Reputation: 298143
If you keep your design, you are creating non-portable code that relies on non-guaranteed behavior.
If a method’s return type refers to a non-available class, it doesn’t help if the method’s code attempts to handle the absence of the class (return null
or whatever) or that the method is not called.
It’s not only the Class.getMethods
which may fail, it may cause the entire class to be unusable. The problem is that the JVM has some freedom regarding when to throw an error if a directly referenced class cannot be resolved. One valid point is the class loading time, so with a different JVM, implementing such a resolving strategy, you wouldn’t even get so far that you can call Class.getMethods()
.
The only way to have valid code dealing with optional classes is to decouple the directly referenced classes from the optional classes, e.g.
public static BaseType getOptionalFeature() {
try {
return (BaseType)Class.forName("OptionalType").newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
return null;
}
}
Assuming that OptionalType
extends or implements BaseType
, the important aspect is that Baseype
is a type always available and that all non-optional code always refers to the optional feature through BaseType
. You may have more code accessing OptionalType
directly, which you assume not to be called if OptionalType
is not available, but it also must be detached from the non-optional code using dynamic class loading.
Otherwise you are risking a complete failure on eagerly resolving JVMs and spurious failures on lazily resolving JVMs like Oracle’s. The fact that it doesn’t immediately fail if you don’t call the problematic method is not a guaranteed behavior for yours sake— it’s just an optimization.
Upvotes: 3
Reputation:
What about:
public Class<?> getClazz() {
try {
return Class.forName(this.getClassPath());
} catch (ClassNotFoundException e) {
// ignore or better log
}
return null;
}
then later:
Class<?> clazz = this.getClazz();
if (clazz != null) {
for (Method method : clazz.getMethods()) {
// ....
}
}
Upvotes: 0