java.sql.Time 字段时区问题 Jackson 源码分析 意想不到的Time处理类

java.sql.Time 字段时区问题 系列文章目录

第一章 初步分析
第二章 Mybatis 源码分析
第三章 Jackson 源码分析 意想不到的Time处理类


文章目录

  • java.sql.Time 字段时区问题 系列文章目录
  • 前言
  • Jackson 源码阅读
    • 1. 先找 JsonFormat.class 打断点一步步跟踪
    • 2. 跟踪进入实际处理类`SqlTimeSerializer`
    • 3. `Jackson` 对`java.util.Date`类进行时区处理源码
  • 总结
  • 解决方案


前言

初步分析文中,主要针对项目部署服务器时区、数据库时区、Jvm运行设置时区和java.sql.Time字段序列化过程时区问题进行展开分析。并给出三个可能问题相对应的解决方案。但是,前段时间又出现时区问题。让我必须重新思考此问题。

因此,我初步认为是Mybatis在数据持久化过程中,对java.sql.Time进行时区处理,导致获取的数据产生时区问题。但通过对Mybatis源码分析,排除了此原因。

以下内容主要对Jackson源码进行阅读,理解分析java.sql.Time字段序列化过程,并定位时区问题。


Jackson 源码阅读

1. 先找 JsonFormat.class 打断点一步步跟踪

java.sql.Time 字段时区问题 Jackson 源码分析 意想不到的Time处理类_第1张图片
Jackson 主要源码:

package com.fasterxml.jackson.annotation;

import java.lang.annotation.*;
import java.util.Locale;
import java.util.TimeZone;

