SpringBoot2-第四章:使用Gson作為HttpMessageConverter

pc859107393發表於2019-03-01

上一章我們簡單的介紹了Springboot的事務控制,這一章我們來在springboot中使用一下Gson。

本專案的GitHub:https://github.com/pc859107393/Go2SpringBoot.git

有興趣交流springboot進行快速開發的同學可以加一下下面的企鵝群。

行走的java全棧

Gson的簡單使用

在網際網路上面有很多關於Gson的使用介紹,在這裡我直接貼出kotlin版本的GsonUtil,具體程式碼如下:

import com.google.gson.*
import java.io.IOException
import java.io.Reader
import java.lang.reflect.Type
import java.util.ArrayList

@SuppressWarnings("unchecked")
object GsonUtil {

    private var gson: Gson? = null


    /**
     * 自定義TypeAdapter ,null物件將被解析成空字串
     */
    private val STRING = object : TypeAdapter<String>() {
        override fun read(reader: JsonReader): String {
            try {
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull()
                    return ""//原先是返回Null,這裡改為返回空字串
                }
                return reader.nextString()
            } catch (e: Exception) {
                e.printStackTrace()
            }

            return ""
        }

        override fun write(writer: JsonWriter, value: String?) {
            try {
                if (value == null) {
                    writer.nullValue()
                    return
                }
                writer.value(value)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }

    /**
     * 自定義adapter,解決由於資料型別為Int,實際傳過來的值為Float,導致解析出錯的問題
     * 目前的解決方案為將所有Int型別當成Double解析,再強制轉換為Int
     */
    private val INTEGER = object : TypeAdapter<Number>() {
        @Throws(IOException::class)
        override fun read(`in`: JsonReader): Number {
            if (`in`.peek() == JsonToken.NULL) {
                `in`.nextNull()
                return 0
            }
            try {
                val i = `in`.nextDouble()
                return i.toInt()
            } catch (e: NumberFormatException) {
                throw JsonSyntaxException(e)
            }

        }

        @Throws(IOException::class)
        override fun write(out: JsonWriter, value: Number) {
            out.value(value)
        }
    }

    init {
        val gsonBulder = GsonBuilder()
        gsonBulder.registerTypeAdapter(String::class.java, STRING)   //所有String型別null替換為字串“”
        gsonBulder.registerTypeAdapter(Int::class.javaPrimitiveType, INTEGER) //int型別對float做相容
        gsonBulder.setDateFormat("yyyy-MM-dd HH:mm:ss")
        //通過反射獲取instanceCreators屬性
        try {
            val builder = gsonBulder.javaClass as Class<*>
            val f = builder.getDeclaredField("instanceCreators")
            f.isAccessible = true
            //註冊陣列的處理器
            gsonBulder.registerTypeAdapterFactory(CollectionTypeAdapterFactory(ConstructorConstructor(f.get(gsonBulder) as Map<Type, InstanceCreator<Any>>)))
        } catch (e: Exception) {
            e.printStackTrace()
        }

        gson = gsonBulder.create()
    }

    /**
     * Json字串 轉為指定物件
     *
     * @param json json字串
     * @param type 物件型別
     * @param <T>  物件型別
     * @return
     * @throws JsonSyntaxException
    </T> */
    @Throws(JsonSyntaxException::class)
    fun <T> toBean(json: String, type: Class<T>): T {
        return gson!!.fromJson(json, type)
    }

    /**
     * 將jsonStr轉換為javaBean
     *
     * @param object
     * @return json string
     */
    fun toJson(`object`: Any): String {
        return gson!!.toJson(`object`)
    }

    /**
     * 將jsonStr轉換為javaBean
     *
     * @param json
     * @param type
     * @return instance of type
     */
    fun <V> fromJson(json: String, type: Class<V>): V {
        return gson!!.fromJson(json, type)
    }

    /**
     * 將jsonStr轉換為javaBean
     *
     * @param json
     * @param type
     * @return instance of type
     */
    fun <V> fromJson(json: String, type: Type): V {
        return gson!!.fromJson(json, type)
    }

    /**
     * 將reader轉換為javaBean
     *
     * @param reader
     * @param type
     * @return instance of type
     */
    fun <V> fromJson(reader: Reader, type: Class<V>): V {
        return gson!!.fromJson(reader, type)
    }

    /**
     * 將reader轉換為javaBean
     *
     * @param reader
     * @param type
     * @return instance of type
     */
    fun <V> fromJson(reader: Reader, type: Type): V {
        return gson!!.fromJson(reader, type)
    }
    
    /**
    * 將json集合轉換為ArrayList
    *  
    * @param json 需要轉換的json集合
    * @param type 轉出的型別
    */
    fun <T> toList(json: String, type: Class<T>): ArrayList<T>? {
        val list = ArrayList<T>()
        return try {
            val parser = JsonParser()
            parser.parse(json).asJsonArray.forEach { element -> list.add(gson!!.fromJson(element, type)) }
            ArrayList(list)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }

    }
}
複製程式碼

是的沒錯,上面僅僅是一個kotlin上面用的gson的工具類,簡化你的gson操作,但是我們的核心是什麼?在SpringMVC中使用Gson解析json資料(HttpMessageConverter)。

SpringMVC的訊息轉換器HttpMessageConverter

SpringMVC使用訊息轉換器(HttpMessageConverter)實現將請求資訊轉換為物件、將物件轉換為響應資訊。

眾所周知,http請求的資料交換完全是依靠資料流的讀寫來實現的。在servlet中我們是直接使用ServletRequest或者ServletResponse的輸入輸出流完成一些操作。但是在SPringMVC中我們使用一些註解來完成相關操作,具體使用的註解在org.springframework.web.bind.annotation下面。當然我們常用的一些如:@RequestBody@RequestPart@RequestParam等等。

使用了@RequestBodyHttpMessageConverter的相關實現類就會把資料轉換到對應的變數中(@RequestBody標記的某個請求的請求體可以將json自動換轉為對應的實體,當然@ResponseBody也是由HttpMessageConverter相關類來轉換)。 具體的程式碼請看org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,這裡暫且不表。

接著我們向下看,可以找到一個FormHttpMessageConverter的實現類,顧名思義就是表單資訊轉換的轉換器。但是具體的內容太多,我們接著查詢相關的實現類AllEncompassingFormHttpMessageConverter,具體的程式碼如下:

public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {

	private static final boolean jaxb2Present =
			ClassUtils.isPresent("javax.xml.bind.Binder",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());

	private static final boolean jackson2Present =
			ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
			ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());

