2017-08-29-java-serialVersionID的作用

java-serialVersionID的作用

很多代码都有一句

private static final long serialVersionUID = 1L

这句是什么作用呢?暂且理解为:在java序列化和反序列化的时候作为判断是否能把这个对象进行反序列化。

聚个栗子

定义一个worker,有id,name,age三个属性,当然如果要序列化需要实现Serializable接口,下面这个Worker认为是版本1。

package test;

import java.io.Serializable;

public class Worker implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;
	private Integer age;
	/* 省略get/set方法 */

	@Override
	public String toString() {
		return "Worker [id=" + id + ", name=" + name + ", age=" + age + "]";
	}
}

新建两个对象,并保存到文件中

package test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class App {

	public static void main(String[] args) {
		Worker worker = new Worker();
		worker.setId(100);
		worker.setName("alien lin");
		worker.setAge(24);

		Worker worker2 = new Worker();
		worker2.setId(60);
		worker2.setName("jack ma ");
		worker2.setAge(66);
		try {
			File f = new File("d:/temp/a.txt");
			if (!f.exists()) {
				f.createNewFile();
			}
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
			oos.writeObject(worker);
			oos.writeObject(worker2);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

再从文件中读取出这两个对象

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class App2 {

	public static void main(String[] args) {
		File f = new File("d:/temp/a.txt");
		Object o = null;
		try {
			FileInputStream fis = new FileInputStream(f);
			ObjectInputStream ois = new ObjectInputStream(fis);
			// 用fis来判断是否已经读到结尾了。
			while (0 != fis.available() && null != (o = ois.readObject())) {
				System.out.println(((Worker) o).toString());
			}
			ois.close();
			fis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

结果:

Worker [id=100, name=alien lin, age=24]
Worker [id=60, name=jack ma , age=66]

现在我们在不改动Worker其他属性的情况下,把Worker的serialVersionUID改成2L,认为是版本2。如下

private static final long serialVersionUID = 2L;

然后再次运行上面的读取对象的代码,结果报了一个错

java.io.InvalidClassException: test.Worker; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
	at test.App2.main(App2.java:16)

根据提示我们知道,这两个冲突了,因为serialVersionUID的不同导致了读出对象失败。

  • 如果版本1和版本2兼容,即它们之间可以任意序列化反序列化,那么它们的serialVersionUID就应该相同。
    • 如版本2新增了一个属性,而这个属性不必一定有值。从而版本2的类能够从保存版本1的类的存储中读取版本1的对象,这样版本1和2就可以是兼容的。
  • 如果认为版本1和版本2之间不应该进行任意序列化反序列化,那么就不应该有相同的serialVersionUID。
    • 如版本2新增了一个属性,而这个属性从数据库/文件/网络中读取后要求必须有值,那么版本2就不能从保存版本1数据库/文件/网络中读取。此时两个版本的代码的serialVersionUID就要设置为不同才行。

下面新建一个版本3,新增了属性gender,去掉了属性age,但是serialVersionUID不变,说明版本3兼容版本1,它们之间可以任意序列化反序列化,但是由于属性的不对等,所以会造成数据的缺失。

package test;

import java.io.Serializable;

public class Worker implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;
	// private Integer age;
	private String gender;
	/* 省略get/set方法 */
	
	@Override
	public String toString() {
		return "Worker [id=" + id + ", name=" + name + ", gender=" + gender + "]";
	}
}

运行进行读取程序,结果如下。age缺失,genkder为空,

Worker [id=100, name=alien lin, gender=null]
Worker [id=60, name=jack ma , gender=null]

如果不指定serialVersionUID的值将如何?

如果我们不显式指定serialVersionUID的值,那么JVM会根据属性的生成一个serialVersionUID,如果我们更改了属性,添加了非private方法,那么serialVersionUID就不同了,就不能序列化了。

定义一个Person,有id,name属性及其get/set方法。注意该对象没有设置serialVersionUID的值

package test;

import java.io.Serializable;

public class Person implements Serializable {
	private Integer id;
	private String name;
	/* 省略get/set方法 */
}

使用Test1将两个Person对象存入文本中

package test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Test1 {

	public static void main(String[] args) {
		Person p = new Person();
		p.setId(1);
		p.setName("lxy");
		Person p1 = new Person();
		p1.setId(2);
		p1.setName("ddd");
		
		try {
			ObjectOutput oo = new ObjectOutputStream(
				new FileOutputStream("d:/logs/c.txt"));
			oo.writeObject(p);
			oo.writeObject(p1);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

使用Test2读出该对象。

package test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;

public class Test2 {
	
	public static void main(String[] args) throws ClassNotFoundException, FileNotFoundException, IOException {
		FileInputStream fis = new FileInputStream("d:/logs/c.txt");
		ObjectInput oi = new ObjectInputStream(fis);
		Person p = null;
		while (0 != fis.available()) {
			p = (Person)oi.readObject();
			System.out.println(p.getId() + " " + p.getName());
		}
	}
}

现在我们给Person新增一个属性age及其get/set属性

package test;

import java.io.Serializable;

public class Person implements Serializable {
	private Integer id;
	private String name;
	private Integer age;
	/* 省略get/set方法 */
}

再运行Test2.发现报错了,是由于自动生成的serialVersionUID不同导致的。

Exception in thread "main" java.io.InvalidClassException: test.Person; local class incompatible: stream classdesc serialVersionUID = -3988139021726296639, local class serialVersionUID = -2271613962938391343
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at test.Test2.main(Test2.java:16)

如果给Person加上一个私有的方法private void whateverMethod(){}。结果是没有报错的,但是如果修饰符不是private就会报同样的错误。

package test;

import java.io.Serializable;

public class Person implements Serializable {
	private Integer id;
	private String name;
	
	/* 省略get/get()方法 */
	private void whateverMethod() {	
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用 `com.github.wechatpay-apiv3` 库处理微信 H5 支付的异步通知,您可以按照以下步骤进行操作: 1. 在项目的 Maven 配置文件(例如 `pom.xml`)中添加 `com.github.wechatpay-apiv3` 的依赖项: ```xml <dependencies> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>1.0.0-beta4</version> </dependency> </dependencies> ``` 2. 在异步通知的接口中编写代码。 ```java import com.github.wechatpay.apiv3.WxPayApiV3; import com.github.wechatpay.apiv3.WxPayApiV3Config; import com.github.wechatpay.apiv3.model.notify.WxPayOrderNotifyResult; import com.github.wechatpay.apiv3.model.notify.WxPayOrderNotifyResult.NotifyResponse; import com.github.wechatpay.apiv3.model.notify.WxPayOrderNotifyResult.Resource; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.util.Timeout; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.KeyStore; public class WeChatH5NotifyHandler { private static final String WECHAT_API_CERT_SERIAL_NUMBER = "YOUR_WECHAT_API_CERT_SERIAL_NUMBER"; private static final String WECHAT_API_CERTIFICATE_PATH = "path/to/your/wechat/api/certificate.p12"; private static final String WECHAT_API_CERTIFICATE_PASSWORD = "your_certificate_password"; public void handleNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { try { // 创建微信支付 API 配置 WxPayApiV3Config config = new WxPayApiV3Config.Builder() .appId("your_app_id") .merchantId("your_merchant_id") .privateKeySerialNumber(WECHAT_API_CERT_SERIAL_NUMBER) .privateKey(getPrivateKey()) .build(); // 创建微信支付 API 实例 WxPayApiV3 wxPayApiV3 = new WxPayApiV3(config); // 读取异步通知的请求体 String notifyData = EntityUtils.toString(new BasicAsyncEntityConsumer(), request.getInputStream(), StandardCharsets.UTF_8); // 解析异步通知数据 WxPayOrderNotifyResult notifyResult = wxPayApiV3.parseOrderNotifyResult(notifyData); // 验证签名 if (wxPayApiV3.verifySignature(notifyResult)) { // 签名验证成功 // 处理支付成功的逻辑 // ... // 返回成功响应给微信服务器 response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("SUCCESS"); } else { // 签名验证失败,返回失败响应给微信服务器 response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().write("FAIL"); } } catch (Exception e) { e.printStackTrace(); // 返回失败响应给微信服务器 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("FAIL"); } } private KeyStore getPrivateKey() throws Exception { return SSLContextBuilder .create() .loadKeyMaterial(getClass().getClassLoader().getResourceAsStream(WECHAT_API_CERTIFICATE_PATH), WECHAT_API_CERTIFICATE_PASSWORD.toCharArray()) .build() .getKeyStore(); } } ``` 在上述代码中,我们创建了一个名为 `WeChatH5NotifyHandler` 的类,其中的 `handleNotify` 方法用于处理微信 H5 支付的异步通知。该方法接收 `HttpServletRequest` 和 `HttpServletResponse` 对象作为参数,从请求中获取异步通知的数据,并进行相应的处理逻辑。 在 `handleNotify` 方法中,我们首先创建了一个 `WxPayApiV3Config` 对象,用于配置微信支付 API 的相关参数。其中,我们需要提供应用 ID(`appId`)、商户号(`merchantId`)、微信支付 API 证书的序列号(`privateKeySerialNumber`)以及证书的私钥(`privateKey`)。您需要将这些参数替换为您自己的值。 然后,我们使用 `WxPayApiV3` 实例来解析异步通知数据,并验证签名。如果签名验证成功,则表示支付成功,可以进行相应的处理逻辑,并返回成功响应给微信服务器。如果签名验证失败,则返回失败响应给微信服务器。 请注意,以上示例代码仅供参考,具体的实现可能因应用的需求而有所不同。您需要根据实际情况进行修改和完善。另外,在真实的项目中,请确保您已正确配置和保护微信支付 API 证书的私钥。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值