SharedSecrets深入理解

前言

最近在看JDK的一些源码,发现一些类总是会使用到SharedSecrets这个类,发现这不是JDK API里面的类,原来在dt.jar包里面。但是看了源码之后总觉得不是很明白,此类其实没有太多的注释说明。http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/sun/misc/SharedSecrets.java 这个就是这个类的内容。一开始没有懂这里面的意思,那么现在我们来深入理解一下。

从属性开始

我们来看一下这个类的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SharedSecrets {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static JavaUtilJarAccess javaUtilJarAccess;
private static JavaLangAccess javaLangAccess;
private static JavaIOAccess javaIOAccess;
private static JavaNetAccess javaNetAccess;
private static JavaNetHttpCookieAccess javaNetHttpCookieAccess;
private static JavaNioAccess javaNioAccess;
private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess;
private static JavaSecurityProtectionDomainAccess javaSecurityProtectionDomainAccess;
private static JavaSecurityAccess javaSecurityAccess;
private static JavaUtilZipFileAccess javaUtilZipFileAccess;
private static JavaAWTAccess javaAWTAccess;

然后再去看看具体方法,发现都是这个类的getter和setter方法,具体看起来是没有啥可理解的。那么具体是什么意思呢?

解释

那么我们来解释一下,就看一下注释里面的说明。

“共享私有”库,这是在其他包中调用私有实现方法的一种机制,而不用使用反射。好了就解释这么多,下面是英文原文。

/* A repository of “shared secrets”, which are a mechanism for
calling implementation-private methods in another package without
using reflection. A package-private class implements a public
interface and provides the ability to call package-private methods
within that package; the object implementing that interface is
provided through a third package to which access is restricted.
This framework avoids the primary disadvantage of using reflection
for this purpose, namely the loss of compile-time checking.
/

示例

可能上面的说明还是比较空洞,不知道要表达什么意思,我们来看一些具体的示例就好了。

在JAAS的ProtectionDomain类中有两个地方使用了SharedSecrets,我们来看看具体是怎么使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static {
// Set up JavaSecurityAccess in SharedSecrets
// 在SharedSecrets设置好JavaSecurityAccess
// 设置java安全访问,这里是使用匿名实现类实现的。
// 一直在找JavaSecurityAccess的实现类,这里有一个,居然是匿名类难怪找不到。
SharedSecrets.setJavaSecurityAccess(
new JavaSecurityAccess() {
// 执行交叉特权
public <T> T doIntersectionPrivilege(
PrivilegedAction<T> action,
final AccessControlContext stack,
final AccessControlContext context)
{
if (action == null) {
throw new NullPointerException();
}
return AccessController.doPrivileged(
action,
new AccessControlContext(
stack.getContext(), context).optimize()
);
}

// 执行交叉特权
public <T> T doIntersectionPrivilege(
PrivilegedAction<T> action,
AccessControlContext context)
{
return doIntersectionPrivilege(action,
AccessController.getContext(), context);
}
}
);
}

static {
SharedSecrets.setJavaSecurityProtectionDomainAccess(
// 设置默认的JavaSecurityProtectionDomainAccess
new JavaSecurityProtectionDomainAccess() {
public ProtectionDomainCache getProtectionDomainCache() {
return new ProtectionDomainCache() {
private final Map<Key, PermissionCollection> map =
Collections.synchronizedMap
(new WeakHashMap<Key, PermissionCollection>());
public void put(ProtectionDomain pd,
PermissionCollection pc) {
map.put((pd == null ? null : pd.key), pc);
}
public PermissionCollection get(ProtectionDomain pd) {
return pd == null ? map.get(null) : map.get(pd.key);
}
};
}
});
}

是的这个类中有两个静态块,用于实例化一些基础的类库。我们来看一下JavaSecurityProtectionDomainAccess和JavaSecurityAccess这两个接口,发现找不到,但是在dt.jar中可以找到;接着我尝试去找实现类发现也找不到,但是终于在这里我找到了相应的实现类,可谓不容易,不过也让我彻底明白了,这个类的实际意义,我想大家也应该明白这里面的作用了吧!很棒的设计,但是代码隐藏太深,如果不是去看到这个示例,我可能永远都不理解这个类的用意。

我想这个类的具体作用还是要从属性出发,发现很多属性都和Java的security相关。

不管这么多了,这个类的设计方式值得我们学习,但是会使代码难于理解。

JavaLangAccess示例

我也在网上看到这样的实例,挺好玩的。使用JavaLangAccess和SharedSecrets来获取JVM中的实例。

SharedSecrets和JavaLangAccess的作用

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.apache.ibatis.logging;

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

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();
}

执行结果,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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可以获取栈帧中的所有实例对象的类名称,接下来我们需要剔除掉不可能是调用类的名字。

总结

我们发现SharedSecrets这个类挺有趣的,但是注意这个类只是工具,具体的实现都是通过属性来完成,而属性的填充可能是JVM在加载类的时候填入的,所以只要了解接口的作用,我们只要取得里面的接口实例来做一些我们觉得好玩的事情。这就是这个类所不同的地方,凡是此类不能防止set方法只执行一次,所以要在setter方法中做限制,我们发现有些是做了限制的,有些没有。我觉得java8引入函数式编程之后,这个方式或许应该被替代了。

参考和引用

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