我们知道java中对象是存放在内存中的,为了持久化对象或者将对象通过网络传输,java提供了序列化对象的 方式,一个类只需集成Serializable接口,就可以实现可序列化
但是值得注意的是,要实现可序列化,需要保证类中所有的变量都是可序列化的。
其实我理解序列化可以等同于一种特殊的toString,就是将对象转化为二进制数组然后进行输出,反序列化接收到二进制数组后将其转化为对象:这不是很像我们将对象toJsonString然后再Json.parseObject吗?
这样理解的话,为什么序列化类中的属性必须也是可序列化的,就很好理解了,试想一下,你要实现对象的toString的时候,里面的属性是没办法toString的,那我toString函数实现了有啥意义呢?我还是啥也看不见。
1. 可序列化类中的属性
我的类里面需要使用不可序列化的属性怎么办(比方说类里面我有一个属性,是mysql的Connection对象)
这时候在属性前面加上transient
关键字就好啦。
摘抄一段runoob的原话:
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
2. 遇到的问题
我在实现flink中的MapFunction接口,这个接口是必须可序列化的。因为flink是一个分布式框架,所以它会把写的代码序列化后发送到集群上不同的机器。我的MapFunction的需求是给定一个userId
,到mysql表中查询这个userId
所含有的信息,返回一个userInfo
的bean对象。我的代码开始是长这样的:
static class userInfoMapFunction implements MapFunction<String, UserInfo>{
Statement statement;
userInfoMapFunction() throws SQLException {
java.sql.Connection connection = DriverManager.getConnection(
"jdbc:mysql://ubuntu:3306/gmall",
"root",
"123456"
);
statement = connection.createStatement();
}
@Override
public UserInfo map(String userId) throws Exception {
String sql = String.format("select * from user_info where id=%s",userId);
ResultSet resultSet = statement.executeQuery(sql);
UserInfo userInfo = new UserInfo();
if (resultSet.next()){
userInfo.setUserId(userId);
userInfo.setGender(resultSet.getString("gender"));
}
return userInfo;
}
}
可以看出,我为这个类声明了一个句柄statement
对象,因为如果将statement的声明嵌入到数据流UserInfo map(String userId)
函数中,数据流每来一个userId
就new一个数据库连接,无疑会给数据库带来很大的负载。
但是这样写是不行的,因为statement
对象是无法序列化的。
于是我在开头的属性前面加上transient
关键字,变成:
// 表示这个属性不需要序列化
transient Statement statement;
这样做编译是ok,但是我根本没有将statement
对象序列化发送到集群中,这就意味着,集群中机器上的这个属性是null
,当数据流中的userId
来的时候,调用UserInfo map(String userId)
函数,会报空调用的error。
3. 解决的方法
又要保证MapFunction可序列化,又不能来一个数据new一个连接,我最终选择将statement
对象放在了MapFunction这个类的外面,结构是这样:
public class FirstOrder {
static Statement statement;
public static void main(String[] args) throws Exception {
java.sql.Connection connection = DriverManager.getConnection(
"jdbc:mysql://ubuntu:3306/gmall",
"root",
"123456"
);
statement = connection.createStatement();
//省略业务代码
}
static class userInfoMapFunction implements MapFunction<String, UserInfo>{
@Override
public UserInfo map(String userId) throws Exception {
String sql = String.format("select * from user_info where id=%s",userId);
ResultSet resultSet = statement.executeQuery(sql);
UserInfo userInfo = new UserInfo();
if (resultSet.next()){
userInfo.setUserId(userId);
userInfo.setGender(resultSet.getString("gender"));
// TODO: 2022-05-13
// calculate user's age
}
return userInfo;
}
}
}
这样虽然不太优雅,因为userInfoMapFunction
中要用的东西我在外面定义,但是确实可以解决问题,就这样吧,over。