Just so that everyone understands how important this subject is, this change to
getCallerClass(...) is being labeled a "disaster" for logging frameworks
everywhere. Here's a benchmark for getting Classes from the following methods:
> 1,000,000 calls of all alternatives were measured as follows :
> Reflection: 10.195 ms.
> Current Thread StackTrace: 5886.964 ms.
> Throwable StackTrace: 4700.073 ms.
> SecurityManager: 1046.804 ms.
My goal here is to get the entire list engaged in coming up with the right
solution. We (the community) can't afford for Java 8 not to have an equivalent
replacement for getCallerClass().
Nick
On Jul 27, 2013, at 2:01 PM, Nick Williams wrote:
> All,
>
> In the last two months, there have been a number of discussions surrounding
> stack traces, Classes on the stack trace, and caller classes [1], [2], [3].
> These are all related discussions and the solution to them is equally
> related, so I wanted to consolidate it all into this one discussion where I
> hope we can finalize on a solution and get it implemented for Java 8.
>
> In a nut shell, here are the underlying needs that I have seen expressed
> through many, many messages:
>
> - Some code needs to get the Class of the caller of the current method,
> skipping any reflection methods.
> - Some code needs to get the Class of the caller /n/ stack frames before the
> current method, skipping any reflection methods.
> - Some code needs to get the current stack trace, populated with Classes,
> Executables, file names, line numbers, and native flags instead of the String
> class names and String method names in StackTraceElement. This /should/
> include any reflection methods, just like StackTraceElement[]s.
> - Some code needs to get the stack trace from when a Throwable was created,
> populated with Classes, Executables, file names, line numbers, and native
> flags instead of the String class names and String method names in
> StackTraceElement. This /should/ include any reflection methods, just like
> StackTraceElement[]s.
> - There needs to be a reflection way to achieve all of this since some
> libraries (e.g., Log4j) need to be compiled against Java 6 but run on 7 and 8
> (and thus can't use @CallerSensitive).
>
> I believe the solutions to these needs are all related. Importantly, I think
> it is very important that action be taken in Java 8 due to the changes made
> to sun.reflect.Reflection#getCallerClass(...). While we all understand that
> relying on private sun.* APIs is not safe, the fact is that many people have
> relied on sun.reflect.Reflection#getCallerClass(...) due to the fact that
> there is simply no other way to do this in the standard API. This includes
> Log4j 2, Logback, SLF4j, and Groovy, some features of which will stop working
> correctly in Java 7 >= u25.
>
> I would point out that this could all easily be solved simply by adding a
> getElementClass() method to StackTraceElement, but there was strong
> opposition to this, largely due to serialization issues. Since that is
> apparently not an option, I propose the following API, based on the various
> discussions in the last two months, StackTraceElement, and the API that .NET
> provides to achieve the same needs as listed above:
>
> CallerSensitive.java:
> package java.lang;
>
> /** Previously private API, now public */
> public @interface CallerSensitive {
> ...
> }
>
> StackTraceFrame.java:
> package java.lang;
>
> import java.util.Objects.
>
> public final class StackTraceFrame {
> private final Class<?> declaringClass;
> private final Executable executable;
> private final String fileName;
> private final int lineNumber;
>
> public StackTraceFrame(Class<?> declaringClass, Executable executable,
> String fileName, int lineNumber) {
> this.declaringClass = Objects.requireNonNull(declaringClass,
> "Declaring class is null");
> this.executable = Objects.requireNonNull(executable, "Executable is
> null");
> this.fileName = fileName;
> this.lineNumber = lineNumber;
> }
>
> public Class<?> getDeclaringClass() {
> return this.declaringClass;
> }
>
> public Executable getExecutable() {
> return this.executable;
> }
>
> public String getFileName() {
> return this.fileName;
> }
>
> public int getLineNumber() {
> return this.lineNumber;
> }
>
> public boolean isNative() {
> return this.lineNumber == -2;
> }
>
> public String toString() { /* Same as StackTraceElement */ }
> public boolean equals() { /* Ditto */ }
> public int hashCode() { /* Ditto */ }
>
> /** Uses @CallerSensitive */
> public static native StackTraceFrame getCallerFrame();
>
> /** Works like Java < 7u25 sun.reflect.Reflection#getCallerClass() */
> public static native StackTraceFrame getCallerFrame(int skipFrames);
>
> public static native StackTraceFrame[] getCurrentStackTrace();
> }
>
> Throwable.java:
> package java.lang;
>
> ...
>
> public class Throwable {
> ...
> public synchronized Throwable fillInStackTraceFrames() { ... }
>
> private native Throwable fillInStackTraceFrames(int dummy);
>
> public StackTraceFrame[] getStackTraceFrames() {
> return this.getOurStackTraceFrames().clone();
> }
>
> private synchronized StackTraceFrame[] getOurStackTraceFrames() { ... }
> ...
> }
>
> Furthermore, I propose that we restore the behavior of
> sun.reflect.Reflection#getCallerClass(int) /just for Java 7/ since the
> proposed above solution cannot be added to Java 7.
>
> I would love if we could quickly coalesce around this solution or a
> derivative thereof so that it can be implemented before Feature Complete. The
> absence of any replacement or alternative for
> sun.reflect.Reflection#getCallerClass(int) will be a serious issue in Java 8
> that will cause hardships for many projects.
>
> [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018049.html
> [2]
> http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018349.html,
> http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/019098.html
> [3] http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/018855.html