/**
 * General-purpose annotation used for configuring details of how
 * values of properties are to be serialized.
 * Unlike most other Jackson annotations, annotation does not
 * have specific universal interpretation: instead, effect depends on datatype
 * of property being annotated (or more specifically, deserializer
 * and serializer being used).
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonFormat
{
    public final static String DEFAULT_LOCALE = "##default";
    public final static String DEFAULT_TIMEZONE = "##default";
    public String pattern() default "";
    public Shape shape() default Shape.ANY;
    public String locale() default DEFAULT_LOCALE;
    public String timezone() default DEFAULT_TIMEZONE;
    public OptBoolean lenient() default OptBoolean.DEFAULT;
    public JsonFormat.Feature[] with() default { };
    public JsonFormat.Feature[] without() default { };
    public enum Shape
    {
        ANY,
        NATURAL,
        SCALAR,
        ARRAY,
        OBJECT,
        NUMBER,
        NUMBER_FLOAT,
        NUMBER_INT,
        STRING,
        BOOLEAN,
        BINARY
        ;
        public boolean isNumeric() {
            return (this == NUMBER) || (this == NUMBER_INT) || (this == NUMBER_FLOAT);
        }
        public boolean isStructured() {
            return (this == OBJECT) || (this == ARRAY);
        }
    }
    public enum Feature {
        ACCEPT_SINGLE_VALUE_AS_ARRAY,
        ACCEPT_CASE_INSENSITIVE_PROPERTIES,
        ACCEPT_CASE_INSENSITIVE_VALUES,
        WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS,
        WRITE_DATES_WITH_ZONE_ID,
        WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,
        WRITE_SORTED_MAP_ENTRIES,
        ADJUST_DATES_TO_CONTEXT_TIME_ZONE
    }
    public static class Features
    {
        private final int _enabled, _disabled;
        private final static Features EMPTY = new Features(0, 0);
        private Features(int e, int d) {
            _enabled = e;
            _disabled = d;
        }
    }
    public static class Value
        implements JacksonAnnotationValue<JsonFormat>, // since 2.6
            java.io.Serializable
    {
        private static final long serialVersionUID = 1L;
        private final static Value EMPTY = new Value();
        private final String _pattern;
        private final Shape _shape;
        private final Locale _locale;
        private final String _timezoneStr;
        private final Boolean _lenient;
        private final Features _features;
        // lazily constructed when created from annotations
        private transient TimeZone _timezone;
  
        public Value() {
            this("", Shape.ANY, "", "", Features.empty(), null);
        }
        
        public Value(JsonFormat ann) {
            this(ann.pattern(), ann.shape(), ann.locale(), ann.timezone(),
                    Features.construct(ann), ann.lenient().asBoolean());
        }

        public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
                Boolean lenient)
        {
            this(p, sh,
                    (localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?
                            null : new Locale(localeStr),
                    (tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?
                            null : tzStr,
                    null, f, lenient);
        }
        
        public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
                Boolean lenient)
        {
            _pattern = p;
            _shape = (sh == null) ? Shape.ANY : sh;
            _locale = l;
            _timezone = tz;
            _timezoneStr = null;
            _features = (f == null) ? Features.empty() : f;
            _lenient = lenient;
        }

        public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
                Boolean lenient)
        {
            _pattern = p;
            _shape = (sh == null) ? Shape.ANY : sh;
            _locale = l;
            _timezone = tz;
            _timezoneStr = tzStr;
            _features = (f == null) ? Features.empty() : f;
            _lenient = lenient;
        }
    }
}

2. 跟踪进入实际处理类SqlTimeSerializer

SqlTimeSerializer 源码

package com.fasterxml.jackson.databind.ser.std;

import java.io.IOException;
import java.lang.reflect.Type;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;

@JacksonStdImpl
@SuppressWarnings("serial")
public class SqlTimeSerializer
    extends StdScalarSerializer<java.sql.Time>
{
    public SqlTimeSerializer() { super(java.sql.Time.class); }

    @Override
    public void serialize(java.sql.Time value, JsonGenerator g, SerializerProvider provider) throws IOException
    {
        g.writeString(value.toString());
    }

    @Override
    public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
        return createSchemaNode("string", true);
    }
    
    @Override
    public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
        throws JsonMappingException
    {
        visitStringFormat(visitor, typeHint, JsonValueFormat.DATE_TIME);
    }
}

UTF8JsonGenerator源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;

public class UTF8JsonGenerator extends JsonGeneratorImpl {
    private static final byte BYTE_u = 117;
    private static final byte BYTE_0 = 48;
    private static final byte BYTE_LBRACKET = 91;
    private static final byte BYTE_RBRACKET = 93;
    private static final byte BYTE_LCURLY = 123;
    private static final byte BYTE_RCURLY = 125;
    private static final byte BYTE_BACKSLASH = 92;
    private static final byte BYTE_COMMA = 44;
    private static final byte BYTE_COLON = 58;
    private static final int MAX_BYTES_TO_BUFFER = 512;
    private static final byte[] HEX_CHARS = CharTypes.copyHexBytes();
    private static final byte[] NULL_BYTES = new byte[]{110, 117, 108, 108};
    private static final byte[] TRUE_BYTES = new byte[]{116, 114, 117, 101};
    private static final byte[] FALSE_BYTES = new byte[]{102, 97, 108, 115, 101};
    protected final OutputStream _outputStream;
    protected byte _quoteChar;
    protected byte[] _outputBuffer;
    protected int _outputTail;
    protected final int _outputEnd;
    protected final int _outputMaxContiguous;
    protected char[] _charBuffer;
    protected final int _charBufferLength;
    protected byte[] _entityBuffer;
    protected boolean _bufferRecyclable;

    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar) {
        super(ctxt, features, codec);
        this._outputStream = out;
        this._quoteChar = (byte)quoteChar;
        if (quoteChar != '"') {
            this._outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar);
        }

        this._bufferRecyclable = true;
        this._outputBuffer = ctxt.allocWriteEncodingBuffer();
        this._outputEnd = this._outputBuffer.length;
        this._outputMaxContiguous = this._outputEnd >> 3;
        this._charBuffer = ctxt.allocConcatBuffer();
        this._charBufferLength = this._charBuffer.length;
        if (this.isEnabled(Feature.ESCAPE_NON_ASCII)) {
            this.setHighestNonEscapedChar(127);
        }

    }

    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
        super(ctxt, features, codec);
        this._outputStream = out;
        this._quoteChar = (byte)quoteChar;
        if (quoteChar != '"') {
            this._outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar);
        }

        this._bufferRecyclable = bufferRecyclable;
        this._outputTail = outputOffset;
        this._outputBuffer = outputBuffer;
        this._outputEnd = this._outputBuffer.length;
        this._outputMaxContiguous = this._outputEnd >> 3;
        this._charBuffer = ctxt.allocConcatBuffer();
        this._charBufferLength = this._charBuffer.length;
    }

    /** @deprecated */
    @Deprecated
    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out) {
        this(ctxt, features, codec, out, '"');
    }

    /** @deprecated */
    @Deprecated
    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
        this(ctxt, features, codec, out, '"', outputBuffer, outputOffset, bufferRecyclable);
    }


    public void writeString(String text) throws IOException {
        this._verifyValueWrite("write a string");
        if (text == null) {
            this._writeNull();
        } else {
            int len = text.length();
            if (len > this._outputMaxContiguous) {
                this._writeStringSegments(text, true);
            } else {
                if (this._outputTail + len >= this._outputEnd) {
                    this._flushBuffer();
                }

                this._outputBuffer[this._outputTail++] = this._quoteChar;
                this._writeStringSegment((String)text, 0, len);
                if (this._outputTail >= this._outputEnd) {
                    this._flushBuffer();
                }

                this._outputBuffer[this._outputTail++] = this._quoteChar;
            }
        }
    }

    protected final void _verifyValueWrite(String typeMsg) throws IOException {
        int status = this._writeContext.writeValue();
        if (this._cfgPrettyPrinter != null) {
            this._verifyPrettyValueWrite(typeMsg, status);
        } else {
            byte b;
            switch(status) {
            case 0:
            case 4:
            default:
                return;
            case 1:
                b = 44;
                break;
            case 2:
                b = 58;
                break;
            case 3:
                if (this._rootValueSeparator != null) {
                    byte[] raw = this._rootValueSeparator.asUnquotedUTF8();
                    if (raw.length > 0) {
                        this._writeBytes(raw);
                    }
                }

                return;
            case 5:
                this._reportCantWriteValueExpectName(typeMsg);
                return;
            }

            if (this._outputTail >= this._outputEnd) {
                this._flushBuffer();
            }

            this._outputBuffer[this._outputTail++] = b;
        }
    }

    private final void _writeStringSegments(String text, boolean addQuotes) throws IOException {
        if (addQuotes) {
            if (this._outputTail >= this._outputEnd) {
                this._flushBuffer();
            }

            this._outputBuffer[this._outputTail++] = this._quoteChar;
        }

        int left = text.length();

        int len;
        for(int offset = 0; left > 0; left -= len) {
            len = Math.min(this._outputMaxContiguous, left);
            if (this._outputTail + len > this._outputEnd) {
                this._flushBuffer();
            }

            this._writeStringSegment(text, offset, len);
            offset += len;
        }

        if (addQuotes) {
            if (this._outputTail >= this._outputEnd) {
                this._flushBuffer();
            }

            this._outputBuffer[this._outputTail++] = this._quoteChar;
        }

    }

    private final void _writeStringSegments(String text, int offset, int totalLen) throws IOException {
        do {
            int len = Math.min(this._outputMaxContiguous, totalLen);
            if (this._outputTail + len > this._outputEnd) {
                this._flushBuffer();
            }

            this._writeStringSegment(text, offset, len);
            offset += len;
            totalLen -= len;
        } while(totalLen > 0);

    }

    private final void _writeStringSegment(String text, int offset, int len) throws IOException {
        len += offset;
        int outputPtr = this._outputTail;
        byte[] outputBuffer = this._outputBuffer;

        for(int[] escCodes = this._outputEscapes; offset < len; ++offset) {
            int ch = text.charAt(offset);
            if (ch > 127 || escCodes[ch] != 0) {
                break;
            }

            outputBuffer[outputPtr++] = (byte)ch;
        }

        this._outputTail = outputPtr;
        if (offset < len) {
            if (this._characterEscapes != null) {
                this._writeCustomStringSegment2(text, offset, len);
            } else if (this._maximumNonEscapedChar == 0) {
                this._writeStringSegment2(text, offset, len);
            } else {
                this._writeStringSegmentASCII2(text, offset, len);
            }
        }

    }

    private final void _writeUTF8Segments(byte[] utf8, int offset, int totalLen) throws IOException, JsonGenerationException {
        do {
            int len = Math.min(this._outputMaxContiguous, totalLen);
            this._writeUTF8Segment(utf8, offset, len);
            offset += len;
            totalLen -= len;
        } while(totalLen > 0);

    }

    private final void _writeUTF8Segment(byte[] utf8, int offset, int len) throws IOException, JsonGenerationException {
        int[] escCodes = this._outputEscapes;
        int ptr = offset;
        int end = offset + len;

        byte ch;
        do {
            if (ptr >= end) {
                if (this._outputTail + len > this._outputEnd) {
                    this._flushBuffer();
                }

                System.arraycopy(utf8, offset, this._outputBuffer, this._outputTail, len);
                this._outputTail += len;
                return;
            }

            ch = utf8[ptr++];
        } while(ch < 0 || escCodes[ch] == 0);

        this._writeUTF8Segment2(utf8, offset, len);
    }

    private final void _writeUTF8Segment2(byte[] utf8, int offset, int len) throws IOException, JsonGenerationException {
        int outputPtr = this._outputTail;
        if (outputPtr + len * 6 > this._outputEnd) {
            this._flushBuffer();
            outputPtr = this._outputTail;
        }

        byte[] outputBuffer = this._outputBuffer;
        int[] escCodes = this._outputEscapes;
        len += offset;

        while(true) {
            while(offset < len) {
                byte b = utf8[offset++];
                if (b >= 0 && escCodes[b] != 0) {
                    int escape = escCodes[b];
                    if (escape > 0) {
                        outputBuffer[outputPtr++] = 92;
                        outputBuffer[outputPtr++] = (byte)escape;
                    } else {
                        outputPtr = this._writeGenericEscape(b, outputPtr);
                    }
                } else {
                    outputBuffer[outputPtr++] = b;
                }
            }

            this._outputTail = outputPtr;
            return;
        }
    }

}

