问题的暴露
起因: 在工作中,授权框架从 Spring Security 更换为 Oauth2,授权获取 token 的时候,发现登录后,使用 FastJson 和采用默认的Jackson2 颁发 token 返回结果格式完全不同,按照 Oauth2 官网提供的示例中的返回结果,对比使用 FastJson 返回结果完全不同,于是在这个情况下,决定先探一下 Jackson2 为什么会返回正确格式,而 FastJson 却存在问题。
FastJson 错误格式
{
"additionalInformation": {},
"expiration": "2021-04-15 16:22:09",
"expired": false,
"expiresIn": 7199,
"refreshToken": null,
"scope": [
"all"
],
"tokenType": "bearer",
"value": "852fd42a-1765-402d-bd27-3c6dfbf37eef"
}
Jackson2 正确格式
{
"access_token": "852fd42a-1765-402d-bd27-3c6dfbf37eef",
"token_type": "bearer",
"expires_in": 7199,
"scope": "all"
}
1、寻找问题根源
最开始发现这个问题的时候,我也是一头雾水,反正就很懵。很显然我只是更换了一个消息序列化框架,并没有更换其他内容,所以问题就出在消息转换器上。既然问题定位到了后,那么久先从正确的一个消息序列化框架入手,先看看他是怎么完成对 Oauth2 定制化解析的。
根据以前看 Mybatis 和 Spring 源码打下的基础,决定从 Jackson2 源码入手,通过对源码 DEBUG 看看能不能找到一些端倪。
1.1 寻找 Jackson2 写出消息的方法
- 进入
MappingJackson2HttpMessageConverter类中,查看每个方法,凭借直觉判断哪个方法是往外写结果的方法,最终在org.springframework.http.converter.AbstractGenericHttpMessageConverter类中找到了org.springframework.http.converter.AbstractGenericHttpMessageConverter#write方法,代码如下:
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}


通过 DEBUG 模式,将断点打到
if条件上,最终发现并没有进入if内,而是走的else, 并且我能够确定到当前参数t的类型是DefaultOAuth2AccessToken,并且这个对象和FastJson序列化后的json格式十分相似,那么我就确定了我之前的判断是对的,那么继续进入writeInternal(t, type, outputMessage)方法内;
1.2 确定 writeInternal(t, type, httpOutputMessage) 实现类

啊,这~, 不要紧,咱们只是为了解决问题,而不是学源码,三个实现类都来一个断点,走哪个类,就看哪个类嘛,但是根据类名,我猜测是在
AbstractJackson2HttpMessageConverter中的。
1.3 进入 Jackson2 抽象消息转换器中
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// ********************************断点打在此处***************************************
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
try {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
// 通过 DEBUG ,看到这句话被执行了,很显然在 1.1 步中,我就知道这一步执行的答案了
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// 最终进入了这个方法
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
generator.close();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}

1.4 进入写对象的处理类中 ObjectWrite
在
1.3中,通过DEBUG发现方法进入了objectWriter.writeValue(generator, value);内,那么这一步就DEBUG这个方法内部
public void writeValue(JsonGenerator g, Object value) throws IOException {
_assertNotNull("g", g);
_configureGenerator(g);
if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
&& (value instanceof Closeable)) {
Closeable toClose = (Closeable) value;
try {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
} catch (Exception e) {
ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
} else {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
}
}

通过
DEBUG此方法,最终进入了else的逻辑,执行了_prefetch.serialize(g, value, _serializerProvider())这句代码,那么继续进入该方法
1.5 进入 com.fasterxml.jackson.databind.ObjectWriter.Prefetch#serialize 方法内
public void serialize(JsonGenerator gen, Object value, DefaultSerializerProvider prov) throws IOException {
if (typeSerializer != null) {
prov.serializePolymorphic(gen, value, rootType, valueSerializer, typeSerializer);
} else if (valueSerializer != null) {
prov.serializeValue(gen, value, rootType, valueSerializer);
} else if (rootType != null) {
prov.serializeValue(gen, value, rootType);
} else {
prov.serializeValue(gen, value);
}
}