	private static final boolean jackson2XmlPresent =
			ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());

	private static final boolean jackson2SmilePresent =
			ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());

	private static final boolean gsonPresent =
			ClassUtils.isPresent("com.google.gson.Gson",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());

	private static final boolean jsonbPresent =
			ClassUtils.isPresent("javax.json.bind.Jsonb",
					AllEncompassingFormHttpMessageConverter.class.getClassLoader());


	public AllEncompassingFormHttpMessageConverter() {
		addPartConverter(new SourceHttpMessageConverter<>());

		if (jaxb2Present && !jackson2XmlPresent) {
			addPartConverter(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
			addPartConverter(new MappingJackson2HttpMessageConverter());
		}
		else if (gsonPresent) {
			addPartConverter(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
			addPartConverter(new JsonbHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
			addPartConverter(new MappingJackson2XmlHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
			addPartConverter(new MappingJackson2SmileHttpMessageConverter());
		}
	}

}
複製程式碼

從上面我們可以看到SpringMVC中HttpMessage的轉換器佇列中已經加入了Jackson和Gson的解析器,所以我們要使用Gson來解析,只需要移除MappingJackson2HttpMessageConverter即可。

接下來我們需要在WebMvcConfigurer中去操作MessageConverters的資料,程式碼如下:

@SpringBootApplication
@EnableWebMvc
@EnableSwagger2
@MapperScan(value = ["cn.acheng1314.base.dao"])
@Configuration
@EnableTransactionManagement
class BaseApplication : WebMvcConfigurer {

    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 刪除MappingJackson2HttpMessageConverter,並使用GsonHttpMessageConverter替換
        converters.forEach { t: HttpMessageConverter<*>? ->
            if (t is MappingJackson2HttpMessageConverter) {
                converters.remove(t)
                return super.extendMessageConverters(converters)
            }
        }
    }
    
}
複製程式碼

這個時候我們跑一下專案,實現Gson解析json是完全沒有問題的,但是新的問題產生了!

問題:在我們的swagger中,不能正確的解析json了,仔細看一下web的除錯資訊,會提示我們不能正確的解析json,emmm。。。是不是覺得坑來了?是的,沒錯,我們通過一番查詢可以看到在SpringFox的包springfox.documentation.spring.web.json下的JsonJsonSerializer均是採用的Jackson實現,程式碼如下:

package springfox.documentation.spring.web.json;

import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.annotation.JsonValue;

public class Json {
  private final String value;

  public Json(String value) {
    this.value = value;
  }

  @JsonValue
  @JsonRawValue
  public String value() {
    return value;
  }
}


package springfox.documentation.spring.web.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;

public class JsonSerializer {
  private ObjectMapper objectMapper = new ObjectMapper();

  public JsonSerializer(List<JacksonModuleRegistrar> modules) {
    for (JacksonModuleRegistrar each : modules) {
      each.maybeRegisterModule(objectMapper);
    }
  }

  public Json toJson(Object toSerialize) {
    try {
      return new Json(objectMapper.writeValueAsString(toSerialize));
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Could not write JSON", e);
    }
  }
}
複製程式碼

所以這裡就提示我們需要做到Gson實現的json節點都需要偽裝成Jackson的樣子,處理程式碼如下:

import java.lang.reflect.Type

import com.google.gson.*
import springfox.documentation.spring.web.json.Json
/**
* 實現自己的JsonSerializer
*/
class SpringfoxJsonToGsonAdapter : com.google.gson.JsonSerializer<Json> {

        override fun serialize(json: Json, type: Type, context: JsonSerializationContext): JsonElement {
                val parser = JsonParser()
                return parser.parse(json.value())
        }

}
複製程式碼

同樣的在我們的WebMvcConfigurer實現類中重寫extendMessageConverters方法這裡應該使用我們的SpringfoxJsonToGsonAdapter,程式碼如下:

    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 刪除MappingJackson2HttpMessageConverter,並使用GsonHttpMessageConverter替換
        converters.forEach { t: HttpMessageConverter<*>? ->
            if (t is MappingJackson2HttpMessageConverter) {
                converters.remove(t)
                converters.add(object : GsonHttpMessageConverter() {
                    init {
                        //自定義Gson介面卡
                        super.setGson(GsonBuilder()
                                .registerTypeAdapter(Json::class.java, SpringfoxJsonToGsonAdapter())
                                .create())
                    }
                }) // 新增GsonHttpMessageConverter
                return super.extendMessageConverters(converters)
            }
        }
    }
複製程式碼

現在我們再去試一試swagger,是不是一切恢復原狀了?好的今天的東西已經說完了,讓我們在結束的時候再安利一下自己!

每天進步一點點,十年磨一劍。加油!

相關文章