使用JavaLangAccess和SharedSecrets来获取JVM中的实例

使用场景实例:

最近一段时间在mybatis框架的源码,看到框架的日志模块的时候,发现其集成了JDK的原生日志java.util.logging,于是我就去看了一下java.util.logging实现源码,在其中的LogRecord.java文件中看到其中有一段代码:

// Private method to infer the caller's class and method names
    private void inferCaller() {
        needToInferCaller = false;
        JavaLangAccess access = SharedSecrets.getJavaLangAccess();
        Throwable throwable = new Throwable();
        int depth = access.getStackTraceDepth(throwable);

        boolean lookingForLogger = true;
        for (int ix = 0; ix < depth; ix++) {
            // Calling getStackTraceElement directly prevents the VM
            // from paying the cost of building the entire stack frame.
            StackTraceElement frame =
                access.getStackTraceElement(throwable, ix);
            String cname = frame.getClassName();
            boolean isLoggerImpl = isLoggerImplFrame(cname);
            if (lookingForLogger) {
                // Skip all frames until we have found the first logger frame.
                if (isLoggerImpl) {
                    lookingForLogger = false;
                }
            } else {
                if (!isLoggerImpl) {
                    // skip reflection call
                    if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
                       // We've found the relevant frame.
                       setSourceClassName(cname);
                       setSourceMethodName(frame.getMethodName());
                       return;
                    }
                }
            }
        }
        // We haven't found a suitable frame, so just punt.  This is
        // OK as we are only committed to making a "best effort" here.
    }

这个函数的目的是推断调用日志log的类,被public String getSourceClassName()方法使用。通常来说一个方法在调用log时一般都会显式的给当前类的信息作为log的输入参数,当没有给定的时候,就需要日志框架来自己推测,这时候就是用到了上面这个函数。当然这个推测不能保证找到的调用类实例一定正确。


SharedSecrets和JavaLangAccess的作用

当我们需要对使用Log的类名进行的推断的时候,我们就需要知道JVM里面的实例对象了,这时候我们就需要使用到SharedSecretsJavaLangAccess,通过这两个类来获取Java栈帧中存储的类信息,然后进行挑选,从而找出调用的类。

接下来看一下SharedSecrets和JavaLangAccess的使用方式:

  • 测试环境:为了方便,在mybatis源码工程里面利用junit进行测试,创建新类testLang.java,也可以自己创建java工程进行测试。

测试代码:

package org.apache.ibatis.logging;

import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;

/**
 * Created by 茂升 on 2016/11/2.
 */
public class testLang {
    public void testPrint() {
        JavaLangAccess access = SharedSecrets.getJavaLangAccess();
        Throwable throwable = new Throwable();

        int depth = access.getStackTraceDepth(throwable);

        //输出JVM栈帧中的所有类实例
        for (int i = 0; i < depth; i++) {
            StackTraceElement frame = access.getStackTraceElement(throwable, i);
            System.out.println(frame);
        }
    }
}

在LogFactoryTest.java文件中添加方法,这里使用了junit:

@Test
  public void yumsTest() {
    //System.err.println("Yumaosheng TEST");
    testLang t = new testLang();
      t.testPrint();
  }

执行结果:

org.apache.ibatis.logging.testLang.testPrint(testLang.java:12)
org.apache.ibatis.logging.LogFactoryTest.yumsTest(LogFactoryTest.java:52)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
org.junit.runners.ParentRunner.run(ParentRunner.java:309)
org.junit.runner.JUnitCore.run(JUnitCore.java:160)
com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Process finished with exit code 0

我们会发现代码输出了所有JVM栈帧中的实例对象的类名。也就是说我们使用JavaLangAccess和SharedSecrets可以获取栈帧中的所有实例对象的类名称,接下来我们需要剔除掉不可能是调用类的名字。


剔除不可能的调用类

首先日志框架自身是不会调用的,所以先判断当前在栈中找到的类是不是日志框架自身,使用isLoggerImplFrame()方法(这个方法比较简单就不做过多解释了)。然后我们还要排除在获取栈帧过程中和日志框架使用过程中的额外引入的类,像java.lang.reflect.*和sun.reflect.*这些类都是额外引入的类,不会是调用类。剔除这些类以后,我们就找到了调用log的类。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值