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

news2024/9/27 19:20:12

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 打断点一步步跟踪

JsonFormat
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;

/**
 * <P>A thin wrapper around the <code>java.util.Date</code> class that allows the JDBC
 * API to identify this as an SQL <code>TIME</code> value. The <code>Time</code>
 * class adds formatting and
 * parsing operations to support the JDBC escape syntax for time
 * values.
 * <p>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 <code>Time</code> 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
     * <code>Time</code> object will throw a
     * <code>java.lang.IllegalArgumentException</code>.
     * <P>
     * 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 <code>Time</code> 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 <code>String</code> 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 <code>Date</code> represents a specific instant
 * in time, with millisecond precision.
 * <p>
 * Prior to JDK&nbsp;1.1, the class <code>Date</code> 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&nbsp;1.1, the
 * <code>Calendar</code> class should be used to convert between dates and time
 * fields and the <code>DateFormat</code> class should be used to format and
 * parse date strings.
 * The corresponding methods in <code>Date</code> are deprecated.
 * <p>
 * Although the <code>Date</code> 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&nbsp;day&nbsp;=
 * 24&nbsp;&times;&nbsp;60&nbsp;&times;&nbsp;60&nbsp;= 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.
 * <p>
 * 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
 * <i>not</i> adjusted for leap seconds. An interesting source of
 * further information is the U.S. Naval Observatory, particularly
 * the Directorate of Time at:
 * <blockquote><pre>
 *     <a href=http://tycho.usno.navy.mil>http://tycho.usno.navy.mil</a>
 * </pre></blockquote>
 * <p>
 * and their definitions of "Systems of Time" at:
 * <blockquote><pre>
 *     <a href=http://tycho.usno.navy.mil/systime.html>http://tycho.usno.navy.mil/systime.html</a>
 * </pre></blockquote>
 * <p>
 * In all methods of class <code>Date</code> that accept or return
 * year, month, date, hours, minutes, and seconds values, the
 * following representations are used:
 * <ul>
 * <li>A year <i>y</i> is represented by the integer
 *     <i>y</i>&nbsp;<code>-&nbsp;1900</code>.
 * <li>A month is represented by an integer from 0 to 11; 0 is January,
 *     1 is February, and so forth; thus 11 is December.
 * <li>A date (day of month) is represented by an integer from 1 to 31
 *     in the usual manner.
 * <li>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.
 * <li>A minute is represented by an integer from 0 to 59 in the usual manner.
 * <li>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.
 * </ul>
 * <p>
 * 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 <tt>Date</tt> object. The
     * returned value is a number (<tt>0</tt> through <tt>23</tt>)
     * representing the hour within the day that contains or begins
     * with the instant in time represented by this <tt>Date</tt>
     * 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 <code>Calendar.get(Calendar.HOUR_OF_DAY)</code>.
     */
    @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 <code>0</code> and <code>59</code>.
     *
     * @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 <code>Calendar.get(Calendar.MINUTE)</code>.
     */
    @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实际调用了serialize()方法进行序列化,进一步根据看到实际把java.sql.Time的字符化作为参数调用了UTF8JsonGeneratorwriteString()方法,进行UTF8格式化。而通过对java.sql.TimetoString()源码阅读,可以发现其中获取是调用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进行处理,支持处理指定时区。我这两天也会自己写个出来,并发布。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/688505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RTSP视频流相关的一些操作

播放rtsp camera 内容 端口554在网络通信中用于Real Time Streaming Protocol(RTSP)。 gst-launch-1.0 playbin urirtsp://admin:WANGfengtu1210.0.20.190:554/client0x gst-launch-1.0 playbin urirtsp://admin:WANGfengtu1210.0.20.61:554/client1xgst-launch-1.0 rtspsrc …

基于Arduino UNO的循迹小车

目录 1.analogWrite函数的使用 2.红外循迹模块介绍 3.循迹小车代码实现 4.实物示例 1.analogWrite函数的使用 用analogWrite来替换digitalWrite 说明 将一个模拟数值写进Arduino引脚。这个操作可以用来控制LED的亮度, 或者控制电机的转速. 在Arduino UNO控制器中&#…

关于二叉树的操作,详细操作与实现方法

树是数据结构中的重中之重&#xff0c;尤其以各类二叉树为学习的难点。在面试环节中&#xff0c;二叉树也是必考的模块。本文主要讲二叉树操作的相关知识&#xff0c;梳理面试常考的内容。一起来复习吧。 本篇针对面试中常见的二叉树操作作个总结&#xff1a; 前序遍历&#x…

Kubernetes(k8s)容器编排控制器使用

目录 1 Pod控制器1.1 Pod控制器是什么1.2 Pod和Pod控制器1.3 控制器的必要性1.4 常见的控制器1.4.1 ReplicaSet1.4.2 Deployment1.4.3 DaemonSet 2 ReplicaSet控制器2.1 ReplicaSet概述2.2 ReplicaSet功能2.2.1 精确反应期望值2.2.2 保证高可用2.2.3 弹性伸缩 2.3 创建ReplicaS…

专项练习12

目录 一、选择题 1、JavaScript中定义var a"40",var b7,则执行a%b会得到&#xff1f; 2、下面哪个选项中的对象与浏览列表有关&#xff08; &#xff09; 3、下面哪一个语句可以实现在jQuery中找到所有元素的同辈元素&#xff1f; 4、如何阻止IE和各大浏览器默认行为…

CVSS4.0将于2023年底正式发布

