2

一、修改负向缓存过期时间

步骤:

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的那对键值对就行了。
image.png

二、添加自定义的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


临_择
7 声望4 粉丝

« 上一篇
Git常用操作
下一篇 »
理解枚举类型

引用和评论

0 条评论