Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

最近有个小需求,要把我们一个 springboot 应用的 request mapping 给导出来,这么说已经是转化过了的,应该是要整理这个应用所有的接口路径,比如我有一个 api.baidu1.com 作为接口域名,然后这个域名下有很多个接口提供服务,这些接口都是写在一个 springboot 应用里,如果本身有网关管理这些接口转发的其实可以通过网关的数据输出,这里就介绍下通过代码来获取

1
2
3
4
5
6
7
8
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
Set<String> urlSet = entry.getKey().getPatternsCondition().getPatterns();
for (String url : urlSet) {
System.out.println(url);
}
}

第一行

逐行来解析下,第一行就是从上下文中获取 RequestMappingHandlerMapping 这个 bean,

第二行

然后调用了 getHandlerMethods,
这里面具体执行了

1
2
3
4
5
6
7
8
9
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

前后加了锁,重要的就是从 mappingRegistry 中获取 mappings, 这里获取的就是
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getMappings 具体的代码

1
2
3
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}

而这个 mappingLookup 再来看下

1
2
3
4
5
class MappingRegistry {

private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

可以看到就是在 MappingRegistry 中的一个变量,至于这个变量是怎么来的,简单的考虑下 springboot 处理请求的流程,就是从 Mapping 去找到对应的 Handler,所以就需要提前将这个对应关系存到这个变量里,
具体可以看这 org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register

1
2
3
4
5
6
7
8
9
10
11
12
13
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);

就是在这里会把 mapping 和 handleMethod 对应关系存进去

第三行

这里拿的是上面的 map 里的 key,也就是 RequestMappingInfo 也就是 org.springframework.web.servlet.mvc.method.RequestMappingInfo
而真的 url 就存在 org.springframework.web.servlet.mvc.condition.PatternsRequestCondition
最终这里面的patterns就是我们要的路径

1
2
3
4
5
6
public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {

private final static Set<String> EMPTY_PATH_PATTERN = Collections.singleton("");


private final Set<String> patterns;

写到这下一篇是不是可以介绍下 mapping 的具体注册逻辑

再一次环境部署是发现了个问题,就是在请求微信 https 请求的时候,出现了个错误
No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
一开始以为是环境问题,从 oracle 的 jdk 换成了基于 openjdk 的底包,没有 javax 的关系,
完整的提示包含了 javax 的异常
java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
后面再看了下,是不是也可能是证书的问题,然后就去找了下是不是证书相关的,
可以看到在 /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security 路径下的 java.security
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA,
而正好在我们代码里 createSocketFactory 的时候使用了 TLSv1 这个证书协议

1
2
3
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
return new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());

所以就有两种方案,一个是使用更新版本的 TLS 或者另一个就是使用比较久的 jdk,这也说明其实即使都是 jdk8 的,不同的小版本差异还是会有些影响,有的时候对于这些错误还是需要更深入地学习,不能一概而之认为就是 jdk 用的是 oracle 还是 openjdk 的,不同的错误可能就需要仔细确认原因所在。

