一、修改负向缓存过期时间
步骤:
1. 删除{javaHome}/lib/security/java.security里的键值对networkaddress.cache.negative.ttl
2. InetAddressCachePolicy.setNegativeIfNotSet(int var0 ) // var0就是过期时间
下面通过源码解释为什么可以这样做。
public Cache put(String host, InetAddress[] addresses) {
.....
// 过期时间的策略
int policy = getPolicy();
....
long expiration;
if (policy == InetAddressCachePolicy.FOREVER) {
expiration = -1;
} else {
//设置缓存过期时间
expiration = System.currentTimeMillis() + (policy * 1000);
}
CacheEntry entry = new CacheEntry(addresses, expiration);
cache.put(host, entry);
return this;
......
}
从上面的代码可以看出。当将host存入缓存时,会先获取当前缓存的过期时间策略policy,然后与当前时间戳相加作为过期时间存入cache中。
再看看getPolicy方法。
private int getPolicy() {
// 类型是正向缓存
if (type == Type.Positive) {
return InetAddressCachePolicy.get();
//负向缓存
} else {
// 实际上调用的是
return InetAddressCachePolicy.getNegative();
}
}
下面是InetAddressCachePolicy.getNegative()的代码
public static synchronized int getNegative() {
return negativeCachePolicy;
}
那是当我们获取过期时间策略policy实际上获取的是InetAddressCachePolicy的类变量negativeCachePolicy。
再看看静态公共方法 setNegativeIfNotSet
public static synchronized void setNegativeIfNotSet(int var0) {
//propertyNegativeSet为false才可以设置成功
if (!propertyNegativeSet) {
negativeCachePolicy = var0;
}
}
从上面的代码可以得出结论要自由地设置负向缓存过期时间就得使propertyNegativeSet =false;
propertyNegativeSe是如何初始化的
static {
....................
var0 = (Integer)AccessController.doPrivileged(new PrivilegedAction<Integer>() {
public Integer run() {
String var1;
try {
// Security中获取
var1 = Security.getProperty("networkaddress.cache.negative.ttl");
if (var1 != null) {
return Integer.valueOf(var1);
}
} catch (NumberFormatException var3) {
}
try {
// 从系统属性中获取
var1 = System.getProperty("sun.net.inetaddr.negative.ttl");
if (var1 != null) {
return Integer.decode(var1);
}
} catch (NumberFormatException var2) {
}
return null;
}
});
// 如果不为nul,就设置propertyNegativeSet为true
if (var0 != null) {
negativeCachePolicy = var0;
if (negativeCachePolicy < 0) {
negativeCachePolicy = -1;
}
propertyNegativeSet = true;
}
}
从上面的源码可以看出如果要让propertyNegativeSet为false,那么Security.getProperty("networkaddress.cache.negative.ttl")的值和System.getProperty("sun.net.inetaddr.negative.ttl")的值都必须为null,而我们没有设置系统属性sun.net.inetaddr.negative.ttl
,那么剩下的就是Security.getProperty("networkaddress.cache.negative.ttl")了。
下面在看看Security的部分源码
package java.security;
public final class Security {
/* Are we debugging? -- for developers */
private static final Debug sdebug =
Debug.getInstance("properties");
/* The java.security properties */
private static Properties props;
........
static {
// doPrivileged here because there are multiple
// things in initialize that might require privs.
// (the FileInputStream call and the File.exists call,
// the securityPropFile call, etc)
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
initialize();
return null;
}
});
}
private static void initialize() {
props = new Properties();
boolean loadedProps = false;
boolean overrideAll = false;
// first load the system properties file
// to determine the value of security.overridePropertiesFile
// 文件名java.security
File propFile = securityPropFile("java.security");
.......
// 将流转化为props
props.load(is);
}
.........
private static File securityPropFile(String filename) {
// maybe check for a system property which will specify where to
// look. Someday.
//分隔符
String sep = File.separator;
//从{javaHome}/lib/security/java.security中读取
return new File(System.getProperty("java.home") + sep + "lib" + sep +
"security/" + sep + filename);
}
...........
// 从props中获取值
public static String getProperty(String key) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SecurityPermission("getProperty."+
key));
}
String name = props.getProperty(key);
if (name != null)
name = name.trim(); // could be a class name with trailing ws
return name;
}
}
上面Security的源码可以看出,当我们调用getProperty时实际上是获取类变量props里对应key的值,而props是从{javaHome}/lib/security/java.security中读取键值对封装而成的,那么只要我们删除了{javaHome}/lib/security/java.security这个文件中的key为networkaddress.cache.negative.ttl的那对键值对就行了。
二、添加自定义的NameService
先上实现步骤:
1. 在META-INF/services下创建一个sun.net.spi.nameservice.NameServiceDescriptor文件
2. 在文件内的一行写上自定义dns服务发现类的全限定类名
3. 自定义dns实现类重写NameServiceDescriptor接口的三个方法,getProviderName返回"XYDnsNameService",getType返回"dns",createNameService返回一个自定义NameService实现类的实例。
4. 自定义XYDnsNameService实现NameService接口的三个方法。
5. 通过代码设置自定义nameservice为默认dns解析器:System.setProperty("sun.net.spi.nameservice.provider.1", "dns,XYDnsNameService");
下面通过源码分析:
当我们使用默认dns解析百度的域名时,方法调用栈如下
InetAddress.getByName(“www.baidu.com”)
InetAddress.getAllByName(“www.baidu.com”)[0]
InetAddress.getAllByName(“www.baidu.com”, null)
InetAddress.getAllByName0(“www.baidu.com”, null, true)
InetAddress.getCachedAddress(“www.baidu.com”)
InetAddress.getCachedAddress(“www.baidu.com”) 从缓存中查不到对应的数据时,就会执行下面的语句
private static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check)
throws UnknownHostException {
........
if (addresses == null) {
addresses = getAddressesFromNameService(host, reqAddr);
}
.........
return addresses.clone();
再看看getAddressesFromNameService方法
/* Used to store the name service provider */
private static List<NameService> nameServices = null;
for (NameService nameService : nameServices) {
try {
addresses = nameService.lookupAllHostAddr(host);
success = true;
break;
} catch (UnknownHostException uhe) {
if (host.equalsIgnoreCase("localhost")) {
InetAddress[] local = new InetAddress[] { impl.loopbackAddress() };
addresses = local;
success = true;
break;
}
else {
addresses = unknown_array;
success = false;
ex = uhe;
}
}
}
getAddressesFromNameService方法遍历nameService(类静态属性),调用nameService的lookupAllHostAddr对域名进行解析。这意味着我们可以通过添加自定义的nameService并重写其lookupAllHostAddr方法来实现自定义的dns解析逻辑。
那么,现在的问题就是向类属性nameService添加自定义nameService
下面的静态代码块的作用是初始化nameService
static {
// create the impl
impl = InetAddressImplFactory.create();
// get name service if provided and requested
String provider = null;;
String propPrefix = "sun.net.spi.nameservice.provider.";
int n = 1;
nameServices = new ArrayList<NameService>();
// 从系统属性属性中获取key为sun.net.spi.nameservice.provider.x的配置的值
provider = AccessController.doPrivileged(
new GetPropertyAction(propPrefix + n));
//值不为空
while (provider != null) {
NameService ns = createNSProvider(provider);
if (ns != null)
nameServices.add(ns);
n++;
provider = AccessController.doPrivileged(
new GetPropertyAction(propPrefix + n));
}
// if not designate any name services provider,
// create a default one
if (nameServices.size() == 0) {
NameService ns = createNSProvider("default");
nameServices.add(ns);
}
}
可以看出,如何系统配置sun.net.spi.nameservice.provider.x(x >= 1)不为空的话,就会执行下面的createNSProvider方法
//createNSProvider方法中
final String providerName = provider;
try {
nameService = java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<NameService>() {
public NameService run() {
Iterator<NameServiceDescriptor> itr =
// spi加载 META-INF/services/sun.net.spi.nameservice.NameServiceDescriptor里写的全限定包名的类的接口实现类~~~~
ServiceLoader.load(NameServiceDescriptor.class)
.iterator();
while (itr.hasNext()) {
// 如果providerName和实现类的getType+,+getProviderName拼接的字符串一致时,调用该实现类的createNameService方法创建一个NameService实例。
NameServiceDescriptor nsd = itr.next();
if (providerName.
equalsIgnoreCase(nsd.getType()+","
+nsd.getProviderName())) {
try {
return nsd.createNameService();
} catch (Exception e) {
e.printStackTrace();
System.err.println(
"Cannot create name service:"
+providerName+": " + e);
}
}
}
return null;
}
}
所以我们在设置System.setProperty("sun.net.spi.nameservice.provider.1", "dns,xxxDnsNameService")后,xxxNameServiceDescriptor的getProviderName方法返回"xxxDnsNameService",getType方法返回"dns"。createNameService方法放回一个自定义的NameService实例,这样就实现了自定义dns NameService。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。