在Windows Azure平台中,應用程式除了可選擇完整的資料庫平台SQL Azure儲存資料外,也可選擇較為簡單的Windows Azure Storage Services來儲存資料,
Windows Azure Storage Services共提供三種儲存體
Using Windwos Azure Storage Services with Java
文/黃忠成
雲端儲存體
在Windows Azure平台中,應用程式除了可選擇完整的資料庫平台SQL Azure儲存資料外,也可選擇較為簡單的Windows Azure Storage Services來儲存資料,
Windows Azure Storage Services共提供三種儲存體:
- Table Storage Service儲存row/column(Table)類型的資料
- Blob Service儲存大型的二進位資料
- Queue Service佇列
接下來本文將探討如何於Java/Tomcat中與這三種儲存體互動。
Windows Azure SDK Eclipse Plug-In中的Storage Explorer
Windows Azure SDK的Eclipse Plug-In提供了一個相當方便的Storage Explorer工具,可以讓開發者於Eclipse直接連到Windows Azure Storage Services來
查看或變更資料,開Storage Explorer的方法很簡單,請點選Windows|Show Views|Other選單,就可開出圖1的畫面。
圖1
圖2
點選OK按鈕後,Storage Explorer就出現在右方,開發者可依據喜好來移動她。
圖3
進行下一步動作前,請記得確認Storage Emulator是在執行中的,如果沒有,可透過程式集來啟動。
圖4
圖5
一切正常的話,就可以點選Open按鈕來連結到Storage Emulator。
圖6
開發者可透過Storage Explorer來取得或是管理Table、Blob或是Queue等儲存體中的資料。
圖7
圖8
圖9
圖10
圖11
當然,開發者也可以直接連結到真正位於Windows Azure平台中的Storage Services,只要點選Windows|Perferences選單來添加Account即可。
圖12
圖13
圖14
使用Windows Azure SDK for Java Client Library
要在應用程式中存取Windows Azure Storage Services,開發者還需要至以下網址下載Windows Azure Libraries for Java。
http://www.windowsazure.com/en-us/develop/java/java-home/download-the-windows-azure-sdk-for-java/
圖15
紅框框起來的部分都要一一下載,慣用Maven的開發者也可於此網頁中找到取得的方法(這個較為快速且簡單)。
在取得這個Library後,就可以著手進行開發,本文以一個Web Project為例,第一步驟就是要引用Windows Azure Libraries for Java中的所有.jar。
圖16
圖17
圖18
使用Table Storage Service
Table Storage Servcie可以用來儲存Table型態的資料,是一種完全針對雲端運算環境所設計的儲存體。在Java中使用的方式很簡單,先新增一個StorageClient.java。
package com.azure.stroage.demo;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import com.microsoft.windowsazure.serviceruntime.RoleEnvironment;
import com.microsoft.windowsazure.services.blob.client.CloudBlobClient;
import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount;
import com.microsoft.windowsazure.services.queue.client.CloudQueueClient;
import com.microsoft.windowsazure.services.table.client.CloudTableClient;
public class StorageClient {
//for running outside worker role.
static final String storageConnectionString =
"UseDevelopmentStorage=true;" +
"AccountName=devstoreaccount1;" +
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
private static CloudStorageAccount Account = null;
private static CloudTableClient _tableClient = null;
private static CloudBlobClient _blobClient = null;
private static CloudQueueClient _queueClient = null;
public static CloudTableClient getCloudTableClient() {
if(_tableClient == null)
_tableClient = Account.createCloudTableClient();
return _tableClient;
}
public static CloudBlobClient getCloudBlobClient() {
if(_blobClient == null)
_blobClient = Account.createCloudBlobClient();
return _blobClient;
}
public static CloudQueueClient getCloudQueueClient() {
if(_queueClient == null)
_queueClient = Account.createCloudQueueClient();
return _queueClient;
}
static {
try {
Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));
//Account = CloudStorageAccount.parse(storageConnectionString);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
StorageClient類別負責提供Table Storage、Blob、Queue的初始化程式碼,這裡有一段需特別注意。
Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));
這一段是透過Windows Azure Worker Role的組態檔來取得Storage Services的連線參數,但這只能夠在Windows Azure Worker Role環境中執行,如果你想直接執行Web Project的話(比較快),需修改成下面這樣子。
Account = CloudStorageAccount.parse(storageConnectionString);
接著新增一個Customer.java,其是對應成Table Storage中的一筆資料。
package com.azure.stroage.demo;
import com.microsoft.windowsazure.services.table.client.TableServiceEntity;
public class Customer extends TableServiceEntity {
int _age;
public String getName() {
return this.partitionKey;
}
public void setName(String name) {
this.partitionKey = name;
}
public String getCustomerID() {
return this.rowKey;
}
public void setCustomerID(String id) {
this.rowKey = id;
}
public int getAge() {
return _age;
}
public void setAge(int age) {
_age = age;
}
public Customer(String id, String name) {
setName(name);
setCustomerID(id);
}
public Customer() {
}
}
為了方便使用,我把操作Table的程式碼封裝在Customers類別中。
package com.azure.stroage.demo;
import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.table.client.TableBatchOperation;
import com.microsoft.windowsazure.services.table.client.TableConstants;
import com.microsoft.windowsazure.services.table.client.TableOperation;
import com.microsoft.windowsazure.services.table.client.TableQuery;
import com.microsoft.windowsazure.services.table.client.TableQuery.Operators;
import com.microsoft.windowsazure.services.table.client.TableQuery.QueryComparisons;
public class Customers {
public static final void createIfNotExists() throws StorageException {
StorageClient.getCloudTableClient().createTableIfNotExists("Customers");
}
public static final void insert(Customer entity) throws StorageException {
TableOperation operation = TableOperation.insert(entity);
StorageClient.getCloudTableClient().execute("Customers", operation);
}
public static final void insertEntitys(Customer[] entitys) throws StorageException {
TableBatchOperation batch = new TableBatchOperation();
for(Customer c : entitys)
batch.insert(c);
StorageClient.getCloudTableClient().execute("Customers", batch);
}
public static final void delete(Customer entity) throws StorageException {
TableOperation operation = TableOperation.delete(entity);
StorageClient.getCloudTableClient().execute("Customers", operation);
}
public static final void update(Customer entity) throws StorageException {
TableOperation operation = TableOperation.replace(entity);
StorageClient.getCloudTableClient().execute("Customers", operation);
}
public static final Iterable retrieveAll() {
TableQuery myQuery = TableQuery.from("Customers", Customer.class)
.take(999);
return StorageClient.getCloudTableClient().execute(myQuery);
}
public static final Iterable retrieveWithName(String name) {
String queryString = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, name);
TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);
return StorageClient.getCloudTableClient().execute(myQuery);
}
public static final Iterable retrieveWithAge(int age) {
String queryString = TableQuery.generateFilterCondition("Age", QueryComparisons.EQUAL, age);
TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);
return StorageClient.getCloudTableClient().execute(myQuery);
}
public static final Customer retrieveWithID(String id) {
String queryString = TableQuery.generateFilterCondition(TableConstants.ROW_KEY, QueryComparisons.EQUAL, id);
TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);
for(Customer c : StorageClient.getCloudTableClient().execute(myQuery))
return c;
return null;
}
public static final Iterable retrieveWithNameAndAge(String name,int age) {
String queryString = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, name);
queryString = TableQuery.combineFilters(queryString, Operators.AND,
TableQuery.generateFilterCondition("Age", QueryComparisons.EQUAL, age));
TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);
return StorageClient.getCloudTableClient().execute(myQuery);
}
}
程式碼應該不難理解,只是新增刪動作而已。完成後即可建立.jsp頁面來測試。
<%@pagelanguage="java"contentType="text/html; charset=BIG5"
pageEncoding="BIG5"%>
<%@pageimport="com.azure.stroage.demo.*"%>
<%@pageimport="com.microsoft.windowsazure.services.table.client.*"%>
<%@pageimport="com.microsoft.windowsazure.services.core.storage.*"%>
<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html; charset=BIG5">
<title>Customers</title>
</head>
<body>
<%
if(request.getParameter("btnGenerate") != null) {
Customers.createIfNotExists();
Customer c1 = newCustomer("A00001","Jeffery");
c1.setAge(18);
Customer c2 = newCustomer("A00002","Tom");
c2.setAge(28);
Customers.insert(c1);
Customers.insert(c2);
response.sendRedirect("Customers.jsp");
}
%>
<formname="form1"method="POST">
<table>
<tr>
<td>Customer ID</td>
<td>Name</td>
<td>Age</td>
</tr>
<%
Customers.createIfNotExists();
for(Customer c : Customers.retrieveAll()) {%>
<tr>
<td><%=c.getCustomerID()%></td>
<td><%=c.getName()%></td>
<td><%=c.getAge()%></td>
</tr>
<%} %>
<tr>
<tdcolspan="3">
<inputtype="submit"name="btnGenerate"value="Generate"/>
</td>
</tr>
</table>
</form>
</body>
</html>
圖19為執行畫面。
圖19
使用Blob Service
當瞭解Table Storage Service的大概用法後,Blob就不難了。
package com.azure.stroage.demo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import com.microsoft.windowsazure.services.blob.client.BlobContainerPermissions;
import com.microsoft.windowsazure.services.blob.client.BlobContainerPublicAccessType;
import com.microsoft.windowsazure.services.blob.client.CloudBlobContainer;
import com.microsoft.windowsazure.services.blob.client.CloudBlockBlob;
import com.microsoft.windowsazure.services.blob.client.ListBlobItem;
import com.microsoft.windowsazure.services.core.storage.StorageException;
public class MyPictureBlob {
private static CloudBlobContainer _container = null;
public static final void insert(String name, byte[] data) throws URISyntaxException, StorageException, IOException {
CloudBlockBlob blob = _container.getBlockBlobReference(name);
ByteArrayInputStream stream = new ByteArrayInputStream(data);
blob.upload(stream, data.length);
}
public static final Iterable getBlobs() {
return _container.listBlobs();
}
public static final void delete(String name) throws URISyntaxException, StorageException {
CloudBlockBlob blob = _container.getBlockBlobReference(name);
blob.delete();
}
static {
try {
_container = StorageClient.getCloudBlobClient().getContainerReference("pics");
_container.createIfNotExist();
BlobContainerPermissions ps = new BlobContainerPermissions();
ps.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);
_container.uploadPermissions(ps);
} catch (URISyntaxException | StorageException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
在本文中,測試Blob的是一個供使用者瀏覽及上傳圖片的頁面。
<%@page language="java"contentType="text/html; charset=BIG5"
pageEncoding="BIG5"%>
<%@page import="com.azure.stroage.demo.*"%>
<%@page import="com.microsoft.windowsazure.services.blob.client.*"%>
<!DOCTYPE htmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Blobs</title>
<meta http-equiv="Content-Type" content="text/html; charset=big5"/>
</head>
<body>
<form name="upload" enctype="multipart/form-data" method="post" action="uploadHandler.jsp">
<p>上傳圖片:<input type="file" name="file" size="20 "maxlength="20"/></p>
<p><input type="submit"value="上傳"/><input type="reset"value="清除"/></p>
</form>
<table>
<%
for(ListBlobItem item : MyPictureBlob.getBlobs()) { %>
<tr><td><img src='<%=item.getUri().toString() %>'/></td></tr>
<%} %>
</table>
</body>
</html>
下面是uploadHandler.jsp的程式碼。
<%@ page language="java" %>
<%@ page import="java.io.*"%>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.util.List"%>
<%@ page import="com.azure.stroage.demo.*"%>
<%@ page import="com.microsoft.windowsazure.services.blob.client.*" %>
<%
String contentType = request.getContentType();
if ((contentType != null) && (contentType.indexOf("multipart/form-data") >= 0)) {
DataInputStream in = new DataInputStream(request.getInputStream());
int formDataLength = request.getContentLength();
byte dataBytes[] = new byte[formDataLength];
int byteRead = 0;
int totalBytesRead = 0;
while (totalBytesRead < formDataLength) {
byteRead = in.read(dataBytes, totalBytesRead, formDataLength);
totalBytesRead += byteRead;
}
String file = new String(dataBytes);
String saveFile = file.substring(file.indexOf("filename=\"") + 10);
saveFile = saveFile.substring(0, saveFile.indexOf("\n"));
saveFile = saveFile.substring(saveFile.lastIndexOf("\\") + 1,saveFile.indexOf("\""));
int lastIndex = contentType.lastIndexOf("=");
String boundary = contentType.substring(lastIndex + 1,contentType.length());
int pos;
pos = file.indexOf("filename=\"");
pos = file.indexOf("\n", pos) + 1;
pos = file.indexOf("\n", pos) + 1;
pos = file.indexOf("\n", pos) + 1;
int boundaryLocation = file.indexOf(boundary, pos) - 4;
int startPos = ((file.substring(0, pos)).getBytes()).length;
int endPos = ((file.substring(0, boundaryLocation)).getBytes()).length;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.write(dataBytes, startPos, dataBytes.length - startPos - boundary.length() - 8);
MyPictureBlob.insert(saveFile, stream.toByteArray());
stream.close();
response.sendRedirect("Blobs.jsp");
}
%>
圖20
使用Queue Service
Queue Service就更簡單了。
package com.azure.stroage.demo;
import java.net.URISyntaxException;
import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.queue.client.CloudQueue;
import com.microsoft.windowsazure.services.queue.client.CloudQueueMessage;
public class MyQueue {
private static CloudQueue _queue = null;
public static void enqueue(String message) throws URISyntaxException, StorageException {
_queue.addMessage(new CloudQueueMessage(message));
}
public static String dequeue() throws URISyntaxException, StorageException {
return _queue.peekMessage().getMessageContentAsString();
}
static {
try {
_queue = StorageClient.getCloudQueueClient().getQueueReference("messages");
_queue.createIfNotExist();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (StorageException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面是測試的頁面。
<%@ page language="java" contentType="text/html; charset=BIG5"
pageEncoding="BIG5"%>
<%@ page import="com.azure.stroage.demo.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=BIG5">
<title>Insert title here</title>
</head>
<body>
<%
MyQueue.enqueue("Hello World");
%>
<%= MyQueue.dequeue() %>
</body>
</html>
佈署到真正的Windows Azure環境
第一個步驟就是修改StorageClient.java,讓他由Worker Role組態中取得Storage Services的連線參數。
Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));
接著修改Azure Project中的ServiceDefinitions.csdef檔案。
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ServiceDefinition xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" name="WindowsAzureProject">
<WorkerRole name="WorkerRole1" vmsize="Small">
<Startup>
<!-- Sample startup task calling startup.cmd from the role's approot folder -->
<Task commandLine="util/.start.cmd startup.cmd" executionContext="elevated" taskType="simple">
<Environment>
<Variable name="_JAVA_OPTIONS" value="-agentlib:jdwp=transport=dt_socket,server=y,address=8090,suspend=n"/>
</Environment>
</Task>
</Startup>
<Runtime executionContext="elevated">
<EntryPoint>
<!-- Sample entry point calling run.cmd from the role's approot folder -->
<ProgramEntryPoint commandLine="run.cmd" setReadyOnProcessStart="true"/>
</EntryPoint>
</Runtime>
<Imports>
<Import moduleName="RemoteAccess"/>
<Import moduleName="RemoteForwarder"/>
</Imports>
<Endpoints>
<InputEndpoint localPort="8080" name="http" port="80" protocol="tcp"/>
<InputEndpoint localPort="8090" name="Debugging" port="8090" protocol="tcp"/>
</Endpoints>
<ConfigurationSettings>
<Setting name="DataConnectionString"/>
</ConfigurationSettings>
</WorkerRole>
</ServiceDefinition>
再修改ServiceDefinitions.cscfg檔案,加入連線參數。
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ServiceConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*" serviceName="WindowsAzureProject">
<Role name="WorkerRole1">
<Instances count="1"/>
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true"/>
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true"/>
<!-- NOTE: replace the following settings with your own -->
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test"/>
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="......."/>
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2039-12-31T23:59:59.0000000-08:00"/>
<!-- <Setting name="DataConnectionString" value="UseDevelopmentStorage=true"/> -->
<Setting name="DataConnectionString" value="DefaultEndpointsProtocol=http;AccountName=<your account name>;AccountKey=<your account key> "/>
</ConfigurationSettings>
<Certificates>
<Certificate name="Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" thumbprint=".........." thumbprintAlgorithm="sha1"/>
</Certificates>
</Role>
</ServiceConfiguration>
最後進行Build All後,將產生的.cspkg、.cscfg上傳至Azure平台即可。