java.sql.Time源码:

package java.sql;

import java.time.Instant;
import java.time.LocalTime;

/**
 * 

A thin wrapper around the java.util.Date class that allows the JDBC * API to identify this as an SQL TIME value. The Time * class adds formatting and * parsing operations to support the JDBC escape syntax for time * values. *

The date components should be set to the "zero epoch" * value of January 1, 1970 and should not be accessed. */ public class Time extends java.util.Date { /** * Constructs a Time object initialized with the * given values for the hour, minute, and second. * The driver sets the date components to January 1, 1970. * Any method that attempts to access the date components of a * Time object will throw a * java.lang.IllegalArgumentException. *

* The result is undefined if a given argument is out of bounds. * * @param hour 0 to 23 * @param minute 0 to 59 * @param second 0 to 59 * * @deprecated Use the constructor that takes a milliseconds value * in place of this constructor */ @Deprecated public Time(int hour, int minute, int second) { super(70, 0, 1, hour, minute, second); } /** * Constructs a Time object using a milliseconds time value. * * @param time milliseconds since January 1, 1970, 00:00:00 GMT; * a negative number is milliseconds before * January 1, 1970, 00:00:00 GMT */ public Time(long time) { super(time); } /** * Formats a time in JDBC time escape format. * * @return a String in hh:mm:ss format */ @SuppressWarnings("deprecation") public String toString () { int hour = super.getHours(); int minute = super.getMinutes(); int second = super.getSeconds(); String hourString; String minuteString; String secondString; if (hour < 10) { hourString = "0" + hour; } else { hourString = Integer.toString(hour); } if (minute < 10) { minuteString = "0" + minute; } else { minuteString = Integer.toString(minute); } if (second < 10) { secondString = "0" + second; } else { secondString = Integer.toString(second); } return (hourString + ":" + minuteString + ":" + secondString); } }