最近本来是在读《舞舞舞》,然后看到有介绍说,这个跟《寻羊历险记》是有情节上的关联,所以就先去看了《寻羊历险记》,《寻羊历险记》也是村上春树的第一本成规模的长篇小说,也可以认为是《舞舞舞》的前篇。
最开始这个情节跟之前的刺杀骑士团长还是哪本有点类似,都有跟老婆离婚了,主角应该是个跟朋友一起开翻译社的,后面也开始做广告相关的,做到了经济收益还不错的阶段,也有一些挺哲学的对话,朋友觉得这么赚钱不地道(可能也是觉得这样忘了初心),在离婚以后又结交了一个耳朵很好看的女友,这个女友也是个比较抽象的存在,描述中给人感觉是一个外貌很普通的女孩,但是耳朵漂亮的惊为天人,不知道是不是有什么隐喻,感觉现实中没见过这样的人,女友平时把耳朵遮起来不轻易露出来,只有在跟主角做爱的时候才露出来
主体剧情是因为男主在广告中用了一张一位叫“鼠”的朋友寄给他的一张包含一只特殊的羊的照片,就有个政界大佬的秘书找过来,逼迫主角要找到照片上的羊,这只羊背上有星纹,基本不可能属于在日本当时可能存在的羊的品种,因为这位政界大佬快病危了,所以要求主角必须在一个月内找到这只羊,因为这里把这只神秘的羊塑造成神一样的存在,这位政界大佬在年轻时被这只羊上身,后面就开始在政治事业上大展宏图,就基本成了能左右整个日本走向的巨佬,但近期随着巨佬,身体状态慢慢变差,这只羊就从他身上消失了,所以秘书要主角必须找到这只羊,不然基本会让主角的翻译社完蛋,这样主角就会面临破产赔偿等严重后果,只是说主角本来也一直是这种丧气存在,再说这么茫茫人海找个人都难,还要找这么一只玄乎的不太可能真实存在的羊,所以基本是想要放弃的,结果刚才说的耳朵很漂亮的女友却有着神乎其神的直觉,就觉得能找到,然后他们就踏上了巡羊的旅程,一开始到了札幌,寻找一无所获,然后神奇的是女友推荐一定要住的海豚宾馆,恰恰是一切线索的源头,宾馆老板的父亲正好是羊博士,在年轻的时候被羊上身过,后面离开后就去了那位巨佬身上,让巨佬成了真正控制日本的地下帝国的王,跟羊博士咨询后知道羊可能在十二瀑镇的牧场出现过,所以一路找寻,发现其实这个牧场中有个别墅正好是主角朋友“鼠”的,到了别墅后出现了个神秘的羊男,这个羊男真的不太明白是怎么个存在,就是让主角的女友回去了,然后最后一个肉体承载着“鼠”的灵魂跟主角有了一次接触,主角呆在这个别墅过着百无聊赖的生活,在才到有一次羊男来跟他交流的时候其实是承载着鼠的灵魂,就觉得是不是一切都是在忽悠他,离一个月期限也越来越近了,而在发怒之后,“鼠”真的出现后,但是不是真的“鼠”,而是只有他的灵魂,因为“鼠”已经死了,因为不想被“羊”附身,成为羊壳,并且让主角设定好时钟后的装置后尽快下山,第二天主角离开上了火车后山上牧场就传来爆炸声,“鼠”已经跟羊同归于尽了,免得再有其他人被羊附身,主角也很难过,回到故乡在四周大海已经被填掉了的旧防波堤上大哭悼念逝去的“鼠”。
其实对于没什么时代概念或者对村上一直以来的观念不是特别敏感的,对这本书讲了啥有点云里雾里,后面看了一些简单的解释就是羊其实代表日本帝国主义和资本主义,可能是村上本人反帝国主义,军国主义和资本主义的一个表征,想来也有些道理,不然“鼠”的牺牲就感觉比较没意义,但是另一方面我的理解也可能是对自由的向往的表达,被羊控制,虽然可以成就“伟大”的事业,但是也丧失了自我。

Java 中最常见的一类问题或者说异常就是 NullPointerException,而往往这种异常我们在查看日志的时候都是根据打印出来的异常堆栈找到对应的代码以确定问题,但是有一种情况会出现一个比较奇怪的情况,虽然我们在日志中打印了异常

1
log.error("something error", e);

这样是可以打印出来日志,譬如 slf4j 就可以把异常作为最后一个参数传入

1
void error(String var1, Throwable var2);

有的时候如果不小心把异常通过占位符传入就可能出现异常被吞的情况,不过这点不是这次的重点
即使我们正常作为最后一个参数传入了,也会出现只打印出来

1
something error, java.lang.NullPointerException: null

这样就导致了如果我们这个日志所代表的异常包含的代码范围比较大的话,就不确定具体是哪一行出现的异常,除非对这些代码逻辑非常清楚,这个是为什么呢,其实这也是 jvm 的一种优化机制,像我们目前主要还在用的 jdk8中使用的 jvm 虚拟机 hotspot 的一个热点异常机制,对于这样一直出现的相同异常,就认为是热点异常,后面只会打印异常名,不再打印异常堆栈,这样对于 io 性能能够提供一定的优化保证,这个可以通过 jvm 启动参数
-XX:-OmitStackTraceInFastThrow 来关闭优化,这个问题其实在一开始造成了一定困扰,找不准具体是哪一行代码,不过在知道这个之后简单的重启也能暂时解决问题。

添加节点

添加节点非常简单,比如 app store 或者官网可以下载 mac 的安装包,

安装包直接下载可以在这里,下载安装完后还需要做一些处理,才能让 Tailscale 使用 Headscale 作为控制服务器。当然,Headscale 已经给我们提供了详细的操作步骤,你只需要在浏览器中打开 URL:http://<HEADSCALE_PUB_IP>:<HEADSCALE_PUB_PORT>/apple,记得端口替换成自己的,就会看到这样的说明页

image

然后对于像我这样自己下载的客户端安装包,也就是standalone client,就可以用下面的命令

defaults write io.tailscale.ipn.macsys ControlURL http://<HEADSCALE_PUB_IP>:<HEADSCALE_PUB_PORT> 类似于 Windows 客户端需要写入注册表,就是把控制端的地址改成了我们自己搭建的 headscale 的,设置完以后就打开 tailscale 客户端右键点击 login,就会弹出一个浏览器地址

image

按照这个里面的命令去 headscale 的机器上执行,注意要替换 namespace,对于最新的 headscale 已经把 namespace 废弃改成 user 了,这点要注意了,其他客户端也同理,现在还有个好消息,安卓和 iOS 客户端也已经都可以用了,后面可以在介绍下局域网怎么部分打通和自建 derper。

0%