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