java.util.Date源码:

package java.util;

import java.text.DateFormat;
import java.time.LocalDate;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.lang.ref.SoftReference;
import java.time.Instant;
import sun.util.calendar.BaseCalendar;
import sun.util.calendar.CalendarDate;
import sun.util.calendar.CalendarSystem;
import sun.util.calendar.CalendarUtils;
import sun.util.calendar.Era;
import sun.util.calendar.Gregorian;
import sun.util.calendar.ZoneInfo;

/**
 * The class Date represents a specific instant
 * in time, with millisecond precision.
 * 

* Prior to JDK 1.1, the class Date had two additional * functions. It allowed the interpretation of dates as year, month, day, hour, * minute, and second values. It also allowed the formatting and parsing * of date strings. Unfortunately, the API for these functions was not * amenable to internationalization. As of JDK 1.1, the * Calendar class should be used to convert between dates and time * fields and the DateFormat class should be used to format and * parse date strings. * The corresponding methods in Date are deprecated. *

* Although the Date class is intended to reflect * coordinated universal time (UTC), it may not do so exactly, * depending on the host environment of the Java Virtual Machine. * Nearly all modern operating systems assume that 1 day = * 24 × 60 × 60 = 86400 seconds * in all cases. In UTC, however, about once every year or two there * is an extra second, called a "leap second." The leap * second is always added as the last second of the day, and always * on December 31 or June 30. For example, the last minute of the * year 1995 was 61 seconds long, thanks to an added leap second. * Most computer clocks are not accurate enough to be able to reflect * the leap-second distinction. *

* Some computer standards are defined in terms of Greenwich mean * time (GMT), which is equivalent to universal time (UT). GMT is * the "civil" name for the standard; UT is the * "scientific" name for the same standard. The * distinction between UTC and UT is that UTC is based on an atomic * clock and UT is based on astronomical observations, which for all * practical purposes is an invisibly fine hair to split. Because the * earth's rotation is not uniform (it slows down and speeds up * in complicated ways), UT does not always flow uniformly. Leap * seconds are introduced as needed into UTC so as to keep UTC within * 0.9 seconds of UT1, which is a version of UT with certain * corrections applied. There are other time and date systems as * well; for example, the time scale used by the satellite-based * global positioning system (GPS) is synchronized to UTC but is * not adjusted for leap seconds. An interesting source of * further information is the U.S. Naval Observatory, particularly * the Directorate of Time at: *

 *     http://tycho.usno.navy.mil
 * 
*

* and their definitions of "Systems of Time" at: *

 *     http://tycho.usno.navy.mil/systime.html
 * 
*

* In all methods of class Date that accept or return * year, month, date, hours, minutes, and seconds values, the * following representations are used: *

    *
  • A year y is represented by the integer * y - 1900. *
  • A month is represented by an integer from 0 to 11; 0 is January, * 1 is February, and so forth; thus 11 is December. *
  • A date (day of month) is represented by an integer from 1 to 31 * in the usual manner. *
  • An hour is represented by an integer from 0 to 23. Thus, the hour * from midnight to 1 a.m. is hour 0, and the hour from noon to 1 * p.m. is hour 12. *
  • A minute is represented by an integer from 0 to 59 in the usual manner. *
  • A second is represented by an integer from 0 to 61; the values 60 and * 61 occur only for leap seconds and even then only in Java * implementations that actually track leap seconds correctly. Because * of the manner in which leap seconds are currently introduced, it is * extremely unlikely that two leap seconds will occur in the same * minute, but this specification follows the date and time conventions * for ISO C. *
*

* In all cases, arguments given to methods for these purposes need * not fall within the indicated ranges; for example, a date may be * specified as January 32 and is interpreted as meaning February 1. * * @author James Gosling * @author Arthur van Hoff * @author Alan Liu * @see java.text.DateFormat * @see java.util.Calendar * @see java.util.TimeZone * @since JDK1.0 */ public class Date implements java.io.Serializable, Cloneable, Comparable<Date> { private static final BaseCalendar gcal = CalendarSystem.getGregorianCalendar(); private static BaseCalendar jcal; private transient long fastTime; private transient BaseCalendar.Date cdate; private static int defaultCenturyStart; /** * Returns the hour represented by this Date object. The * returned value is a number (0 through 23) * representing the hour within the day that contains or begins * with the instant in time represented by this Date * object, as interpreted in the local time zone. * * @return the hour represented by this date. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by Calendar.get(Calendar.HOUR_OF_DAY). */ @Deprecated public int getHours() { return normalize().getHours(); } /** * Returns the number of minutes past the hour represented by this date, * as interpreted in the local time zone. * The value returned is between 0 and 59. * * @return the number of minutes past the hour represented by this date. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by Calendar.get(Calendar.MINUTE). */ @Deprecated public int getMinutes() { return normalize().getMinutes(); } private final BaseCalendar.Date normalize() { if (cdate == null) { BaseCalendar cal = getCalendarSystem(fastTime); cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime, TimeZone.getDefaultRef()); return cdate; } // Normalize cdate with the TimeZone in cdate first. This is // required for the compatible behavior. if (!cdate.isNormalized()) { cdate = normalize(cdate); } // If the default TimeZone has changed, then recalculate the // fields with the new TimeZone. TimeZone tz = TimeZone.getDefaultRef(); if (tz != cdate.getZone()) { cdate.setZone(tz); CalendarSystem cal = getCalendarSystem(cdate); cal.getCalendarDate(fastTime, cdate); } return cdate; } // fastTime and the returned data are in sync upon return. private final BaseCalendar.Date normalize(BaseCalendar.Date date) { int y = date.getNormalizedYear(); int m = date.getMonth(); int d = date.getDayOfMonth(); int hh = date.getHours(); int mm = date.getMinutes(); int ss = date.getSeconds(); int ms = date.getMillis(); TimeZone tz = date.getZone(); // If the specified year can't be handled using a long value // in milliseconds, GregorianCalendar is used for full // compatibility with underflow and overflow. This is required // by some JCK tests. The limits are based max year values - // years that can be represented by max values of d, hh, mm, // ss and ms. Also, let GregorianCalendar handle the default // cutover year so that we don't need to worry about the // transition here. if (y == 1582 || y > 280000000 || y < -280000000) { if (tz == null) { tz = TimeZone.getTimeZone("GMT"); } GregorianCalendar gc = new GregorianCalendar(tz); gc.clear(); gc.set(GregorianCalendar.MILLISECOND, ms); gc.set(y, m-1, d, hh, mm, ss); fastTime = gc.getTimeInMillis(); BaseCalendar cal = getCalendarSystem(fastTime); date = (BaseCalendar.Date) cal.getCalendarDate(fastTime, tz); return date; } BaseCalendar cal = getCalendarSystem(y); if (cal != getCalendarSystem(date)) { date = (BaseCalendar.Date) cal.newCalendarDate(tz); date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); } // Perform the GregorianCalendar-style normalization. fastTime = cal.getTime(date); // In case the normalized date requires the other calendar // system, we need to recalculate it using the other one. BaseCalendar ncal = getCalendarSystem(fastTime); if (ncal != cal) { date = (BaseCalendar.Date) ncal.newCalendarDate(tz); date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); fastTime = ncal.getTime(date); } return date; } /** * Returns the Gregorian or Julian calendar system to use with the * given date. Use Gregorian from October 15, 1582. * * @param year normalized calendar year (not -1900) * @return the CalendarSystem to use for the specified date */ private static final BaseCalendar getCalendarSystem(int year) { if (year >= 1582) { return gcal; } return getJulianCalendar(); } private static final BaseCalendar getCalendarSystem(long utc) { // Quickly check if the time stamp given by `utc' is the Epoch // or later. If it's before 1970, we convert the cutover to // local time to compare. if (utc >= 0 || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER - TimeZone.getDefaultRef().getOffset(utc)) { return gcal; } return getJulianCalendar(); } private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) { if (jcal == null) { return gcal; } if (cdate.getEra() != null) { return jcal; } return gcal; } synchronized private static final BaseCalendar getJulianCalendar() { if (jcal == null) { jcal = (BaseCalendar) CalendarSystem.forName("julian"); } return jcal; } }

Jackson实际调用了SqlTimeSerializer类的serialize()方法进行序列化,进一步跟踪看到调用java.sql.Time类的toString()的结果作为参数给了UTF8JsonGenerator类的writeString()方法,进行UTF8格式化。而通过对java.sql.Time类的toString()方法源码阅读,可以发现其中获取是调用父类java.util.Date的方法获取小时,分钟和秒,但是此些方法中调用的normalize()方法涉及时区问题,可能由于时区问题,导致获取时间和所需产生时差。

normalize()涉及时区代码

    private final BaseCalendar.Date normalize() {
        if (cdate == null) {
            BaseCalendar cal = getCalendarSystem(fastTime);
            cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,
                                                            TimeZone.getDefaultRef());
            return cdate;
        }

        // Normalize cdate with the TimeZone in cdate first. This is
        // required for the compatible behavior.
        if (!cdate.isNormalized()) {
            cdate = normalize(cdate);
        }

        // If the default TimeZone has changed, then recalculate the
        // fields with the new TimeZone.
        TimeZone tz = TimeZone.getDefaultRef();
        if (tz != cdate.getZone()) {
            cdate.setZone(tz);
            CalendarSystem cal = getCalendarSystem(cdate);
            cal.getCalendarDate(fastTime, cdate);
        }
        return cdate;
    }

