AFNetworking源碼閱讀4——請求序列化

前言

序列化,百度百科的解釋摘抄如下:

序列化 (Serialization)將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲或傳輸?shù)男问降倪^程。在序列化期間,對象將其當(dāng)前狀態(tài)寫入到臨時或持久性存儲區(qū)。以后,可以通過從存儲區(qū)中讀取或反序列化對象的狀態(tài),重新創(chuàng)建該對象。
1、以某種存儲形式使自定義對象持久化;
2、將對象從一個地方傳遞到另一個地方。
3、使程序更具維護(hù)性。

簡單點說就是將對象轉(zhuǎn)換為二進(jìn)制流,以便存儲或傳輸,且日后也能從二進(jìn)制流轉(zhuǎn)換回對象。 所以我們今天要說的請求序列化,說到底就是造一個可以在網(wǎng)絡(luò)上傳遞(被序列化)的、合法可用的請求。這其中就包括了參數(shù)的處理請求頭的設(shè)置等。不管怎樣的代碼,萬變不離其宗,它的核心就是這個知識點。所以理解了這個,再去看代碼就容易多了。
我們知道,GET請求和POST請求對于參數(shù)是不同的處理。GET請求是多個參數(shù)之間以&相連,且單個參數(shù)的鍵值間以=連接,并將參數(shù)以?開頭,經(jīng)過編碼再追加在url后面,而POST卻是將其放入請求體HTTPBody的。
另外一個HTTP連接的請求頭HTTPHeaderField設(shè)置也是非常重要的,它提供了很多字段,用于不同場景,不同特征下的使用。

生成請求request的地方是在AFHTTPSessionManager中,當(dāng)時我們說了該類有名為序列化器的屬性requestSerializer,然后調(diào)用該序列化器的方法生成了一個request。當(dāng)時,因為這是AFHTTPRequestSerializer類中的方法,所以對于具體實現(xiàn)沒有研究。今天我們的工作就是這個。


源碼

先來看頭文件AFHTTPRequestSerializer.h。它繼承自NSObject,實現(xiàn)了AFURLRequestSerialization協(xié)議。

首先看頭文件中定義的屬性:


@property (nonatomic, assign) NSStringEncoding stringEncoding;

@property (nonatomic, assign) BOOL allowsCellularAccess;  // 是否允許使用蜂窩網(wǎng)絡(luò),默認(rèn)YES

@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy; // 緩存策略

@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;  // 是否處理Cookie

@property (nonatomic, assign) BOOL HTTPShouldUsePipelining; // 是否開啟管線化

@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

@property (nonatomic, assign) NSTimeInterval timeoutInterval;

@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;

@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

stringEncoding意為序列化參數(shù)時的字符串編碼,默認(rèn)是NSUTF8StringEncoding
HTTPRequestHeaders是個字典,意為即將被序列化的HTTP的請求頭。默認(rèn)包括Accept-LanguageUser-Agent等字段;
HTTPMethodsEncodingParametersInURI是個集合對象,它表示序列化時參數(shù)被追加在url里的請求方式的集合,想想也知道,它的元素應(yīng)該是GET、Head。
其他幾個屬性都比較好理解,而且注釋寫得很清楚了,不解釋了。

接著看在頭文件中暴露的前幾個方法:


+ (instancetype)serializer;

- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;

- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;

- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password;
- (void)clearAuthorizationHeader;

- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;

首先是初始化方法,return一個該類的實例對象;然后是兩個對請求頭字典操作的兩個方法;再接著是兩個關(guān)于登錄認(rèn)證的方法;然后是一個設(shè)置序列化類型的方法,代表遵循什么樣的規(guī)則進(jìn)行queryString轉(zhuǎn)換。參數(shù)是個枚舉,但是這個枚舉只有一個值AFHTTPRequestQueryStringDefaultStyle。最后一個方法提供了以block形式自定義queryString轉(zhuǎn)換的接口,也就是說可以通過block回調(diào)的方式讓調(diào)用者以自己的方式完成queryString的轉(zhuǎn)換。

最后就剩下三個核心方法了。其中第一個方法便是我們在前面已經(jīng)接觸過的,由HTTP method、URLStringparameters返回一個請求request。

下面代碼注釋非常占篇幅,但是注釋的很好,舍不得刪。

Creates an NSMutableURLRequest object with the specified HTTP method and URL string.

If the HTTP method is GET, HEAD, or DELETE, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the parameterEncoding property, and set as the request body.

