App Engine中的Google Services身份驗證,第2部分

在本教程的第一部分中, 我描述了如何使用OAuth進行Google API服務的訪問/身份驗證。 不幸的是,正如我稍后發現的那樣,我使用的方法是OAuth 1.0,顯然現在Google正式棄用了OAuth 1.0,改用OAuth 2.0版本。 顯然,發現這一點讓我有些沮喪,并承諾我將創建一個新博客條目,其中包含有關如何使用2.0的說明。 好消息是,有了2.0支持,Google添加了一些附加的幫助程序類,這些類使事情變得更加容易,特別是如果您使用的是Google App Engine,這正是我在本教程中使用的。

Google Developers網站現在對如何設置OAuth 2.0進行了很好的描述 。 但是,事實證明,配置一個真實的示例如何完成它是一個挑戰,因此我想記錄下我學到的東西。
教程場景在上一個教程中,我創建的項目說明了如何訪問用戶的Google Docs文件列表。 在本教程中,我做了一些改動,而是使用YouTube的API來顯示用戶喜歡的視頻的列表。 訪問用戶的收藏夾確實需要使用OAuth進行身份驗證,因此這是一個很好的測試。

入門 (可以在此處找到本教程的Eclipse項目)。

您必須做的第一件事是遵循Google官方文檔中概述的有關使用OAuth 2.0的步驟。 由于我正在創建一個Web應用程序,因此您將需要遵循這些文檔中名為“ Web服務器應用程序”的部分。 此外,我之前討論的用于設置Google App Engine的步驟仍然很重要,因此我將直接跳入代碼并跳過這些設置步驟。

(注意:可以在這里找到Eclipse項目-我再次選擇不使用Maven,以使那些沒有安裝Maven或在Maven方面有豐富知識的人保持簡單。)