3. Jacksonjava.util.Date类进行时区处理源码

        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
                BeanProperty property)
           throws JsonMappingException
        {
            final JsonFormat.Value format = findFormatOverrides(ctxt, property,
                    handledType());

            if (format != null) {
                TimeZone tz = format.getTimeZone();
                final Boolean lenient = format.getLenient();

                // First: fully custom pattern?
                if (format.hasPattern()) {
                    final String pattern = format.getPattern();
                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                    // 时间格式化处理
                    SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
                    if (tz == null) {
                        tz = ctxt.getTimeZone();
                    }
                    // 时区设置
                    df.setTimeZone(tz);
                    if (lenient != null) {
                        df.setLenient(lenient);
                    }
                    return withDateFormat(df, pattern);
                }
                // But if not, can still override timezone
                if (tz != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withTimeZone(tz);
                        std = std.withLocale(loc);
                        if (lenient != null) {
                            std = std.withLenient(lenient);
                        }
                        df = std;
                    } else {
                        // otherwise need to clone, re-set timezone:
                        df = (DateFormat) df.clone();
                        df.setTimeZone(tz);
                        if (lenient != null) {
                            df.setLenient(lenient);
                        }
                    }
                    return withDateFormat(df, _formatString);
                }
                // or maybe even just leniency?
                if (lenient != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    String pattern = _formatString;
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withLenient(lenient);
                        df = std;
                        pattern = std.toPattern();
                    } else {
                        // otherwise need to clone,
                        df = (DateFormat) df.clone();
                        df.setLenient(lenient);
                        if (df instanceof SimpleDateFormat) {
                            ((SimpleDateFormat) df).toPattern();
                        }
                    }
                    if (pattern == null) {
                        pattern = "[unknown]";
                    }
                    return withDateFormat(df, pattern);
                }
            }
            return this;
        }

实际时区处理代码如下:

                if (format.hasPattern()) {
                    final String pattern = format.getPattern();
                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                    // 时间格式化处理
                    SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
                    if (tz == null) {
                        tz = ctxt.getTimeZone();
                    }
                    // 时区设置
                    df.setTimeZone(tz);
                    if (lenient != null) {
                        df.setLenient(lenient);
                    }
                    return withDateFormat(df, pattern);
                }

总结

通过以上的源码分析,得出你觉得的觉得不是真的觉得,Jackson就在对java.sql.Time序列化中根本不考虑时区问题。


解决方案

建议你自己自定义个处理类对java.sql.Time进行处理,支持处理指定时区。

以下是我写的序列化工具,希望可以对你有帮助!
java.sql.Time 字段使用 Jackson 进行序列化


你可能感兴趣的:(学习,框架,Spring,java,Jackson)