/**
 Creates an `NSMutableURLRequest` object with the specified HTTP method and URL string.

 If the HTTP method is `GET`, `HEAD`, or `DELETE`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body.

 @param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. This parameter must not be `nil`.
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body.
 @param error The error that occurred while constructing the request.

 @return An `NSMutableURLRequest` object.
 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(nullable id)parameters
                                     error:(NSError * _Nullable __autoreleasing *)error;

/**
 Creates an `NSMutableURLRequest` object with the specified HTTP method and URLString, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2

 Multipart form requests are automatically streamed, reading files directly from disk along with in-memory data in a single HTTP body. The resulting `NSMutableURLRequest` object has an `HTTPBodyStream` property, so refrain from setting `HTTPBodyStream` or `HTTPBody` on this request object, as it will clear out the multipart form body stream.

 @param method The HTTP method for the request. This parameter must not be `GET` or `HEAD`, or `nil`.
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded and set in the request HTTP body.
 @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
 @param error The error that occurred while constructing the request.

 @return An `NSMutableURLRequest` object
 */
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(nullable NSDictionary <NSString *, id> *)parameters
                              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError * _Nullable __autoreleasing *)error;

/**
 Creates an `NSMutableURLRequest` by removing the `HTTPBodyStream` from a request, and asynchronously writing its contents into the specified file, invoking the completion handler when finished.

 @param request The multipart form request. The `HTTPBodyStream` property of `request` must not be `nil`.
 @param fileURL The file URL to write multipart form contents to.
 @param handler A handler block to execute.

 @discussion There is a bug in `NSURLSessionTask` that causes requests to not send a `Content-Length` header when streaming contents from an HTTP body, which is notably problematic when interacting with the Amazon S3 webservice. As a workaround, this method takes a request constructed with `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:`, or any other request with an `HTTPBodyStream`, writes the contents to the specified file and returns a copy of the original request with the `HTTPBodyStream` property set to `nil`. From here, the file can either be passed to `AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler:`, or have its contents read into an `NSData` that's assigned to the `HTTPBody` property of the request.

 @see https://github.com/AFNetworking/AFNetworking/issues/1398
 */
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(nullable void (^)(NSError * _Nullable error))handler;

頭文件中基本就是以上東西,現(xiàn)在我們應(yīng)該如饑似渴,迫不及待的開始看.m文件了。

@interface AFHTTPRequestSerializer ()

@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
@property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue;
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;

@end

@implementation AFHTTPRequestSerializer

+ (instancetype)serializer {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

    NSString *userAgent = nil;
#if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
    if (userAgent) {
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    // 在AFHTTPRequestSerializer的初始化方法init中就初始化了集合mutableObservedChangedKeyPaths。并且遍歷AFHTTPRequestSerializerObservedKeyPaths數(shù)組,為每一項添加觀察
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

    return self;
}

- (void)dealloc {
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
        }
    }
}

首先是初始化方法,乍看這里感覺好復(fù)雜,其實一點不復(fù)雜。初始化方法說到底就是初始化。它首先初始化了幾個屬性。stringEncoding屬性初始化默認(rèn)為NSUTF8StringEncoding;然后初始化了用于裝HTTP請求頭屬性的字典mutableHTTPRequestHeaders;還初始化了修改請求頭時而專門創(chuàng)建的隊列requestHeaderModificationQueue。
接下來是一大串亂糟糟的代碼,仔細(xì)看就明白了:此時既然已初始化了裝置請求頭屬性的字典,那不就可以先設(shè)置一些可以設(shè)置的請求頭屬性了。即Accept-LanguageUser-Agent。

初始化方法的最后為序列化需要觀察的屬性添加了監(jiān)聽。這里是指哪些需要觀察的屬性字段呢?從上面代碼可以看到,它是由一個C函數(shù)獲取的AFHTTPRequestSerializerObservedKeyPaths(),返回了一個數(shù)組,數(shù)組便是需要觀察的屬性字段的數(shù)組。

而當(dāng)我們設(shè)置了這些HTTP配置屬性的值時,就會觸發(fā)觀察回調(diào)的方法,在此方面里將該屬性字符串放入了mutableObservedChangedKeyPaths數(shù)組。代碼如下:

// 當(dāng)我們設(shè)置了這些HTTP配置屬性的值時,就會觸發(fā)觀察回調(diào)方法。并將該屬性字符串放入mutableObservedChangedKeyPaths集合
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

接著往下看:

- (NSDictionary *)HTTPRequestHeaders {
    NSDictionary __block *value;
    dispatch_sync(self.requestHeaderModificationQueue, ^{
        value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
    });
    return value;
}

- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
        [self.mutableHTTPRequestHeaders setValue:value forKey:field];
    });
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    NSString __block *value;
    dispatch_sync(self.requestHeaderModificationQueue, ^{
        value = [self.mutableHTTPRequestHeaders valueForKey:field];
    });
    return value;
}

- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

- (void)clearAuthorizationHeader {
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
        [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
    });
}

#pragma mark -

- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
    self.queryStringSerializationStyle = style;
    self.queryStringSerialization = nil;
}

- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
    self.queryStringSerialization = block;
}

上面這些代碼沒什么可說的,接著往下看,就到了最核心的方法了:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    /*
     為該HTTP的request設(shè)置配置屬性

     方法AFHTTPRequestSerializerObservedKeyPaths()返回一個數(shù)組,代表我們需要關(guān)注的HTTP配置屬性。
     而mutableObservedChangedKeyPaths集合代表我們已設(shè)置的配置屬性,
     */
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    /*
     將傳入的parameters進(jìn)行編碼,添加到request中
     */
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
    return mutableRequest;
}

開始閱讀該方法。可以看到,首先是對幾個參數(shù)的斷言。然后以URLString生成url,再以url生成mutableRequest,并為其該HTTP請求設(shè)置配置屬性。然后它是將最核心的模塊封裝在了一個本類的方法里。我們跳入該方法,準(zhǔn)備繼續(xù)閱讀,此時發(fā)現(xiàn)不光本類中有這個方法,它的幾個子類中也實現(xiàn)了該方法。原來這個方法便是定義在AFURLRequestSerialization協(xié)議中的方法。在不同的子類中有不同的實現(xiàn),用以實現(xiàn)不同的功能。下面是本類中該方法的實現(xiàn):

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    /*
     1.為request設(shè)置請求頭
     */
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    
    
    /*
     2.參數(shù)序列化處理(將參數(shù)字典解析為url query)
     */
    NSString *query = nil;
    if (parameters) {
        // 若自定義了queryStringSerialization,那么就使用自定義的queryStringSerialization構(gòu)建方式
        if (self.queryStringSerialization)
        {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError); // 通過queryStringSerialization構(gòu)建query字符串

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        }
        else
        {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters); // 由參數(shù)生成url的query部分
                    break;
            }
        }
    }

    // HTTPMethodsEncodingParametersInURI是個NSSet,裝了"GET"、"HEAD"、"DELETE"請求方式(在該類的初始化方法里初始化了),因為這幾個請求方式的query是直接拼接在url后面的。
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    }
    else // 若請求方式為"POST"、"PUT"等,則需要將query設(shè)置到http body上
    {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; // 將query string編碼
    }

    return mutableRequest;
}

該方法里的脈絡(luò)很清晰,分為兩個步驟。第一步便是為request設(shè)置請求頭屬性;第二步是參數(shù)序列化處理:先看是否實現(xiàn)自定義的序列化處理block,若有,則調(diào)用自定義的block,讓使用者自己實現(xiàn)參數(shù)序列化;若無,則調(diào)用AFQueryStringFromParameters()方法,由參數(shù)字典轉(zhuǎn)換為query string。關(guān)于這個函數(shù)如何將字典解析為query string的釋義,這篇文章已經(jīng)解釋得很好:AFNetworking源碼閱讀(二)。總之,現(xiàn)在有了query string了,但還是要根據(jù)HTTP method的不同,將其放在正確的位置上。方法里接下來的代碼就是完成這部分的。

值得注意的是在HTTP method為POST等其他時,它設(shè)置Content-Typeapplication/x-www-form-urlencoded,這個代表什么意思呢?我趕緊百度了下。收集了以下干貨資料:
四種常見的 POST 提交數(shù)據(jù)方式
HTTP協(xié)議之multipart/form-data請求分析
關(guān)于 Content-Type:application/x-www-form-urlencoded 和 Content-Type:multipart/related


結(jié)尾

剛說到了Content-Type了,但時間不早了,明天把這部分補充完整。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,533評論 19 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,332評論 6 13
  • AFHTTPRequestOperationManager 網(wǎng)絡(luò)傳輸協(xié)議UDP、TCP、Http、Socket、X...
    Carden閱讀 5,316評論 0 12
  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 11,175評論 0 24
  • 我終于遞交了辭呈,把公司里的私人物品全都?xì)w納在一個小小的灰色的手提箱里,像被遺棄了的小狗,茫然不知歸屬。 我成了待...
    _微微閱讀 401評論 0 1

友情鏈接更多精彩內(nèi)容