有一个常见的场景,咱们在开发过程中,要配置很多的本地 host,以实现测试环境一些资源的拜访,那么其它人参加进来开发的话,也得在本人电脑上配置,这样既麻烦,又容易出错。那么能不能把这些配置,写到 project 中去实现呢,这样每个人本地的 /etc/hosts 文件中会很洁净,能够随时 clone 启动调试而不需任何配置。答案是必定的,那么接下来咱们就应用 java 反射的形式来实现。咱们次要操作的对象就是 java.net.InetAddress, 能够从源码中看到,该类中有两个外围的变量 cache 和 expirySet。
// mapping from host name to Addresses - either NameServiceAddresses (while
// still being looked-up by NameService(s)) or CachedAddresses when cached
private static final ConcurrentMap<String, Addresses> cache =
new ConcurrentHashMap<>();
// CachedAddresses that have to expire are kept ordered in this NavigableSet
// which is scanned on each access
private static final NavigableSet<CachedAddresses> expirySet =
new ConcurrentSkipListSet<>();
这里会有个疑难,为啥还有个 expirySet,这个问题也能够通过源码失去解决,即为了删除生效的条目。具体含意能够通过浏览正文进行了解。
// remove expired addresses from cache - expirySet keeps them ordered
// by expiry time so we only need to iterate the prefix of the NavigableSet...
long now = System.nanoTime();
for (CachedAddresses caddrs : expirySet) {
// compare difference of time instants rather than
// time instants directly, to avoid possible overflow.
// (see System.nanoTime() recommendations...)
if ((caddrs.expiryTime - now) < 0L) {
// ConcurrentSkipListSet uses weakly consistent iterator,
// so removing while iterating is OK...
if (expirySet.remove(caddrs)) {
// ... remove from cache
cache.remove(caddrs.host, caddrs);
}
} else {
// we encountered 1st element that expires in future
break;
}
}
接下来就是如何来实现反射增加 dns 条目了, 本例中基于 java17 实现,其它版本会有相应的变动。
Class<?> cachedAddresses_Class = Class.forName("java.net.InetAddress$CachedAddresses");
Constructor<?> constructor = cachedAddresses_Class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Object o = constructor.newInstance(host, toInetAddressArray(host, ip), Long.MAX_VALUE);
Field cacheField = InetAddress.class.getDeclaredField("cache");
cacheField.setAccessible(true);
ConcurrentMap<String, Object> cm = (ConcurrentMap<String, Object>) cacheField.get(null);
cm.put(host, o);
Field expirySetField = InetAddress.class.getDeclaredField("expirySet");
expirySetField.setAccessible(true);
ConcurrentSkipListSet<Object> cs = (ConcurrentSkipListSet<Object>) expirySetField.get(null);
cs.add(o);
这样的话,就能够本人封装一下,比方 dns 条目都写在一个文件中,编译打包的时候,按 profile 配置决定是否加载。