springmvc+hessian 的LocaldateTime 序列化問題

寫在前面:

     阿里代碼規範說自從jdk8之後,可以用Localdatetime取代calder,instant 取代date,所以我就信了這個邪,在自己建的實體類裏面用了這個LocalDatetime,如下:

Student.java

package com.equaker.model;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;

import org.springframework.format.annotation.DateTimeFormat;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;

import lombok.Data;

@TableName(value="student")
@Data
public class Student extends Model<Student> implements Serializable {
	
	/**
	 * 
	 */
	@TableField(exist=false)
	private static final long serialVersionUID = 3605577030483006663L;
	@TableId(value="id",type=IdType.AUTO)
	private Integer id;
	
	@TableField(value="name")
	private String name;
	@TableField(value="grade")
	private Integer grade;
	@TableField(exist = false)
	private List<Course> courses;
	
	@TableField(value="create_time")
	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	private LocalDateTime createTime;
	
	
}

在簡單不過的代碼了,剛開始是因爲前段傳參的時候傳遞形如 "2019-05-22 11:11:11"這樣的時間,但是spring說轉換錯誤 string->localdateTime error,所以在屬性上面加了@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")。so  一切接受正常。一般的mvc模式 都沒問題了。但是在現在微服務流行的形況下(hessian,dubbo等),序列化成爲比較普遍的問題。這裏主要介紹hessian。

案例:

springmvc+hessian

api層:

DemoService.java:

public interface DemoService {
	void gg(LocalDateTime localDateTime);
}

web層

先放送兩個序列化locldatetime需要用的的工具:

LocalDateTimeSerialize.java:

package com.equaker.server.config;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

import com.caucho.hessian.io.AbstractHessianOutput;
import com.caucho.hessian.io.AbstractSerializer;
public class LocalDateTimeSerializer extends AbstractSerializer {
	@Override
    public void writeObject(Object obj, AbstractHessianOutput out)
            throws IOException
    {
        if (obj == null) {
            out.writeNull();
        } else {
            Class cl = obj.getClass();

            if (out.addRef(obj)) {
                return;
            }
            // ref 返回-2 便是開始寫Map
            int ref = out.writeObjectBegin(cl.getName());

            if (ref < -1) {
                out.writeString("value");
                Long milliSecond = ((LocalDateTime) obj).toInstant(ZoneOffset.of("+8")).toEpochMilli();
                out.writeUTCDate(milliSecond);
                out.writeMapEnd();
            } else {
                if (ref == -1) {
                    out.writeInt(1);
                    out.writeString("value");
                    out.writeObjectBegin(cl.getName());
                }

                Long milliSecond = ((LocalDateTime) obj).toInstant(ZoneOffset.of("+8")).toEpochMilli();
                out.writeUTCDate(milliSecond);
            }
        }
    }
}

LocalDateTimeDeserializer.java:

package com.equaker.server.config;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import com.caucho.hessian.io.AbstractDeserializer;
import com.caucho.hessian.io.AbstractHessianInput;
import com.caucho.hessian.io.IOExceptionWrapper;
public class LocalDateTimeDeserializer extends AbstractDeserializer{
	@Override
    public Class getType()
    {
        return LocalDateTime.class;
    }


    @Override
    public Object readObject(AbstractHessianInput in,
                             Object []fields)
            throws IOException
    {
        String []fieldNames = (String []) fields;

        int ref = in.addRef(null);

        long initValue = Long.MIN_VALUE;

        for (int i = 0; i < fieldNames.length; i++) {
            String key = fieldNames[i];

            if (key.equals("value")) {
                initValue = in.readUTCDate();
            } else {
                in.readObject();
            }
        }
        Object value = create(initValue);
        in.setRef(ref, value);
        return value;
    }

    @Override
    public Object readMap(AbstractHessianInput in) throws IOException {
    	Map map = new HashMap();
        
        in.addRef(map);

        while (! in.isEnd()) {
          map.put(in.readObject(), in.readObject());
        }
        in.readEnd();
        return map;
    }
    
    
    private Object create(long initValue)
            throws IOException
    {
        if (initValue == Long.MIN_VALUE) {
            throw new IOException(LocalDateTime.class + " expects name.");
        }
        try {
            return LocalDateTime.ofEpochSecond(new Long(initValue)/1000,Integer.valueOf(String.valueOf(initValue%1000))*1000,ZoneOffset.of("+8"));
        } catch (Exception e) {
            throw new IOExceptionWrapper(e);
        }
    }
}

HessianConfig.java:

package com.equaker.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.caucho.hessian.io.ExtSerializerFactory;
import com.caucho.hessian.io.SerializerFactory;

@Configuration
public class HessianConfig {
	@Bean
    public SerializerFactory serializerFactory() {
        // DO 自定義hessian反序列化
        // step 1. 定義外部序列化工廠
        ExtSerializerFactory extSerializerFactory = new ExtSerializerFactory();
        
        extSerializerFactory.addSerializer(java.time.LocalDateTime.class,new LocalDateTimeSerializer());
        extSerializerFactory.addDeserializer(java.time.LocalDateTime.class,new LocalDateTimeDeserializer());
        // step 2. 序列化工廠
        SerializerFactory serializerFactory = new SerializerFactory();
        serializerFactory.addFactory(extSerializerFactory);
        return serializerFactory;
    }
}

因爲是springmvc模式,所以在application-remote.xml中還需要指定序列化工廠,

application-remote.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
           http://www.springframework.org/schema/context 
		   http://www.springframework.org/schema/context/spring-context-4.1.xsd
		   http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

	<bean id="demoService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
		<property name="serviceUrl" value="${equaker-server-url}/demoService"/>
		<property name="serviceInterface" value="com.equaker.api.DemoService" />
		<property name="serializerFactory" ref="serializerFactory"/>
		<property name="hessian2Request" value="true"/>
	</bean>
    
</beans>

這是控制層(調用端)需要配置的,主要介紹 serializerFactory與hessian2Request

serializerFactory:指向hessianconfig中配置的序列化工廠,記得是ref.

hessian2Request:配置了這個參數,序列化會使用Hessian2Out這個,而不是使用HessianOut,前一個支持二進制序列化,是最新支持的。

server層

同樣需要配置序列化工廠,也就是上面的三個類。加入server即可。這裏不同的是server端的application-remote.xml配置

application-remote.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
           http://www.springframework.org/schema/context 
		   http://www.springframework.org/schema/context/spring-context-4.1.xsd
		   http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
	
    <bean name="/demoService" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="serviceInterface" value="com.equaker.api.DemoService"/>
        <property name="service" ref="demoService"/>
        <property name="serializerFactory" ref="serializerFactory"/>
<!--         <property name="isHessian2" value="true"/> -->
    </bean>
    
</beans>

注意:服務端主要是解析,所以isHessian2就不用配置了

總結:在網上找了很久沒有寫好的代碼,copy不到了,就自己慢慢扣源碼,配置的這兩個參數就是在源碼裏面看到的,hessian已經提供給我們了。但是文檔太少,沒人用。一般的自定義解析器都需要繼承com.caucho.hessian.io.AbstractDeserializer/com.caucho.hessian.io.AbstractSerializer。記得重寫裏面的writeObject,writeMap等你需要的方法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章