通过
DEBUG,不难发现最终走到了else逻辑,进入prov.serializeValue(gen, value)
1.6 进入 DefaultSerializerProvider 类中
通过该类名就能知道,这个类主要是提供一个消息序列化器,那么这个类是一个重头戏,最终今天要分析的源头也就在这个类中
public void serializeValue(JsonGenerator gen, Object value) throws IOException {
_generator = gen;
if (value == null) {
_serializeNull(gen);
return;
}
final Class<?> cls = value.getClass();
// ******************************************************************
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
// ******************************************************************
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, value, ser, _config.findRootName(cls));
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
_serialize(gen, value, ser);
}
在代码中,我标注了一句代码,该代码是产生今天这个问题的核心原因,那么这句代码到底都做了什么内容?
通过方法名,findTypedValSerializer,翻译过来就是通过被序列化的对象的Class对象,获取到该类的序列化器
通过以上的代码,你发现了什么了没,是不是Oauth2针对了Jackson2开发了一个单独的消息序列化器,却并没有对FastJson开发,
既然你不给我开发,我自己开发一个就完事儿了,说干就干。
2. 针对 Oauth2 开发 FastJson 序列化器
2.1 通过 第1 点 的分析,我们知道 Oauth2 对 Jackson2 开发了一个消息序列化器,那么我们需要自己写一个,就先看看别人的处理逻辑是怎么样的!
进入
OAuth2AccessTokenJackson2Serializer类中
public final class OAuth2AccessTokenJackson2Serializer extends StdSerializer<OAuth2AccessToken> {
public OAuth2AccessTokenJackson2Serializer() {
super(OAuth2AccessToken.class);
}
@Override
public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonGenerationException {
jgen.writeStartObject();
// OAuth2AccessToken.ACCESS_TOKEN = access_token
jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
// OAuth2AccessToken.TOKEN_TYPE = token_type
jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
// OAuth2AccessToken.REFRESH_TOKEN = refresh_token
jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
// OAuth2AccessToken.EXPIRES_IN = expires_in
jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuffer scopes = new StringBuffer();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
// OAuth2AccessToken.SCOPE = scope
jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
jgen.writeObjectField(key, additionalInformation.get(key));
}
jgen.writeEndObject();
}
}
通过以上的类,发现好像这并不难啊,我自己也能写一个,那么开始动手写一个
FastJson的消息序列化器
2.2 自己开发 Oauth2AccessTokenFastJsonSerializer
OAuth2AccessTokenJackson2Serializer都是取值赋值,我搞一个Map,然后序列化成String,然后写出去,效果是一样的,那么说干就干
public class Oauth2AccessTokenFastJsonSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
SerializeWriter out = serializer.out;
Map<String, Object> token = this.fastJsonSerializerOAuth2Token((OAuth2AccessToken)object);
String strToken = JSONObject.toJSONString(token);
out.write(strToken);
}
private Map<String, Object> fastJsonSerializerOAuth2Token(OAuth2AccessToken token) {
// 创建结果集包装容器
Map<String, Object> tokenMap = new HashMap<>();
// access_token 赋值
tokenMap.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
// token_type 赋值
tokenMap.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
// 是否存在 refresh_token
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
tokenMap.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
// 是否存在超时时间
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
tokenMap.put(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
// 多个token作用范围需要处理,单不能为空
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuilder scopes = new StringBuilder();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
tokenMap.put(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
// 其余参数的处理
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
tokenMap.put(key, additionalInformation.get(key));
}
return tokenMap;
}
}
注意:Jackson2 是实现的com.fasterxml.jackson.databind.ser.std.StdSerializer这个类,然后重写serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider)方法,而这个类确是 Jackson2 提供的,FastJson 重写序列化器需要实现com.alibaba.fastjson.serializer.ObjectSerializer类,重写write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)方法
2.3 将自己的序列化器注册到序列化器管理器中
@Configuration
public class GlobalFastJsonHttpMessageConvertConfiguration implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.clear();
// converters.removeIf(convert -> convert instanceof FastJsonHttpMessageConverter);
// converters.removeIf(convert -> convert instanceof MappingJackson2HttpMessageConverter);
converters.add(this.fastJsonHttpMessageConvert());
}
private HttpMessageConverter<?> fastJsonHttpMessageConvert() {
FastJsonHttpMessageConverter fc = new FastJsonHttpMessageConverter();
FastJsonConfig fjc = new FastJsonConfig();
fjc.setSerializerFeatures(
// 引用字段名称
SerializerFeature.QuoteFieldNames,
// 写入映射空值
SerializerFeature.WriteMapNullValue,
// 禁用循环参考检测
SerializerFeature.DisableCircularReferenceDetect,
// 写入日期使用日期格式
SerializerFeature.WriteDateUseDateFormat,
// 将空字符串写入为空
SerializerFeature.WriteNullStringAsEmpty
);
// **************************** 注册序列化器到容器中 ************************************
fjc.getSerializeConfig().put(DefaultOAuth2AccessToken.class, new Oauth2AccessTokenFastJsonSerializer());
// **************************** 注册序列化器到容器中 ************************************
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_JSON);
fc.setSupportedMediaTypes(mediaTypeList);
fc.setFastJsonConfig(fjc);
return fc;
}
}
fjc.getSerializeConfig().put(DefaultOAuth2AccessToken.class, new Oauth2AccessTokenFastJsonSerializer())这句代码,就是将自定义的Oauth2定制化序列化器注册到配置容器中,值得注意得是,SerializeConfig中,key是DefaultOAuth2AccessToken.class并不是OAuth2AccessToken.class
2.4 测试 Oauth2AccessTokenFastJsonSerializer
完全符合需求

3. 总结
解决问题其实并不难,只是需要一双慧眼,去发现问题所在的点,然后一点点的推进,就能解决问题,阅读源码需要坚持,有了基础的条件下,很多代码配合注释是嫩看懂的,千万不要遇到问题就打退堂鼓。
