메소드의 호출자를 찾아야합니다. 스택 트레이스 또는 반사를 사용할 수 있습니까?
답변
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
Javadocs에 따르면 :
배열의 마지막 요소는 스택의 맨 아래를 나타내며 시퀀스에서 가장 최근의 메소드 호출입니다.
A는 StackTraceElement
이 getClassName()
, getFileName()
, getLineNumber()
와 getMethodName()
.
원하는 인덱스를 결정하기 위해 실험해야 할 것입니다 (아마도 stackTraceElements[1]
또는 [2]
).
답변
대체 솔루션은 이 개선 요청에 대한 주석에서 찾을 수 있습니다 . getClassContext()
사용자 정의 방법을 사용하며 SecurityManager
스택 추적 방법보다 빠릅니다.
다음 프로그램은 여러 가지 제안 된 방법의 속도를 테스트합니다 (가장 흥미로운 것은 내부 클래스에 있습니다) SecurityManagerMethod
).
/**
* Test the speed of various methods for getting the caller class name
*/
public class TestGetCallerClassName {
/**
* Abstract class for testing different methods of getting the caller class name
*/
private static abstract class GetCallerClassNameMethod {
public abstract String getCallerClassName(int callStackDepth);
public abstract String getMethodName();
}
/**
* Uses the internal Reflection class
*/
private static class ReflectionMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
}
public String getMethodName() {
return "Reflection";
}
}
/**
* Get a stack trace from the current thread
*/
private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
}
public String getMethodName() {
return "Current Thread StackTrace";
}
}
/**
* Get a stack trace from a new Throwable
*/
private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return new Throwable().getStackTrace()[callStackDepth].getClassName();
}
public String getMethodName() {
return "Throwable StackTrace";
}
}
/**
* Use the SecurityManager.getClassContext()
*/
private static class SecurityManagerMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return mySecurityManager.getCallerClassName(callStackDepth);
}
public String getMethodName() {
return "SecurityManager";
}
/**
* A custom security manager that exposes the getClassContext() information
*/
static class MySecurityManager extends SecurityManager {
public String getCallerClassName(int callStackDepth) {
return getClassContext()[callStackDepth].getName();
}
}
private final static MySecurityManager mySecurityManager =
new MySecurityManager();
}
/**
* Test all four methods
*/
public static void main(String[] args) {
testMethod(new ReflectionMethod());
testMethod(new ThreadStackTraceMethod());
testMethod(new ThrowableStackTraceMethod());
testMethod(new SecurityManagerMethod());
}
private static void testMethod(GetCallerClassNameMethod method) {
long startTime = System.nanoTime();
String className = null;
for (int i = 0; i < 1000000; i++) {
className = method.getCallerClassName(2);
}
printElapsedTime(method.getMethodName(), startTime);
}
private static void printElapsedTime(String title, long startTime) {
System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
}
}
Java 1.6.0_17을 실행하는 2.4GHz Intel Core 2 Duo MacBook의 출력 예 :
Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.
내부 반사 방법은 다른 방법 보다 훨씬 빠릅니다. 새로 작성된 스택 추적을 Throwable
얻는 것이 현재 스택을 얻는 것보다 빠릅니다 Thread
. 그리고 호출자 클래스를 찾는 비 내부적 방법 중 사용자 정의 SecurityManager
가 가장 빠른 것 같습니다.
최신 정보
으로 lyomi는 에서 지적 이 댓글sun.reflect.Reflection.getCallerClass()
방법은 자바 7 업데이트 40에서 기본적으로 사용하지 않도록 설정하고 더 이것에 대해 자바 8. 읽기 완전히 제거 된 자바 버그 데이터베이스에서이 문제 .
업데이트 2
으로 zammbi이 발견했다, 오라클이되었다 변경의 철회하도록 강요 을 제거sun.reflect.Reflection.getCallerClass()
. Java 8에서는 여전히 사용할 수 있지만 더 이상 사용되지 않습니다.
업데이트 3
3 년 후 : 현재 JVM으로 타이밍 업데이트.
> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.
답변
this
메서드에 참조를 전달하지 않으려는 것처럼 들립니다 . 전달 this
은 현재 스택 추적을 통해 호출자를 찾는 것보다 낫습니다. 더 많은 OO 디자인으로 리팩토링하는 것이 더 좋습니다. 발신자를 알 필요는 없습니다. 필요한 경우 콜백 객체를 전달하십시오.
답변
Java 9-JEP 259 : 스택 워킹 API
JEP 259 는 스택 추적을위한 효율적인 표준 API를 제공하여 스택 추적의 정보를 쉽게 필터링하고 지연 액세스 할 수 있습니다. 스택 워킹 API 이전에는 스택 프레임에 액세스하는 일반적인 방법은 다음과 같습니다.
Throwable::getStackTrace
및Thread::getStackTrace
배열을 반환
StackTraceElement
각 스택 트레이스 요소의 클래스 이름과 메소드 이름을 포함하는 개체.
SecurityManager::getClassContext
보호 된 방법으로
SecurityManager
서브 클래스가 클래스 컨텍스트에 액세스 입니다.
sun.reflect.Reflection::getCallerClass
어쨌든 사용해서는 안되는 JDK 내부 메소드
이 API를 사용하면 일반적으로 비효율적입니다.
이러한 API를 사용하려면 VM에서 전체 스택의 스냅 샷을 열심히 캡처해야하며 전체 스택을 나타내는 정보를 반환합니다. 호출자가 스택의 최상위 몇 프레임에만 관심이있는 경우 모든 프레임을 검사하는 비용을 피할 수있는 방법이 없습니다.
즉시 발신자 클래스를 찾으려면 먼저 StackWalker
:
StackWalker walker = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
그런 다음 getCallerClass()
:
Class<?> callerClass = walker.getCallerClass();
또는 s와는 제 1 선행을 얻을 :walk
StackFrame
StackFrame
walker.walk(frames -> frames
.map(StackWalker.StackFrame::getDeclaringClass)
.skip(1)
.findFirst());
답변
원 라이너 :
Thread.currentThread().getStackTrace()[2].getMethodName()
2를 1로 바꿔야 할 수도 있습니다.
답변
이 방법은 똑같은 작업을 수행하지만 더 간단하고 약간 더 성능이 좋으며 리플렉션을 사용하는 경우 해당 프레임을 자동으로 건너 뜁니다. 유일한 문제는 JRockit 1.4-> 1.6의 런타임 클래스에 포함되어 있지만 Sun 이외의 JVM에는 없을 수 있다는 것입니다. ( 공개 수업 이 아닙니다 ).
sun.reflect.Reflection
/** Returns the class of the method <code>realFramesToSkip</code>
frames up the stack (zero-based), ignoring frames associated
with java.lang.reflect.Method.invoke() and its implementation.
The first frame is that associated with this method, so
<code>getCallerClass(0)</code> returns the Class object for
sun.reflect.Reflection. Frames associated with
java.lang.reflect.Method.invoke() and its implementation are
completely ignored and do not count toward the number of "real"
frames skipped. */
public static native Class getCallerClass(int realFramesToSkip);
지금까지 무엇을 한 realFramesToSkip
값이되어야한다, 태양 1.5 및 1.6 VM 버전 java.lang.System
, getCallerClass () 호출라는 패키지 보호 방법이 sun.reflect.Reflection.getCallerClass(3)
있지만, 헬퍼 클래스의 추가 프레임이 있기 때문에 내 도우미 유틸리티 클래스에 나는 4를 사용 기도.
답변
/**
* Get the method name for a depth in call stack. <br />
* Utility function
* @param depth depth in the call stack (0 means current method, 1 means call method, ...)
* @return method name
*/
public static String getMethodName(final int depth)
{
final StackTraceElement[] ste = new Throwable().getStackTrace();
//System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
return ste[ste.length - depth].getMethodName();
}
예를 들어, 디버그 목적으로 호출 메소드 행을 얻으려고 시도하면 해당 정적 메소드를 코딩하는 Utility 클래스를 통과해야합니다.
지나야 (이전의 Java1.4 코드, 잠재적 인 StackTraceElement 사용법을 보여주기 위해)
/**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
* From the Stack Trace.
* @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
*/
public static String getClassMethodLine()
{
return getClassMethodLine(null);
}
/**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
*/
public static String getClassMethodLine(final Class aclass)
{
final StackTraceElement st = getCallingStackTraceElement(aclass);
final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
+")] <" + Thread.currentThread().getName() + ">: ";
return amsg;
}
/**
* Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
* Stored in array of the callstack. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
* @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
*/
public static StackTraceElement getCallingStackTraceElement(final Class aclass)
{
final Throwable t = new Throwable();
final StackTraceElement[] ste = t.getStackTrace();
int index = 1;
final int limit = ste.length;
StackTraceElement st = ste[index];
String className = st.getClassName();
boolean aclassfound = false;
if(aclass == null)
{
aclassfound = true;
}
StackTraceElement resst = null;
while(index < limit)
{
if(shouldExamine(className, aclass) == true)
{
if(resst == null)
{
resst = st;
}
if(aclassfound == true)
{
final StackTraceElement ast = onClassfound(aclass, className, st);
if(ast != null)
{
resst = ast;
break;
}
}
else
{
if(aclass != null && aclass.getName().equals(className) == true)
{
aclassfound = true;
}
}
}
index = index + 1;
st = ste[index];
className = st.getClassName();
}
if(resst == null)
{
//Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies
throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
}
return resst;
}
static private boolean shouldExamine(String className, Class aclass)
{
final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
return res;
}
static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
{
StackTraceElement resst = null;
if(aclass != null && aclass.getName().equals(className) == false)
{
resst = st;
}
if(aclass == null)
{
resst = st;
}
return resst;
}