Log4j2需FilteredObjectInputStream,JPA仅提供ObjectInputStream如何解决?
解决Log4j2 JPA Appender与Hibernate输入流不兼容的问题
你遇到的这个问题本质上是Log4j2的安全反序列化机制(依赖FilteredObjectInputStream)和Hibernate/JPA默认使用标准ObjectInputStream之间的冲突。下面给你几个可行的解决方案:
1. 自定义Hibernate UserType来替换输入流实现
Hibernate允许我们自定义类型处理逻辑,通过实现UserType接口,我们可以在读取数据库中的日志事件二进制数据时,强制使用FilteredObjectInputStream进行反序列化:
步骤:
- 实现
UserType接口,重点在nullSafeGet方法中包装输入流:
import org.apache.logging.log4j.core.impl.FilteredObjectInputStream; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; import java.io.*; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; public class LogEventUserType implements UserType { @Override public int[] sqlTypes() { return new int[]{Types.BLOB}; } @Override public Class returnedClass() { return org.apache.logging.log4j.core.LogEvent.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { return x == y || (x != null && y != null && x.equals(y)); } @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { InputStream inputStream = rs.getBinaryStream(names[0]); if (inputStream == null) { return null; } try (FilteredObjectInputStream filteredStream = new FilteredObjectInputStream(inputStream)) { return filteredStream.readObject(); } catch (ClassNotFoundException | IOException e) { throw new HibernateException("Failed to deserialize LogEvent", e); } } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.BLOB); return; } try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(value); st.setBinaryStream(index, new ByteArrayInputStream(baos.toByteArray()), baos.size()); } catch (IOException e) { throw new HibernateException("Failed to serialize LogEvent", e); } } @Override public Object deepCopy(Object value) throws HibernateException { // LogEvent本身是不可变的,直接返回即可 return value; } @Override public boolean isMutable() { return false; } @Override public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } }
- 在你的日志实体类中,给存储
LogEvent的字段标注自定义类型:
import org.hibernate.annotations.Type; import javax.persistence.*; @Entity @Table(name = "log_events") public class LogEventEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Type(type = "com.your.package.LogEventUserType") @Lob private org.apache.logging.log4j.core.LogEvent logEvent; // getter、setter方法 }
这样Hibernate在读取该字段时,就会使用我们自定义的逻辑,用FilteredObjectInputStream替代标准输入流。
2. 在实体类中手动控制序列化/反序列化
如果你不想自定义Hibernate类型,也可以直接在实体类中把LogEvent转换成byte[]存储,然后在getter方法中手动用FilteredObjectInputStream反序列化:
import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.FilteredObjectInputStream; import javax.persistence.*; import java.io.*; @Entity @Table(name = "log_events") public class LogEventEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Lob private byte[] logEventBytes; // 手动实现LogEvent的序列化 public void setLogEvent(LogEvent logEvent) { if (logEvent == null) { this.logEventBytes = null; return; } try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(logEvent); this.logEventBytes = baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Failed to serialize LogEvent", e); } } // 手动用FilteredObjectInputStream反序列化 public LogEvent getLogEvent() { if (this.logEventBytes == null) { return null; } try (FilteredObjectInputStream fis = new FilteredObjectInputStream(new ByteArrayInputStream(this.logEventBytes))) { return (LogEvent) fis.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("Failed to deserialize LogEvent", e); } } // getter、setter for id }
这种方式完全绕开了JPA默认的反序列化逻辑,直接由我们控制输入流类型,兼容性更好。
3. 考虑升级Log4j2版本
你当前使用的是2.11版本,虽然官方说适配了Java9,但后续的2.12+版本可能修复了JPA Appender中的输入流兼容问题。如果项目允许升级,可以尝试更新到较新的稳定版本(比如2.17.x,注意版本的Java兼容性),看看是否能自动解决这个问题。
总的来说,前两种方案都能实现“强制指定JPA输入流类”的需求,其中自定义Hibernate类型更贴合JPA的扩展规范,而实体类手动处理则更直观易维护,你可以根据项目情况选择。
内容的提问来源于stack exchange,提问作者secustor
相关产品推荐
相关产品推荐