通用漏洞评分系统(CVSS)是一种流行的、标准化的方法&#xff0c;用于评估数字系统安全漏洞的严重程度。由事件反应和安全小组论坛(FIRST)开发&#xff0c;它为安全专业人员提供了评估和优先排序风险的一致方法。 目前的CVSS v3.0已经运行了十多年&#xff0c;但因其复杂性和灵…

Jenkins 持续集成:Linux 系统 两台机器互相免密登录

背景知识 我们把public key放在远程系统合适的位置&#xff0c;然后从本地开始进行ssh连接。 此时&#xff0c;远程的sshd会产生一个随机数并用我们产生的public key进行加密后发给本地&#xff0c;本地会用private key进行解密并把这个随机数发回给远程系统。 最后&#xf…

ModaHub魔搭社区:向量数据库MIlvus服务端配置(四)

目录 常见问题 常见问题 除了配置文件外&#xff0c;怎样可以判断我确实在使用 GPU 做搜索&#xff1f; 有以下三种方式&#xff1a; 使用 nvidia-smi 命令查看 GPU 使用情况。用 Prometheus 配置&#xff0c;详见 使用 Grafana 展示监控指标 > 系统运行指标。使用 Milv…

一文教你Mysql如何性能优化

Mysql性能优化 Mysql性能优化 性能优化维度 数据库优化思路 应急调优的思路&#xff1a; 针对突然的业务办理卡顿&#xff0c;无法进行正常的业务处理&#xff01;需要立马解决的场景&#xff01; show processlist&#xff08;查看连接session状态&#xff09; explain(分…

hudi系列-timeline service

Timeline Service(时间线服务)是hudi的一个组件,用于暴露文件系统视图接口给客户端,是一个基于Javalin+Jetty实现的web服务。当客户端使用远程文件系统视图(RemoteHoodieTableFileSystemView)时,就是访问时间线服务http接口 默认情况下,如果开启了时间线服务,则它运行在…

支付中心“收银台“设计方案

01.收银台的产品架构 重点收银台架构的三个方面&#xff1a; 1.公司所支持的收银台种类以未来拓展倾向 2.支付方式的枚举及根据业务发展预判拓展倾向 3.收银台服务端的能力建设规划和选择 02.收银台的业务架构 收银台&#xff0c;是支付的起点&#xff0c;所以无论是何种…

接口自动化测试学习笔记分享(附上视频教程供你学习)

目录 接口自动化测试框架介绍 目录 接口测试场景 自动化测试场景 接口测试在分层测试中的位置 接口自动化测试与 Web/App 自动化测试对比 接口自动化测试与 Web/App 自动化测试对比 接口测试工具类型 为什么推荐 Requests Requests 优势 Requests 环境准备 接口请求…

Java安全——应用安全

Java安全 Java 应用安全 JCE&#xff08;Java Cryptography Extension&#xff09;java加密扩展包 Java Cryptography Extension&#xff08;JCE&#xff09;是一个可选的Java标准扩展&#xff0c;提供了一组用于加密、密钥生成和密钥协商等功能的类和接口。JCE包含了导入、生…

【ChatGpt】解决视频框交换中的平滑过渡的问题

【ChatGpt】解决视频框交换中的平滑过渡的问题 问题抽象chatgpt 看看直接给参考代码 解决效果 问题 在视频的播放中&#xff0c;我们想调换下容器的位置 &#xff0c;在互调的过程中&#xff0c;如果需要重新进行数据的初始化&#xff0c;获取与加载&#xff0c;就会很慢&…

RocketMQ --- 原理篇

一、专业术语 Producer 消息生产者&#xff0c;负责产生消息&#xff0c;一般由业务系统负责产生消息。 Consumer 消息消费者&#xff0c;负责消费消息&#xff0c;一般是后台系统负责异步消费。 Push Consumer Consumer 的一种&#xff0c;应用通常向 Consumer 对象注册一个…

基于JavaScript的百度AI的人脸识别微信小程序(深度学习+机器视觉)含全部工程源码及视频演示(仅供学习)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. Access token 获取2. 人脸注册3. 人脸删除4. 人脸识别 系统测试工程源代码下载其它资料下载 前言 本项目采用了百度AI的训练模型&#xff0c;利用图像识别接口返回结果&#xff0c;旨在实现人脸在库中的判断&am…

制造业质量管理如何实现数字化转型?这份指南讲透了

一、什么是制造业质量管理 制造业质量管理是现代制造业非常重要的一个方面。它包括了一系列的活动和方法&#xff0c;以确保制造产品或提供服务的过程中&#xff0c;实现高质量标准的目标。 制造业质量管理包括质量规划、控制和改进等各种方法和工具&#xff0c;以确保产品或…

4.25 IO多路复用简介 4.26select API介绍 4.27 select代码编写

4.25 IO多路复用简介 IO多路复用使得程序能同时监听多个文件描述符&#xff0c;能够提高程序的性能&#xff0c;Linux下实现IO多路复用的系统调用主要有select、poll和epoll。 4.26select API介绍 主旨思想&#xff1a; 1、首先构造一个关于文件描述符的列表&#xff…

【高危】Openfire权限绕过漏洞(POC公开)

漏洞描述 Openfire是Java开发且基于XMPP&#xff08;前称Jabber&#xff0c;即时通讯协议&#xff09;的开源实时协作&#xff08;RTC&#xff09;服务器。 在受影响版本中&#xff0c;由于路径验证机制存在缺陷&#xff0c;攻击者可以通过/setup/setup-s/%u002e%u002e/%u002e…

基于Python所写的学生管理系统

点击下方链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87950397?spm1001.2014.3001.5503 《学生信息管理系统》程序使用说明 在IDLE中运行《学生信息管理系统》即可进入如图1所示的系统主界面。在该界面中可以选择要使用功能对应的菜单进行…