Logger log = Logger.getLogger("myLogger");
log.info("Test");
We will see simple examples of access to methods and public fields of the Java Logging API using jInterflex.
Let’s suppose we need to execute through reflection a piece of code equivalent to this:
Logger log = Logger.getLogger("myLogger");
log.info("Test");
With jInterflex we will create a public auxiliary interface like this:
@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {
public static final StaticPart STATIC = JInterflex.proxy(StaticPart.class);
public interface StaticPart {
public PLogger getLogger(String name);
}
public void info(String text);
}
The name of the actual class to invoke through reflection is in the realClassName
attribute of the JInterflexProxy
annotation. The methods of the auxiliary
interface mimic the signature of the instance methods of the actual class to
invoke. On the other hand, the static methods are written in a separate class,
the inner interface StaticPart
. It is important to mention that only the methods
or public fields to access have to be included in the auxiliary interfaces.
The method JInterflex.proxy()
creates a proxy object of the class passed as
an argument (in this example our inner class StaticPart
) which will allow access
through reflection to static methods and public fields of the actual class (in
this case java.util.Logger
).
The static method returns a PLogger
object that can be used transparently like
the actual java.util.Logger
object.
The definition of a constant called STATIC
(or with any other name) is optional,
but recommended, because it improves the readability of the invocation and it
also allows to create the proxy object only once, improving performance.
Now we can write the invocations:
PLogger log = PLogger.STATIC.getLogger("myLogger");
log.info("Test");
It may be observed that the jInterflex invocation is very similar to the one
without reflection. Moreover jInterflex will take care of the exception handling,
throwing the same exception that the invocation does (a subclass of JInterflexException
can
also be thrown if there’s a problem preparing the invocation).
We can see that the constant STATIC
makes the code very compact and readable. If
we don’t define the STATIC
constant the invocation would be:
PLogger.StaticPart clz = JInterflex.proxy(PLogger.StaticPart.class);
PLogger log = clz.getLogger("myLogger");
log.info("Test");
We can compare the invocation using jInterflex with the normal invocation using the Reflection API:
try {
Class<?> logClz = Class.forName("java.util.logging.Logger");
Method getLoggerMethod = logClz.getDeclaredMethod("getLogger", String.class);
Object log = getLoggerMethod.invoke(null, "myLogger");
Method infoMethod = logClz.getDeclaredMethod("info", String.class);
infoMethod.invoke(log, "Test");
}
catch (IllegalAccessException ex) {
// -- Handle exception
}
catch (IllegalArgumentException ex) {
// -- Handle exception
}
catch (InvocationTargetException ex) {
// -- Handle exception
}
catch (NoSuchMethodException ex) {
// -- Handle exception
}
catch (SecurityException ex) {
// -- Handle exception
}
catch (ClassNotFoundException ex) {
// -- Handle exception
}
Of course, these catch
blocks can be omitted if we put their equivalent throws
parts in the declaration of the method. On the other hand, there are some exception
enhancements in JDK 7+ which can reduce the verbosity of these multiple catch
blocks.
The central part of the invocations would remain the same anyway.
We are going to use another example from the same java.util.Logging
package:
@JInterflexProxy(realClassName="java.util.logging.ConsoleHandler")
public interface PConsoleHandler {
public static final StaticPart STATIC = JInterflex.proxy(StaticPart.class);
public interface StaticPart {
public PConsoleHandler ctor();
}
}
It is clear from this example that constructors are defined in a similar way as
static methods, in the inner class part of the interface. The main difference is
the name of the method that mimics the constructor. If the name of the static method
is a special one reserved for constructors, the constructor will be invoked when
this method is called. The default name for constructors is "ctor", but it may
be modified setting the attribute ctorMethodName
of the JInterflexProxy
annotation.
The return type of the constructor method is the auxiliary interface itself, because this is the kind of objects the constructor will create (and because an interface has no constructor, so we cannot leave empty the return type, like in a real constructor).
Once we have the auxiliary interface, we can invoke the constructor:
PConsoleHandler consoleHandler = PConsoleHandler.STATIC.ctor();
If we want to change the name of the method to be considered a constructor by
jInterflex, we must specify a value for the optional attribute ctorMethodName
.
For example, if we want to use create
as the constructor method name for our
last auxiliary interface, we would write:
@JInterflexProxy(realClassName="java.util.logging.ConsoleHandler", ctorMethodName="create")
public interface PConsoleHandler {
public static final StaticPart STATIC = JInterflex.proxy(StaticPart.class);
public interface StaticPart {
public PConsoleHandler create();
}
}
@JInterflexProxy(realClassName="java.util.logging.Level")
public interface PLevel {
public static final StaticPart STATIC = JInterflex.proxy(StaticPart.class);
public interface StaticPart {
@FieldAccess
public PLevel FINE();
@FieldAccess
public PLevel INFO();
@FieldAccess
public PLevel WARNING();
}
}
The public fields are also defined as methods in the inner static part, but they
must include the @FieldAccess
annotation. The name of the method must be the
same name of the public field to be read.
The read access of public fields using jInterflex would be done this way:
PLevel level = PLevel.STATIC.FINE();
There are three levels of exceptions in jInterflex:
Clearly identified errors of usage: If the auxiliary interface that
contains the information is incomplete or incorrect, the method proxy
will
throw a JInterflexIllegalArgumentException
. For example, if a static method is defined
in an interface without an enclosing class or whose enclosing class has no
annotations. If we use the constant STATIC
in the auxiliary interface as it is
suggested, the access to it will throw an ExceptionOnInitializerError
(the first
time it’s used) or NoClassDefFoundError
(after the first time). These
errors will be caught at runtime, but in any test done before the code is deployed,
because they will always fail.
Runtime-detectable errors: For these errors, the method proxy
won’t
throw any exception, but any access to a method of the auxiliary interface
returned will throw a JInterflexNotAvailableException
that includes the root (cause)
exception (it is not a checked exception, so it is not necessary to catch it nor
to declare it in the throws
clause). For example, If the name of the class to
invoke is not found at runtime.
Exceptions thrown by the method call itself: They are thrown as they are in the moment of the invocation, like in a normal call.
The main use case of this library is the conditional use of functionality
that may or may not be available at runtime. We can simply use the library as
we have been doing, invoking the methods and reading the fields through the proxy
objects created by the framework. If they are not available at runtime, a
JInterflexNotAvailableException
will be thrown, we capture it in a catch
block and nothing
happens.
The problem with this is that it can lead to a lot of exception captures in
normal situations (not in exceptional -error- situations, where capturing exceptions
would be ok). In order to minimize the exception captures we should be able
to ask if a proxy class is available at runtime before using it. For this task
we can use the isProxyAvailable
method of the JInterflex
class. For example,
with our first simple invocation we would do:
if (JInterflex.isProxyAvailable(PLogger.STATIC)){
PLogger log = PLogger.STATIC.getLogger("myLogger");
log.info("Test");
}
else {
// -- Alternatives if we don't have these classes available at runtime
...
}
If we want to have the exception of the problem that prevented the
classes to be available, we can use the getProxyException
method. For
example:
if (JInterflex.isProxyAvailable(PLogger.STATIC)){
PLogger log = PLogger.STATIC.getLogger("myLogger");
log.info("Test");
}
else {
// -- Trace the exception to the console, or with a Logger, etc...
JInterflex.getProxyException(PLogger.STATIC).printStackTrace();
...
}
It’s important to mention that the methods JInterflex.isProxyAvailable
and JInterflex.getProxyException
would throw an
exception if there is an error of the first group described above.
Only the errors that can only be detectable at runtime
(JInterflexNotAvailableException
) would be caught. This allows to
detect early the mistakes in the auxiliary interfaces.