應用程序流程非常簡單(假設是首次使用用戶):

  1. 用戶訪問Web應用程序時(假設您正在使用GAE開發人員模擬器在http:// localhost:8888在本地運行該Web應用程序),則他們必須首先使用其gmail或Google域帳戶登錄到Google。
  2. 登錄后,用戶將被重定向到一個簡單的JSP頁面,該頁面具有指向其YouTube最喜歡的視頻的鏈接。
  3. 當點擊鏈接時,servlet將啟動OAuth流程以獲取對其YouTube帳戶的訪問權限。 此過程的第一部分將重定向到Google Page,該頁面會提示他們是否要授予應用程序訪問權限。
  4. 假設用戶回答肯定,將顯示10個帶有鏈接的收藏夾列表。
  5. 如果他們單擊鏈接,將加載視頻。

這是前3頁流程的描述:

這是最后兩頁(假設用戶單擊了給定的鏈接):

盡管此示例特定于YouTube,但相同的通用原則也適用于訪問任何基于Google的云服務,例如Google +,Google Drive,Docs等。它們創建此類集成的關鍵推動因素顯然是OAuth,因此讓我們來看一下該過程有效。

OAuth 2.0處理流程

對于剛開始學習這項技術的新開發人員而言,使用OAuth可能會有些不知所措。 其背后的主要前提是允許用戶有選擇地確定他們希望外部應用程序可以訪問哪些“私有”資源,例如我們正在為本教程開發的資源。 通過使用OAuth,用戶可以避免與第三方共享其登錄憑據,而可以簡單地向該第三方授予訪問其某些信息的權限。

為了實現此功能,需要將用戶導航到其私人數據所在的源(在本例中為YouTube)。 然后,他們可以允許或拒絕訪問請求。 如果他們允許,則私有數據(YouTube)的源將一次性授權代碼返回給第三方應用程序。 由于每次需要訪問權限時用戶都必須授予訪問權限是一件很麻煩的事情,因此可以玩一個額外的通話,它將“以舊換新”他們的單次使用授權更長的時間。 我們為本教程開發的Web應用程序的總體流程如下所示。

OAuth流程

進行的第一步是確定用戶是否已經使用其gmail或Google Domain帳戶登錄到Google。 盡管不直接與OAuth流程綁定,但使用戶能夠使用其Google帳戶登錄非常方便,而不是要求用戶登錄您的網站。 這是對Google的第一個標注。 然后,一旦登錄,應用程序將確定用戶是否具有授予OAuth權限的本地帳戶設置。 如果他們是第一次登錄,則不會。 在這種情況下,將啟動OAuth流程。

該過程的第一步是向OAuth提供程序(在本例中為Google YouTube)指定請求訪問的“范圍”。 由于Google提供了很多服務,因此它們具有很多范圍。 您可以使用其OAuth 2.0沙箱輕松確定。

當您啟動OAuth流程時,會向他們提供您想要訪問的范圍,以及Google為您提供的OAuth客戶端憑據(這些步驟實際上是支持OAuth的任何提供程序所通用的)。 為了我們的目的,我們正在尋求訪問該用戶的YouTube帳戶的權限,因此Google提供的范圍是:https://gdata.youtube.com/。

如果最終用戶授予對由范圍標識的資源的訪問權限,則Google會將授權碼發回給應用程序。 這是在servlet中捕獲的。 由于返回的代碼只是“一次性”代碼,因此將其交換為運行時間更長的訪問令牌(和相關的刷新令牌)。 上面的步驟由標題為“請求訪問和刷新令牌”的活動/框表示。

一旦配備了訪問令牌,應用程序便可以通過將API調用與令牌一起放置來訪問用戶的私有數據。 如果一切順利,API將返回結果。

這不是一個可怕的復雜過程,它只涉及幾個步驟。 讓我們看一些具體的實現細節,首先從servlet過濾器開始,該過濾器確定用戶是否已經登錄Google和/或已授予OAuth訪問權限。

授權過濾器

讓我們看一下AuthorizationFilter的前幾行(要了解如何將其配置為過濾器,請參閱web.xml文件)。

public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;HttpSession session = request.getSession();if not present, add credential store to servlet contextif (session.getServletContext().getAttribute(Constant.GOOG_CREDENTIAL_STORE) == null) {LOGGER.fine('Adding credential store to context ' + credentialStore);session.getServletContext().setAttribute(Constant.GOOG_CREDENTIAL_STORE, credentialStore);}if google user isn't in session, add itif (session.getAttribute(Constant.AUTH_USER_ID) == null) {LOGGER.fine('Add user to session');UserService userService = UserServiceFactory.getUserService();User user = userService.getCurrentUser();session.setAttribute(Constant.AUTH_USER_ID, user.getUserId());session.setAttribute(Constant.AUTH_USER_NICKNAME, user.getNickname());if not running on app engine prod, hard-code my email address for testingif (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {session.setAttribute(Constant.AUTH_USER_EMAIL, user.getEmail());} else {session.setAttribute(Constant.AUTH_USER_EMAIL, 'jeffdavisco@gmail.com');}}

前幾行簡單地將通用servlet請求和響應轉換為它們對應的Http等效項-這是必需的,因為我們要訪問HTTP會話。 下一步是確定Servlet上下文中是否存在CredentialStore。 正如我們將看到的,它用于存儲用戶的憑據,因此在后續的servlet中隨時可以使用它很方便。 當我們使用以下命令檢查用戶是否已經在會話中時,事情就開始了:

if (session.getAttribute(Constant.AUTH_USER_ID) == null) {

如果沒有,我們將使用Google的UserService類獲取其Google登錄憑據。 這是GAE用戶可用來獲取用戶的Google用戶ID,電子郵件和昵稱的幫助程序類。 從UserService獲得此信息后,我們將在會話中存儲一些用戶的詳細信息。

目前,我們還沒有對OAuth做任何事情,但是在接下來的代碼行系列中會有所改變:
嘗試{
Utils.getActiveCredential(request,credentialStore); } catch(NoRefreshTokenException e1){ //如果輸入了該catch塊,則需要執行oauth流程 LOGGER.info('未找到用戶–授權URL為:'+ e1.getAuthorizationUrl()); response.sendRedirect(e1.getAuthorizationUrl()); }

大多數OAuth處理都使用一個稱為Utils的幫助程序類。 在這種情況下,我們將調用靜態方法getActiveCredential()。 稍后我們將看到,如果以前沒有為用戶捕獲OAuth憑據,則此方法將返回NoRefreshTokenException。 作為自定義例外,它將返回URL值,該URL值用于將用戶重定向到Google以尋求OAuth批準。

讓我們更詳細地了解getActiveCredential()方法,因為這是管理許多OAuth處理的地方。

public static Credential getActiveCredential(HttpServletRequest request, CredentialStore credentialStore) throws NoRefreshTokenException {String userId = (String) request.getSession().getAttribute(Constant.AUTH_USER_ID);Credential credential = null;try {if (userId != null) {credential = getStoredCredential(userId, credentialStore);}if ((credential == null || credential.getRefreshToken() == null) && request.getParameter('code') != null) {credential = exchangeCode(request.getParameter('code'));LOGGER.fine('Credential access token is: ' + credential.getAccessToken());if (credential != null) {if (credential.getRefreshToken() != null) {credentialStore.store(userId, credential);}}}if (credential == null || credential.getRefreshToken() == null) {String email = (String) request.getSession().getAttribute(Constant.AUTH_USER_EMAIL);String authorizationUrl = getAuthorizationUrl(email, request);throw new NoRefreshTokenException(authorizationUrl);}} catch (CodeExchangeException e) {e.printStackTrace();} return credential;}

我們要做的第一件事是從會話中獲取Google userId(如果沒有填充,則無法做到這一點)。 接下來,我們嘗試使用Utils靜態方法getStoredCredential()從CredentialStore獲取用戶的OAuth憑據(以相同名稱存儲在Google類中)。 如果找不到該用戶的憑據,則調用名為getAuthorizationUrl()的Utils方法。 如下所示,此方法用于構造瀏覽器重定向到的URL,該URL用于提示用戶授權訪問其私有數據(該URL由Google提供,因為它將詢問用戶批準)。

private static String getAuthorizationUrl(String emailAddress, HttpServletRequest request) {GoogleAuthorizationCodeRequestUrl urlBuilder = null;try {urlBuilder = new GoogleAuthorizationCodeRequestUrl(getClientCredential().getWeb().getClientId(),Constant.OATH_CALLBACK,Constant.SCOPES).setAccessType('offline').setApprovalPrompt('force');} catch (IOException e) {TODO Auto-generated catch blocke.printStackTrace();}urlBuilder.set('state', request.getRequestURI());if (emailAddress != null) {urlBuilder.set('user_id', emailAddress);}return urlBuilder.build();}

如您所見,此方法正在使用稱為GoogleAuthorizationCodeRequestUrl的類(來自Google)。 它使用您注冊使用OAuth時由Google提供的OAuth客戶端憑據來構造HTTP調用(巧合的是,這些憑據存儲在一個名為client_secrets.json的文件中。其他參數包括OAuth請求的范圍和URL如果獲得用戶的批準,該用戶將被重定向回該URL。該URL是您在注冊Google的OAuth訪問權限時指定的URL:

現在,如果用戶已經授予OAuth訪問權限,則getActiveCredential()方法將改為從CredentialStore獲取憑據。

回到接收OAuth憑據結果的URL(在本例中為http:// localhost:8888 / authSub),您可能想知道Google如何發布到該內部專用地址? 好吧,實際上是用戶的瀏覽器發回了結果,因此在這種情況下,本地主機就可以很好地解決問題。 讓我們看一下用于處理此回調的名為OAuth2Callback的servlet(有關如何完成authSub的servlet映射的信息,請參見web.xml)。

public class OAuth2Callback extends HttpServlet {private static final long serialVersionUID = 1L;private final static Logger LOGGER = Logger.getLogger(OAuth2Callback.class.getName());public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {StringBuffer fullUrlBuf = request.getRequestURL();Credential credential = null;if (request.getQueryString() != null) {fullUrlBuf.append('?').append(request.getQueryString());}LOGGER.info('requestURL is: ' + fullUrlBuf);AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(fullUrlBuf.toString());check for user-denied errorif (authResponse.getError() != null) {LOGGER.info('User-denied access');} else {LOGGER.info('User granted oauth access');String authCode = authResponse.getCode();request.getSession().setAttribute('code', authCode);response.sendRedirect(authResponse.getState());}}}

該課程最重要的收獲是:
AuthorizationCodeResponseUrl authResponse =新的AuthorizationCodeResponseUrl(fullUrlBuf.toString()); Google提供了AuthorizationCodeResponseUrl類,以方便分析OAuth請求的結果。 如果該類的getError()方法不為null,則意味著用戶拒絕了該請求。 如果它為null(表示用戶已批準該請求),則使用方法調用getCode()來檢索一次性授權碼。 此代碼值放置在用戶的會話中,并且在重定向到用戶的目標URL(通過過濾器)后調用Utils.getActiveCredential()時,它將將該授權代碼交換為長期訪問權限并使用電話: 憑證= exchangeCode((String)request.getSession()。getAttribute('code')); 接下來顯示Utils.exchangeCode()方法:

public static Credential exchangeCode(String authorizationCode)throws CodeExchangeException {try {GoogleTokenResponse response = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), Constant.JSON_FACTORY, Utils.getClientCredential().getWeb().getClientId(), Utils.getClientCredential().getWeb().getClientSecret(),authorizationCode, Constant.OATH_CALLBACK).execute();return Utils.buildEmptyCredential().setFromTokenResponse(response);} catch (IOException e) {e.printStackTrace();throw new CodeExchangeException();}}

此方法還使用稱為GoogleAuthorizationCodeTokenRequest的Google類,該類用于調用Google以將一次性OAuth授權代碼交換為較長時間的訪問令牌。
現在,我們已經(最終)獲得了YouTube API所需的訪問令牌,我們準備向用戶顯示其視頻收藏夾中的10個。

調用YouTube API服務

有了訪問令牌,我們現在可以繼續向用戶顯示其收藏夾列表。 為此,調用了一個名為“ FavoritesServlet”的servlet。 它將調用YouTube API,通過Jackson將生成的JSON-C格式解析為一些本地Java類,然后將結果發送到JSP頁面進行處理。 這是servlet:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {LOGGER.fine('Running FavoritesServlet');Credential credential = Utils.getStoredCredential((String) request.getSession().getAttribute(Constant.AUTH_USER_ID), (CredentialStore) request.getSession().getServletContext().getAttribute(Constant.GOOG_CREDENTIAL_STORE));VideoFeed feed = null;if the request fails, it's likely because access token is expired - we'll refreshtry {LOGGER.fine('Using access token: ' + credential.getAccessToken());feed = YouTube.fetchFavs(credential.getAccessToken());} catch (Exception e) {LOGGER.fine('Refreshing credentials');credential.refreshToken();credential = Utils.refreshToken(request, credential);GoogleCredential googleCredential = Utils.refreshCredentials(credential);LOGGER.fine('Using refreshed access token: ' + credential.getAccessToken());retry feed = YouTube.fetchFavs(credential.getAccessToken());}  LOGGER.fine('Video feed results are: ' + feed);request.setAttribute(Constant.VIDEO_FAVS, feed);RequestDispatcher dispatcher = getServletContext().getRequestDispatcher('htmllistVids.jsp');dispatcher.forward(request, response);  }

由于這篇文章主要是關于OAuth流程的,因此我不會過多介紹API調用的放置方式,但是最重要的代碼行是:feed = YouTube.fetchFavs(credential.getAccessToken()); feed是VideoFeed的實例。 如您所見,另一個名為YouTube的幫助程序類用于執行繁重的工作。 為了總結一下,我將展示fetchFavs()方法。

public static VideoFeed fetchFavs(String accessToken) throws IOException, HttpResponseException {HttpTransport transport = new NetHttpTransport();final JsonFactory jsonFactory = new JacksonFactory();HttpRequestFactory factory = transport.createRequestFactory(new HttpRequestInitializer() {@Overridepublic void initialize(HttpRequest request) {set the parserJsonCParser parser = new JsonCParser(jsonFactory);request.addParser(parser);set up the Google headersGoogleHeaders headers = new GoogleHeaders();headers.setApplicationName('YouTube Favorites1.0');headers.gdataVersion = '2';request.setHeaders(headers);}});build the YouTube URLYouTubeUrl url = new YouTubeUrl(Constant.GOOGLE_YOUTUBE_FEED);url.maxResults = 10;url.access_token = accessToken;build the HTTP GET requestHttpRequest request = factory.buildGetRequest(url);HttpResponse response = request.execute();execute the request and the parse video feedVideoFeed feed = response.parseAs(VideoFeed.class);return feed;}

它使用稱為HttpRequestFactory的Google類構造對YouTube的出站HTTP API調用。 由于我們使用的是GAE,因此我們只能使用哪些類來發出此類請求。 注意代碼行:
url.access_token = accessToken;
那就是我們使用通過OAuth流程獲取的訪問令牌的地方。

因此,雖然需要花費大量代碼才能使OAuth內容正常運行,但是一旦到位,您就可以準備調用各種Google API服務來進行滾動!

參考:我們的JCG合作伙伴 Jeff Davis在Jeff's SOA Ruminations博客上為Google Services進行了認證,第2部分 。


翻譯自: https://www.javacodegeeks.com/2012/06/google-services-authentication-in-app_20.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/372218.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/372218.shtml
英文地址,請注明出處:http://en.pswp.cn/news/372218.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

字符串常用操作

1 常用:分割、長度、索引、切片2 r (1,2,3,4,5)#只讀列表元組3 name "liangml"4 strip5 username input("user:")6 if username.strip() "liangml":#strip可以將輸入前后的空格都換掉7 print("welcome")8 9 split …

java 保存bufferedimage_java - 如何將BufferedImage保存為Fi

答案在于Java Documentation的編寫/保存圖像教程。SaveImage.java類提供以下保存圖像的方法:static boolean ImageIO.write(RenderedImage im, String formatName, File output) throws IOException該教程解釋了這一點BufferedImage類實現RenderedImage接口。所以它…

MarkDownPad2 注冊碼

注冊信息 郵箱地址: Soar360live.com 授權秘鑰:GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGImDHzWdD6xhMNLGVpbP2M5SN6bnxn2kSE8qHqNY5QaaRxmO3YSMHxlv2EYpjdwLcPwfeTG7kUdnhKE0vVy4RidP6Y2wZ0q74f47fzsZo45JE2hfQBFi2O9Jldjp1mW8HUpTtLA2a5/sQytX…

[51nod1297]管理二叉樹

一個初始為空的二叉搜索樹T,以及1到N的一個排列P: {a1, a2, ..., aN}。我們向這個二叉搜索樹T添加這些數,從a1開始, 接下來是 a2, ..., 以aN結束。在每一個添加操作后,輸出T上每對節點之間的距離之和。例如:4 7 3 1 8 …

Java Swing中的聊天氣泡

本文將向您解釋“如何在Java swing應用程序中繪制聊天氣泡?” 聊天氣泡與呼出氣泡或思想氣泡相同。 今天,大多數聊天應用程序都以這種格式顯示轉換,因此本文將幫助您在用Java swing創建的桌面應用程序中進行相同的操作。 以下課程用于繪制第一…

java內存模型按照線程隔離性_深入理解Java多線程與并發框(第③篇)——Java內存模型與原子性、可見性、有序性...

一、Java內存模型Java Memory Modle,簡稱 JMM,中文名稱 Java內存模型,它是一個抽象的概念,用來描述或者規范訪問內存變量的方式。因為各中計算機的操作系統和硬件不同,方式機制也可能不同,Java內存模型用于…

PHP通過PDO連接Microsoft Access數據庫

1連接到access數據庫 $db new PDO("odbc:Driver{Microsoft Access Driver (*.mdb, *.accdb)}; dbq" .realpath("yourfilepath\# ddsbbn3A02.Mdb")) or die("Connect Error"); realpath函數用來規范化絕對路徑 2修改數據庫中BM_sitelink表中字段…

ZK實際應用:樣式和布局

在之前的ZK in Action帖子中,我們使用ZK MVVM實現了CRUD功能 。 我們還快速瀏覽了一些樣式代碼,可能需要更多的解釋。 在本文中,我們將討論如何在ZK小部件上附加新CSS樣式規則,以及如何覆蓋現有樣式。 我們還將介紹ZK中UI布局的一…

java面向對象的三大特征是6_Java面向對象的三大特征

面向對象的本質:以類的方式組織代碼,以對象的方式組織數據。面向對象三大特性:封裝 繼承 多態封裝:概念:隱藏對象內部的復雜性,只對外公開簡單的接口。便于外界調用,從而提高系統的可擴展性&…

Tornado(一)

Tornado 特點 Tornado是一個用Python寫的相對簡單的、不設障礙的Web服務器架構,用以處理上萬的同時的連接口,讓實時的Web服務通暢起來。雖然跟現在的一些用Python寫的Web架構相似,比如Django,但Tornado更注重速度,能夠…

Android下Opengl ES實現單屏幕雙眼顯示

http://blog.csdn.net/u011371324/article/details/68946779 默認情況下,Opengl ES使用系統提供的幀緩沖區作為繪圖表面,一般情況下,如果只在屏幕的表面繪圖的話,系統提供的默認幀緩沖區很高效,但是很多應用程序需要渲…

Oracle Service Bus –線程阻塞案例研究

本案例研究描述了在AIX 6.1和IBM Java VM 1.6上運行的Oracle Service Bus 11g遇到的線程阻塞問題的完整根本原因分析過程。 本文也是您提高線程轉儲分析技能的絕佳機會,我強烈建議您學習并正確理解以下分析方法。 與過早的中間件(Weblogic)重…

java 可以重載等于號碼_Java面試之Java基礎4——重載與重寫的區別

目錄重載與重寫的概念重載與重寫的區別重載與重寫的總結構造器是否能被重寫override為什么函數不能根據返回類型來區分重載重載與重寫的概念重載:同樣一個方法可以根據輸入參數列表的不同,做出不同的處理。普通方法和構造器方法都能夠重載。方法重載&…

二維數組、多維數組

二維數組: 定義二維數組 int[,] myArray new int[幾個一維數組,數組中的個數]; 數組可以具有多個維度。例如,下列聲明創建一個四行兩列的二維數組(可以理解為4個1維數組,數組中包含2個元素): int[,] myArray new int[4,2]; int[…

一張大圖片有多個小圖片

這個頁面也是我看到別人的寫的,感覺不錯,就自己留下了為了以后自己可以容易找到,也希望可以方便到別人。 寫這個頁面 需要注意的是: 1.寫每一個小圖片的位置時候,要用id,這樣等級就高了,不然不起作用。 2.因…

java中如何調用dal接口案例_關于Java:接口的目的

好吧,我認為接口是一種強制對象實現一定數量功能的方法,而不必使用繼承。有點像合同。我半明白他們的意思。但是,如果界面中的所有內容都是:public interface animal{void eat(object food);}它沒有這樣的實現,那么無論…

Android Studio混淆

這一篇說一下Android Studio的代碼混淆: 第一步:要想使混淆生效,要修改項目(App)下的build.gradle一處內容:minifyEnabled 的值 設置為true,當前項目就可以使用混淆了。 apply plugin: com.and…

內存訪問模式很重要

在高性能計算中,通常會說高速緩存未命中的代價是算法的最大性能損失。 多年來,處理器速度的提高大大超過了延遲到主內存的速度。 通過更寬的多通道總線,到主內存的帶寬已大大增加,但是延遲并未顯著減少。 為了掩蓋這種延遲&#x…

上傳頭像將光標去掉

οnfοcus"this.blur();" unselectable"on" οnfοcus"this.blur();"支持火狐,谷歌等主流瀏覽器 unselectable支持ie瀏覽器轉載于:https://www.cnblogs.com/jar-gon/p/6841239.html

java底層 文件操作_JAVA的文件操作【轉】

11.3 I/O類使用由于在IO操作中,需要使用的數據源有很多,作為一個IO技術的初學者,從讀寫文件開始學習IO技術是一個比較好的選擇。因為文件是一種常見的數據源,而且讀寫文件也是程序員進行IO編程的一個基本能力。本章IO類的使用就從…