스택 추적 또는 반사를 사용하여 메서드를 호출한 사람을 찾으려면 어떻게 해야 하는가?
나는 메소드의 발신자를 찾아야 한다.스택트레이스나 반성을 통해 가능한가?
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
자바도크 족에 따르면:
배열의 마지막 요소는 스택의 바닥을 나타내며, 이는 시퀀스에서 가장 최근의 방법 호출이다.
A StackTraceElement
가지다getClassName()
getFileName()
getLineNumber()
그리고getMethodName()
.
원하는 인덱스(아마도)를 결정하기 위해 실험을 해야 할 것이다.stackTraceElements[1]
또는[2]
).
참고: Java 9 이상을 사용하는 경우 다음을 사용하십시오.StackWalker.getCallerClass()
알리 데하니의 대답에 묘사된 바와 같이
아래의 다른 방법의 비교는 대부분 역사적 이유로 흥미롭다.
대체 해결책은 이 개선 요청에 대한 설명에서 찾을 수 있다.그것은 그것을 사용한다.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
가장 빠른 것 같다.
갱신하다
리오미가 이 논평에서 지적했듯이sun.reflect.Reflection.getCallerClass()
메서드는 Java 7 업데이트 40에서 기본적으로 비활성화되었으며 Java 8에서 완전히 제거되었다.이 문제에 대한 자세한 내용은 Java 버그 데이터베이스에서 확인하십시오.
업데이트 2
zammbi가 찾아냈듯이 Oracle은 그 변화를 제거하지 않을 수 없었다.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.
Java 9 - JEP 259: Stack-Walking API
JEP 259는 스택 트레이스에서 정보를 쉽게 필터링하고 느리게 액세스할 수 있는 스택 보행을 위한 효율적인 표준 API를 제공한다.Stack-Walking API 이전에 스택 프레임에 액세스하는 일반적인 방법은 다음과 같다.
Throwable::getStackTrace
그리고Thread::getStackTrace
한 줄로 늘어놓다StackTraceElement
각 스택 정렬 요소의 클래스 이름과 메서드 이름을 포함하는 객체.
SecurityManager::getClassContext
것은 로서 입니다.SecurityManager
클래스 컨텍스트에 액세스하기 위한 하위 클래스.JDK-영문
sun.reflect.Reflection::getCallerClass
어쨌든 사용해서는 안 되는 방법
이러한 API를 사용하는 것은 일반적으로 비효율적이다.
이러한 API를 사용하려면 VM이 전체 스택의 스냅샷을 열심히 캡처해야 하며, 전체 스택을 나타내는 정보를 반환해야 한다.전화를 건 사람이 스택의 상위 몇 개 프레임에만 관심이 있다면 모든 프레임을 검사하는 비용을 피할 방법이 없다.
즉시 호출자의 클래스를 찾으려면 먼저 a를 구하십시오.StackWalker
:
StackWalker walker = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
그럼 어느 쪽이든 전화해서getCallerClass()
:
Class<?> callerClass = walker.getCallerClass();
또는walk
그StackFrame
의 첫 s를을 .StackFrame
:
walker.walk(frames -> frames
.map(StackWalker.StackFrame::getDeclaringClass)
.skip(1)
.findFirst());
대한 것 .this
방법에 따라패스this
현재 스택 추적을 통해 발신자를 찾는 것보다 훨씬 낫지좀 더 OO적인 디자인으로 리팩터링하는 것이 훨씬 더 좋다.전화를 건 사람을 알 필요는 없다.필요한 경우 콜백 개체를 전달하십시오.
Oneliner:
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
.5 및의 versions은 Sun 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();
}
예를 들어 디버그를 위해 호출 메서드 줄을 가져오려고 하면 다음과 같은 정적 메서드를 코딩하는 유틸리티 클래스를 통과해야 한다.
(이전의 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;
}
전에 이걸 한 적이 있다.새 예외를 만들어 던지지 않고 스택 추적을 잡고 스택 추적을 검사하면 된다.다른 답에 따르면, 그것은 매우 비용이 많이 든다. 즉, 그것을 긴밀하게 하지 마라.
성능이 크게 중요하지 않은 앱에서 로깅 유틸리티(버튼 클릭과 같은 동작에 결과를 빠르게 표시하는 한 성능은 전혀 중요하지 않음)를 위해 이전에도 해본 적이 있다.
스택 추적을 받기 전이었는데, 예외적으로 .printStackTrace()가 있어서 시스템을 리디렉션해야 했다.그러면 (새 예외()로) 인쇄StackTrace(); 리디렉션 시스템.뒷걸음질쳐 개울물을 파내다재밌는 거.
private void parseExceptionContents(
final Exception exception,
final OutputStream out)
{
final StackTraceElement[] stackTrace = exception.getStackTrace();
int index = 0;
for (StackTraceElement element : stackTrace)
{
final String exceptionMsg =
"Exception thrown from " + element.getMethodName()
+ " in class " + element.getClassName() + " [on line number "
+ element.getLineNumber() + " of file " + element.getFileName() + "]";
try
{
out.write((headerLine + newLine).getBytes());
out.write((headerTitlePortion + index++ + newLine).getBytes() );
out.write((headerLine + newLine).getBytes());
out.write((exceptionMsg + newLine + newLine).getBytes());
out.write(
("Exception.toString: " + element.toString() + newLine).getBytes());
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered while trying to write "
+ "StackTraceElement data to provided OutputStream.\n"
+ ioEx.getMessage() );
}
}
}
여기 내가 이 주제에 대한 힌트를 바탕으로 만든 코드의 일부가 있다.도움이 되길 바래.
(이 코드를 개선할 수 있는 제안은 자유롭게 해주십시오.)
카운터:
public class InstanceCount{
private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
private CounterInstanceLog counterInstanceLog;
public void count() {
counterInstanceLog= new counterInstanceLog();
if(counterInstanceLog.getIdHashCode() != 0){
try {
if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
}
counterInstanceLog.incrementCounter();
instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
}
(...)
}
그리고 그 목적은:
public class CounterInstanceLog{
private int idHashCode;
private StackTraceElement[] arrayStackTraceElements;
private int instanceCount;
private String callerClassName;
private StackTraceElement getProjectClasses(int depth) {
if(depth< 10){
getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
if(getCallerClassName().startsWith("com.yourproject.model")){
setStackTraceElements(Thread.currentThread().getStackTrace());
setIdHashCode();
return arrayStackTraceElements[depth];
}
//+2 because one new item are added to the stackflow
return getProjectClasses(profundidade+2);
}else{
return null;
}
}
private void setIdHashCode() {
if(getNomeClasse() != null){
this.idHashCode = (getCallerClassName()).hashCode();
}
}
public void incrementaContador() {
this.instanceCount++;
}
//getters and setters
(...)
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
class DBConnection {
String createdBy = null;
DBConnection(Throwable whoCreatedMe) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(os);
whoCreatedMe.printStackTrace(pw);
try {
createdBy = os.toString();
pw.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ThrowableTest {
public static void main(String[] args) {
Throwable createdBy = new Throwable(
"Connection created from DBConnectionManager");
DBConnection conn = new DBConnection(createdBy);
System.out.println(conn.createdBy);
}
}
OR
public static interface ICallback<T> { T doOperation(); }
public class TestCallerOfMethod {
public static <T> T callTwo(final ICallback<T> c){
// Pass the object created at callee to the caller
// From the passed object we can get; what is the callee name like below.
System.out.println(c.getClass().getEnclosingMethod().getName());
return c.doOperation();
}
public static boolean callOne(){
ICallback callBackInstance = new ICallback(Boolean){
@Override
public Boolean doOperation()
{
return true;
}
};
return callTwo(callBackInstance);
}
public static void main(String[] args) {
callOne();
}
}
다음 방법 사용:-
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
stackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
System.out.println(e.getMethodName());
메서드의 호출자 예:-
public class TestString {
public static void main(String[] args) {
TestString testString = new TestString();
testString.doit1();
testString.doit2();
testString.doit3();
testString.doit4();
}
public void doit() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
StackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
System.out.println(e.getMethodName());
}
public void doit1() {
doit();
}
public void doit2() {
doit();
}
public void doit3() {
doit();
}
public void doit4() {
doit();
}
}
'IT이야기' 카테고리의 다른 글
열거형 변수를 문자열로 변환하는 방법 (0) | 2022.05.12 |
---|---|
Java.lang.반사를 일으킬 수 있는 것.호출TargetException? (0) | 2022.05.12 |
Vue 조건부 v-on 이벤트 차단 한정자 (0) | 2022.05.12 |
Vuex 모듈의 형식 오류 "표현으로 호출될 때 클래스 장식가의 서명을 확인할 수 없음" (0) | 2022.05.12 |
Vuex 작업 vs 돌연변이 (0) | 2022.05.12 |