posted @ 2012-09-12 22:29 小胡子 閱讀(164) | 評論 (0) | 編輯 收藏
1. 上下文概述
上下文:其實就是一個邏輯上的業(yè)務(wù)、功能區(qū)域。在這個邏輯區(qū)域里可以有效的進行管理,算是一種制度的約束,也可以理解為某種范圍類的數(shù)據(jù)共享。
其實在很多應(yīng)用框架中到處可以看見上下文的概念,包括.NET本身的設(shè)計就建立在這種思想上的。實例化的對象默認存在于系統(tǒng)中的默認上下文中,我們可以構(gòu)建自己的上下文將對象在運行時進行合理的管理。
在ASP.NET框架中比較經(jīng)典的就是HttpContext上下文對象。所有的運行時對象都會邏輯歸屬到HttpContext上下文中來,如:我們可以使用Request、Response等對象訪問HTTP處理的生命周期數(shù)據(jù)。
在Remoting中跨AppDomin訪問也是建立在上下文基礎(chǔ)上的,請求的消息通過隧道后序列化到達調(diào)用發(fā)。王清培版權(quán)所有,轉(zhuǎn)載請給出署名
在這些強大的應(yīng)用框架背后總有著讓人難以琢磨的設(shè)計秘方,諸多的設(shè)計原則、設(shè)計模式、豐富的實踐經(jīng)驗都將是框架穩(wěn)定運行的基石。Context算是一個比較完美的邏輯范圍設(shè)計模式。[王清培版權(quán)所有,轉(zhuǎn)載請給出署名]
那么就讓我們來領(lǐng)略一下上下文的奧秘吧!
2. 上下文的一般應(yīng)用
上下文的設(shè)計思想絕對的美妙,很多地方一旦進行上下文抽象就能解決很多問題。比如在Remoting中我們可以動態(tài)的在上下文中加入很 多擴展對上下文中的所有對象進行強制管理,比如:調(diào)用某一個方法我們需要進行安全檢查,我們可以編寫一個滿足自己當前項目需求的安全認證插件動態(tài)的注入到 上下文管理器區(qū)域中,在這個地方就體現(xiàn)出上下文的設(shè)計優(yōu)勢。
在Web編程中,由于它有著與Winfrom編程很大的差異性,需要將同一組對象同時服務(wù)于N個客戶端進行使用,而在Winfrom中基本上都是屬于單線程的,當然可以手動的開啟多線程并行操作。對于ASP.NET每當有新的請求處理時,框架會自動開啟新的線程去處理當前的調(diào)用,然后這個時候就是需要一個相對于之前操作的獨立上下文數(shù)據(jù)環(huán)境,而不是在同一個服務(wù)器上的所有線程都是共享的。王清培版權(quán)所有,轉(zhuǎn)載請給出署名
那么我們就需要將當前的HTTP處理的相關(guān)數(shù)據(jù)納入到一個邏輯的上下文進行管理和數(shù)據(jù)共享。
這么多的優(yōu)勢存在,看來我們是有必要嘗試一下這中設(shè)計模式了。那么就目前系統(tǒng)開發(fā)框架而言我們的上下文能用在哪里呢?我想當務(wù)之急就是將分層架構(gòu)中的所有單條線上的對象進行上下文管理。[王清培版權(quán)所有,轉(zhuǎn)載請給出署名]
典型的三層架構(gòu):
- View Code
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- namespace ConsoleApplication1.BLL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class BLL_Order : ContextModule.ContextModuleBaseObject<BLL_Order>
- {
- DAL.DAL_Order dal_order = new DAL.DAL_Order();
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return ContextModule.ContextAction.PostMethod<DAL.DAL_Order, Model.Model_Order>(
- dal_order, dal_order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public void SendOrder(Model.Model_Order ordermodel)
- {
- ContextModule.ContextAction.PostMethod<DAL.DAL_Order, object>(
- dal_order, dal_order.GetMethodInfo("SendOrder"), ordermodel);
- }
- }
- }
DAL的執(zhí)行代碼:
- View Code
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1.DAL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class DAL_Order : ContextModule.ContextModuleBaseObject<DAL_Order>
- {
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- [ContextModule.ContextSecurityHanlder(OperationSort = 2)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- }
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- public void SendOrder(Model.Model_Order ordermodel)
- {
- Console.WriteLine("訂單發(fā)送成功!");
- }
- }
- }
上述代碼是我模擬一個上下文的執(zhí)行過程。
在每個獨立的上下文環(huán)境中應(yīng)該有一片共享的數(shù)據(jù)存儲區(qū)域,以備多個上下文對象訪問。這種方便性多半存在于項目比較緊張的修改需求的時候或者加新業(yè)務(wù)的時候擴展方法用的。
BLL調(diào)用代碼:
- View Code
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public void UpdateOrderSingle()
- {
- Model.Model_Order ordermodel = new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- //放入上下文共享對象池
- ContextModule.ContextRuntime.CurrentContextRuntime.SetValue("updateorder", ordermodel);
- ContextModule.ContextAction.PostMethod<DAL.DAL_Order, object>(
- dal_order, dal_order.GetMethodInfo("UpdateOrderSingle"), null);
- }
DAL執(zhí)行代碼:DAL執(zhí)行代碼:DAL執(zhí)行代碼:DAL執(zhí)行代碼:
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- public void UpdateOrderSingle()
- {
- Model.Model_Order ordermodel =
- ContextModule.ContextRuntime.CurrentContextRuntime.GetValue("updateorder") as Model.Model_Order;
- }
對于上下文運行時環(huán)境的構(gòu)建需要考慮到運行時是共享的上下文對象。對于納入上下文管理的所有對象都需要共享或者說是受控于上下文運行時。
上下文構(gòu)建:
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ContextModule
- {
- /// <summary>
- /// 上下文運行時環(huán)境。
- /// 上下文邏輯運行時環(huán)境,環(huán)境中的功能都是可以通過附加進來的。
- /// </summary>
- public class ContextRuntime : IDisposable
- {
- #region IDisposable成員
- void IDisposable.Dispose()
- {
- _currentContextRuntime = null;
- }
- #endregion
- protected ContextRuntime() { }
- private DateTime _initTime = DateTime.Now;
- /// <summary>
- /// 獲取運行時創(chuàng)建上下文的時間
- /// </summary>
- public virtual DateTime InitTime { get { return _initTime; } }
- private Dictionary<object, object> _runTimeResource = new Dictionary<object, object>();
- private ContextFilterHandlerMap _filterMap = new ContextFilterHandlerMap();
- /// <summary>
- /// 獲取上下文中的方法、類過濾器映射表
- /// </summary>
- public ContextFilterHandlerMap FilterMap { get { return _filterMap; } }
- private Guid _initPrimaryKey = Guid.NewGuid();
- /// <summary>
- /// 獲取運行時創(chuàng)建上下文的唯一標識
- /// </summary>
- public virtual Guid InitPrimaryKey { get { return _initPrimaryKey; } }
- /// <summary>
- /// 獲取上下文共享區(qū)域中的數(shù)據(jù)
- /// </summary>
- /// <param name="key">數(shù)據(jù)Key</param>
- /// <returns>object數(shù)據(jù)對象</returns>
- public virtual object GetValue(object key)
- {
- return _runTimeResource[key];
- }
- /// <summary>
- /// 設(shè)置上下文共享區(qū)域中的數(shù)據(jù)
- /// </summary>
- /// <param name="key">數(shù)據(jù)Key</param>
- /// <param name="value">要設(shè)置的數(shù)據(jù)對象</param>
- public virtual void SetValue(object key, object value)
- {
- _runTimeResource[key] = value;
- }
- [ThreadStatic]
- private static ContextRuntime _currentContextRuntime;
- /// <summary>
- /// 獲取當前上下文運行時對象.
- /// </summary>
- public static ContextRuntime CurrentContextRuntime { get { return _currentContextRuntime; } }
- /// <summary>
- /// 開始運行時上下文
- /// </summary>
- /// <returns>ContextRuntime</returns>
- public static ContextRuntime BeginContextRuntime()
- {
- //可以通過配置文件配置上下文運行時環(huán)境的參數(shù)。這里只是實現(xiàn)簡單的模擬。
- _currentContextRuntime = new ContextRuntime();
- return _currentContextRuntime;
- }
- }
- }
對于上下文的入口構(gòu)建:
- //開啟上下文
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
- }
通過Using的方式我們開始上下文生命周期。
上下文對象的綁定需要延后,不能在對象的構(gòu)建時就創(chuàng)建上下文。
使用后期綁定動態(tài)的切入到執(zhí)行的上下文中。
調(diào)用代碼,上下文入口:
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Data;
- using ConsoleApplication1.BLL;
- using ConsoleApplication1.Model;
- namespace ConsoleApplication1
- {
- public class Program
- {
- public static void Main(string[] args)
- {
- BLL.BLL_Order order = new BLL.BLL_Order();
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
- Model.Model_Order ordermodel = new Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- Model.Model_Order resultmodel = ContextModule.ContextAction.PostMethod<BLL.BLL_Order, Model.Model_Order>(order, order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- ContextModule.ContextAction.PostMethod<BLL.BLL_Order, object>(order, order.GetMethodInfo("SendOrder"), ordermodel);
- }
- }
- }
- }
6. 上下文在分層架構(gòu)中的運用
有了上下文的核心原型之后我們可以擴展到分層架構(gòu)中來,對于分層架構(gòu)的使用其實很有必要,一般的大型業(yè)務(wù)系統(tǒng)都是混合的使用模式,可能有C/S、B/S、Mobile終端等等。
對于加入Service層之后BLL、DAL將位于服務(wù)之后,對于來自客戶端的調(diào)用需要經(jīng)過一些列的身份驗證及權(quán)限授予。有了WCF之后面向SOA的架構(gòu)開發(fā)變的相對容易點,對安全、性能、負載等等都很完美,所以大部分的情況下我們很少需要控制BLL、DAL的執(zhí)行運行。
那么沒有使用WCF構(gòu)建分布式的系統(tǒng)時或者是沒有分布式的需求就是直接的調(diào)用,如WEB的一般開發(fā),從UI到BLL到DAL。或者是普通的Winfrom的項目、控制臺項目屬于內(nèi)網(wǎng)的使用,可能就需要控制到代碼的執(zhí)行。
下面我通過演示一個具體的實例來看看到底效果如何。
我以控制臺的程序作為演示項目類型,也使用簡單的三層架構(gòu)。
這個再簡單不過了吧,為了演示越簡單越好,關(guān)鍵是突出重點。
需求:
在DAL對象里面加入一個插入Order實體對象的方法:
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1.DAL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class DAL_Order : ContextModule.ContextModuleBaseObject<DAL_Order>
- {
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- [ContextModule.ContextSecurityHanlder(OperationSort = 2)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- }
- }
- }
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- namespace ConsoleApplication1.BLL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class BLL_Order : ContextModule.ContextModuleBaseObject<BLL_Order>
- {
- DAL.DAL_Order dal_order = new DAL.DAL_Order();
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return ContextModule.ContextAction.PostMethod<DAL.DAL_Order, Model.Model_Order>(
- dal_order, dal_order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
- }
- }
- /***
- * author:深度訓(xùn)練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Data;
- using ConsoleApplication1.BLL;
- using ConsoleApplication1.Model;
- namespace ConsoleApplication1
- {
- public class Program
- {
- public static void Main(string[] args)
- {
- BLL.BLL_Order order = new BLL.BLL_Order();
- //開啟上下文
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
- Model.Model_Order ordermodel = new Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- Model.Model_Order resultmodel =
- ContextModule.ContextAction.PostMethod<BLL.BLL_Order, Model.Model_Order>(
- order, order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
- }
- }
- }
執(zhí)行效果:
會先執(zhí)行日志的記錄,然后要求我們輸入用戶憑證才能繼續(xù)執(zhí)行下面的方法。
posted @ 2012-09-12 22:28 小胡子 閱讀(228) | 評論 (0) | 編輯 收藏
SSL介紹與Java實例
有關(guān)SSL的原理和介紹在網(wǎng)上已經(jīng)有不少,對于Java下使用keytool生成證書,配置SSL通信的教程也非常多。但如果我們不能夠親自動 手做一個SSL Sever和SSL Client,可能就永遠也不能深入地理解Java環(huán)境下,SSL的通信是如何實現(xiàn)的。對SSL中的各種概念的認識也可能會僅限于可以使用的程度。本文通 過構(gòu)造一個簡單的SSL Server和SSL Client來講解Java環(huán)境下SSL的通信原理。
首先我們先回顧一下常規(guī)的Java Socket編程。在Java下寫一個Socket服務(wù)器和客戶端的例子還是比較簡單的。以下是服務(wù)端的代碼:
Java代碼
1.package org.bluedash.tryssl;
2.
3.import java.io.BufferedReader;
4.import java.io.IOException;
5.import java.io.InputStbreamReader;
6.import java.io.PrintWriter;
7.import java.net.ServerSocket;
8.import java.net.Socket;
9.
10.public class Server extends Thread {
11. private Socket socket;
12.
13. public Server(Socket socket) {
14. this.socket = socket;
15. }
16.
17. public void run() {
18. try {
19. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
20. PrintWriter writer = new PrintWriter(socket.getOutputStream());
21.
22. String data = reader.readLine();
23. writer.println(data);
24. writer.close();
25. socket.close();
26. } catch (IOException e) {
27.
28. }
29. }
30.
31. public static void main(String[] args) throws Exception {
32. while (true) {
33. new Server((new ServerSocket(8080)).accept()).start();
34. }
35. }
36.}
package org.bluedash.tryssl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server extends Thread {
private Socket socket;
public Server(Socket socket) {
this.socket = socket;
}
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String data = reader.readLine();
writer.println(data);
writer.close();
socket.close();
} catch (IOException e) {
}
}
public static void main(String[] args) throws Exception {
while (true) {
new Server((new ServerSocket(8080)).accept()).start();
}
}
}
服務(wù)端很簡單:偵聽8080端口,并把客戶端發(fā)來的字符串返回去。下面是客戶端的代碼:
Java代碼
1.package org.bluedash.tryssl;
2.
3.import java.io.BufferedReader;
4.import java.io.InputStreamReader;
5.import java.io.PrintWriter;
6.import java.net.Socket;
7.
8.public class Client {
9.
10. public static void main(String[] args) throws Exception {
11.
12. Socket s = new Socket("localhost", 8080);
13.
14. PrintWriter writer = new PrintWriter(s.getOutputStream());
15. BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
16. writer.println("hello");
17. writer.flush();
18. System.out.println(reader.readLine());
19. s.close();
20. }
21.
22.}
package org.bluedash.tryssl;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws Exception {
Socket s = new Socket("localhost", 8080);
PrintWriter writer = new PrintWriter(s.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
writer.println("hello");
writer.flush();
System.out.println(reader.readLine());
s.close();
}
}
客戶端也非常簡單:向服務(wù)端發(fā)起請求,發(fā)送一個"hello"字串,然后獲得服務(wù)端的返回。把服務(wù)端運行起來后,執(zhí)行客戶端,我們將得到"hello"的返回。
就是這樣一套簡單的網(wǎng)絡(luò)通信的代碼,我們來把它改造成使用SSL通信。在SSL通信協(xié)議中,我們都知道首先服務(wù)端必須有一個數(shù)字證書,當客戶端連接 到服務(wù)端時,會得到這個證書,然后客戶端會判斷這個證書是否是可信的,如果是,則交換信道加密密鑰,進行通信。如果不信任這個證書,則連接失敗。
因此,我們首先要為服務(wù)端生成一個數(shù)字證書。Java環(huán)境下,數(shù)字證書是用keytool生成的,這些證書被存儲在store的概念中,就是證書倉庫。我們來調(diào)用keytool命令為服務(wù)端生成數(shù)字證書和保存它使用的證書倉庫:
Bash代碼
1.keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123
keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123
這樣,我們就將服務(wù)端證書bluedash-ssl-demo-server保存在了server_ksy這個store文件當中。有關(guān)keytool的用法在本文中就不再多贅述。執(zhí)行上面的命令得到如下結(jié)果:
Bash代碼
1.Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
2. for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
3.[Storing ./server_ks]
Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./server_ks]
然后,改造我們的服務(wù)端代碼,讓服務(wù)端使用這個證書,并提供SSL通信:
Java代碼
1.package org.bluedash.tryssl;
2.
3.import java.io.BufferedReader;
4.import java.io.FileInputStream;
5.import java.io.IOException;
6.import java.io.InputStreamReader;
7.import java.io.PrintWriter;
8.import java.net.ServerSocket;
9.import java.net.Socket;
10.import java.security.KeyStore;
11.
12.import javax.net.ServerSocketFactory;
13.import javax.net.ssl.KeyManagerFactory;
14.import javax.net.ssl.SSLContext;
15.import javax.net.ssl.SSLServerSocket;
16.
17.public class SSLServer extends Thread {
18. private Socket socket;
19.
20. public SSLServer(Socket socket) {
21. this.socket = socket;
22. }
23.
24. public void run() {
25. try {
26. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
27. PrintWriter writer = new PrintWriter(socket.getOutputStream());
28.
29. String data = reader.readLine();
30. writer.println(data);
31. writer.close();
32. socket.close();
33. } catch (IOException e) {
34.
35. }
36. }
37.
38. private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks";
39. private static String SERVER_KEY_STORE_PASSWORD = "123123";
40.
41. public static void main(String[] args) throws Exception {
42. System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);
43. SSLContext context = SSLContext.getInstance("TLS");
44.
45. KeyStore ks = KeyStore.getInstance("jceks");
46. ks.load(new FileInputStream(SERVER_KEY_STORE), null);
47. KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
48. kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
49.
50. context.init(kf.getKeyManagers(), null, null);
51.
52. ServerSocketFactory factory = context.getServerSocketFactory();
53. ServerSocket _socket = factory.createServerSocket(8443);
54. ((SSLServerSocket) _socket).setNeedClientAuth(false);
55.
56. while (true) {
57. new SSLServer(_socket.accept()).start();
58. }
59. }
60.}
package org.bluedash.tryssl;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
public class SSLServer extends Thread {
private Socket socket;
public SSLServer(Socket socket) {
this.socket = socket;
}
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String data = reader.readLine();
writer.println(data);
writer.close();
socket.close();
} catch (IOException e) {
}
}
private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks";
private static String SERVER_KEY_STORE_PASSWORD = "123123";
public static void main(String[] args) throws Exception {
System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);
SSLContext context = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance("jceks");
ks.load(new FileInputStream(SERVER_KEY_STORE), null);
KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
context.init(kf.getKeyManagers(), null, null);
ServerSocketFactory factory = context.getServerSocketFactory();
ServerSocket _socket = factory.createServerSocket(8443);
((SSLServerSocket) _socket).setNeedClientAuth(false);
while (true) {
new SSLServer(_socket.accept()).start();
}
}
}
可以看到,服務(wù)端的Socket準備設(shè)置工作大大增加了,增加的代碼的作用主要是將證書導(dǎo)入并進行使用。此外,所使用的Socket變成了 SSLServerSocket,另外端口改到了8443(這個不是強制的,僅僅是為了遵守習(xí)慣)。另外,最重要的一點,服務(wù)端證書里面的CN一定和服務(wù) 端的域名統(tǒng)一,我們的證書服務(wù)的域名是localhost,那么我們的客戶端在連接服務(wù)端時一定也要用localhost來連接,否則根據(jù)SSL協(xié)議標 準,域名與證書的CN不匹配,說明這個證書是不安全的,通信將無法正常運行。
有了服務(wù)端,我們原來的客戶端就不能使用了,必須要走SSL協(xié)議。由于服務(wù)端的證書是我們自己生成的,沒有任何受信任機構(gòu)的簽名,所以客戶端是無法 驗證服務(wù)端證書的有效性的,通信必然會失敗。所以我們需要為客戶端創(chuàng)建一個保存所有信任證書的倉庫,然后把服務(wù)端證書導(dǎo)進這個倉庫。這樣,當客戶端連接服 務(wù)端時,會發(fā)現(xiàn)服務(wù)端的證書在自己的信任列表中,就可以正常通信了。
因此現(xiàn)在我們要做的是生成一個客戶端的證書倉庫,因為keytool不能僅生成一個空白倉庫,所以和服務(wù)端一樣,我們還是生成一個證書加一個倉庫(客戶端證書加倉庫):
Bash代碼
1.keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456
keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456
結(jié)果如下:
Bash代碼
1.Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
2. for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
3.[Storing ./client_ks]
Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./client_ks]
接下來,我們要把服務(wù)端的證書導(dǎo)出來,并導(dǎo)入到客戶端的倉庫。第一步是導(dǎo)出服務(wù)端的證書:
Bash代碼
1.keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer
keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer
執(zhí)行結(jié)果如下:
Bash代碼
1.Enter keystore password: server
2.Certificate stored in file <server_key.cer>
Enter keystore password: server
Certificate stored in file <server_key.cer>
然后是把導(dǎo)出的證書導(dǎo)入到客戶端證書倉庫:
Bash代碼
1.keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks
keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks
結(jié)果如下:
Bash代碼
1.Enter keystore password: client
2.Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
3.Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
4.Serial number: 4c57c7de
5.Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010
6.Certificate fingerprints:
7. MD5: FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C
8. SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4
9. Signature algorithm name: SHA1withRSA
10. Version: 3
11.Trust this certificate? [no]: yes
12.Certificate was added to keystore
Enter keystore password: client
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c7de
Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010
Certificate fingerprints:
MD5: FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C
SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
好,準備工作做完了,我們來撰寫客戶端的代碼:
Java代碼
1.package org.bluedash.tryssl;
2.
3.import java.io.BufferedReader;
4.import java.io.InputStreamReader;
5.import java.io.PrintWriter;
6.import java.net.Socket;
7.
8.import javax.net.SocketFactory;
9.import javax.net.ssl.SSLSocketFactory;
10.
11.public class SSLClient {
12.
13. private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
14.
15. public static void main(String[] args) throws Exception {
16. // Set the key store to use for validating the server cert.
17. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
18.
19. System.setProperty("javax.net.debug", "ssl,handshake");
20.
21. SSLClient client = new SSLClient();
22. Socket s = client.clientWithoutCert();
23.
24. PrintWriter writer = new PrintWriter(s.getOutputStream());
25. BufferedReader reader = new BufferedReader(new InputStreamReader(s
26. .getInputStream()));
27. writer.println("hello");
28. writer.flush();
29. System.out.println(reader.readLine());
30. s.close();
31. }
32.
33. private Socket clientWithoutCert() throws Exception {
34. SocketFactory sf = SSLSocketFactory.getDefault();
35. Socket s = sf.createSocket("localhost", 8443);
36. return s;
37. }
38.}
package org.bluedash.tryssl;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
public class SSLClient {
private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
public static void main(String[] args) throws Exception {
// Set the key store to use for validating the server cert.
System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
System.setProperty("javax.net.debug", "ssl,handshake");
SSLClient client = new SSLClient();
Socket s = client.clientWithoutCert();
PrintWriter writer = new PrintWriter(s.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(s
.getInputStream()));
writer.println("hello");
writer.flush();
System.out.println(reader.readLine());
s.close();
}
private Socket clientWithoutCert() throws Exception {
SocketFactory sf = SSLSocketFactory.getDefault();
Socket s = sf.createSocket("localhost", 8443);
return s;
}
}
可以看到,除了把一些類變成SSL通信類以外,客戶端也多出了使用信任證書倉庫的代碼。以上,我們便完成了SSL單向握手通信。即:客戶端驗證服務(wù)端的證書,服務(wù)端不認證客戶端的證書。
以上便是Java環(huán)境下SSL單向握手的全過程。因為我們在客戶端設(shè)置了日志輸出級別為DEBUG:
Java代碼
1.System.setProperty("javax.net.debug", "ssl,handshake");
System.setProperty("javax.net.debug", "ssl,handshake");
因此我們可以看到SSL通信的全過程,這些日志可以幫助我們更具體地了解通過SSL協(xié)議建立網(wǎng)絡(luò)連接時的全過程。
結(jié)合日志,我們來看一下SSL雙向認證的全過程:
第一步: 客戶端發(fā)送ClientHello消息,發(fā)起SSL連接請求,告訴服務(wù)器自己支持的SSL選項(加密方式等)。
Bash代碼
1.*** ClientHello, TLSv1
*** ClientHello, TLSv1
第二步: 服務(wù)器響應(yīng)請求,回復(fù)ServerHello消息,和客戶端確認SSL加密方式:
Bash代碼
1.*** ServerHello, TLSv1
*** ServerHello, TLSv1
第三步: 服務(wù)端向客戶端發(fā)布自己的公鑰。
第四步: 客戶端與服務(wù)端的協(xié)通溝通完畢,服務(wù)端發(fā)送ServerHelloDone消息:
Bash代碼
1.*** ServerHelloDone
*** ServerHelloDone
第五步: 客戶端使用服務(wù)端給予的公鑰,創(chuàng)建會話用密鑰(SSL證書認證完成后,為了提高性能,所有的信息交互就可能會使用對稱加密算法),并通過ClientKeyExchange消息發(fā)給服務(wù)器:
Bash代碼
1.*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
第六步: 客戶端通知服務(wù)器改變加密算法,通過ChangeCipherSpec消息發(fā)給服務(wù)端:
Bash代碼
1.main, WRITE: TLSv1 Change Cipher Spec, length = 1
main, WRITE: TLSv1 Change Cipher Spec, length = 1
第七步: 客戶端發(fā)送Finished消息,告知服務(wù)器請檢查加密算法的變更請求:
Bash代碼
1.*** Finished
*** Finished
第八步:服務(wù)端確認算法變更,返回ChangeCipherSpec消息
Bash代碼
1.main, READ: TLSv1 Change Cipher Spec, length = 1
main, READ: TLSv1 Change Cipher Spec, length = 1
第九步:服務(wù)端發(fā)送Finished消息,加密算法生效:
Bash代碼
1.*** Finished
*** Finished
那么如何讓服務(wù)端也認證客戶端的身份,即雙向握手呢?其實很簡單,在服務(wù)端代碼中,把這一行:
Java代碼
1.((SSLServerSocket) _socket).setNeedClientAuth(false);
((SSLServerSocket) _socket).setNeedClientAuth(false);
改成:
Java代碼
1.((SSLServerSocket) _socket).setNeedClientAuth(true);
((SSLServerSocket) _socket).setNeedClientAuth(true);
就可以了。但是,同樣的道理,現(xiàn)在服務(wù)端并沒有信任客戶端的證書,因為客戶端的證書也是自己生成的。所以,對于服務(wù)端,需要做同樣的工作:把客戶端的證書導(dǎo)出來,并導(dǎo)入到服務(wù)端的證書倉庫:
Bash代碼
1.keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
2.Enter keystore password: client
3.Certificate stored in file <client_key.cer>
keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
Enter keystore password: client
Certificate stored in file <client_key.cer>
Bash代碼
1.keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks
2.Enter keystore password: server
3.Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
4.Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
5.Serial number: 4c57c80b
6.Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010
7.Certificate fingerprints:
8. MD5: DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79
9. SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2
10. Signature algorithm name: SHA1withRSA
11. Version: 3
12.Trust this certificate? [no]: yes
13.Certificate was added to keystore
keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks
Enter keystore password: server
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c80b
Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010
Certificate fingerprints:
MD5: DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79
SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
完成了證書的導(dǎo)入,還要在客戶端需要加入一段代碼,用于在連接時,客戶端向服務(wù)端出示自己的證書:
Java代碼
1.package org.bluedash.tryssl;
2.
3.import java.io.BufferedReader;
4.import java.io.FileInputStream;
5.import java.io.InputStreamReader;
6.import java.io.PrintWriter;
7.import java.net.Socket;
8.import java.security.KeyStore;
9.import javax.net.SocketFactory;
10.import javax.net.ssl.KeyManagerFactory;
11.import javax.net.ssl.SSLContext;
12.import javax.net.ssl.SSLSocketFactory;
13.
14.public class SSLClient {
15. private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
16. private static String CLIENT_KEY_STORE_PASSWORD = "456456";
17.
18. public static void main(String[] args) throws Exception {
19. // Set the key store to use for validating the server cert.
20. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
21. System.setProperty("javax.net.debug", "ssl,handshake");
22. SSLClient client = new SSLClient();
23. Socket s = client.clientWithCert();
24.
25. PrintWriter writer = new PrintWriter(s.getOutputStream());
26. BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
27. writer.println("hello");
28. writer.flush();
29. System.out.println(reader.readLine());
30. s.close();
31. }
32.
33. private Socket clientWithoutCert() throws Exception {
34. SocketFactory sf = SSLSocketFactory.getDefault();
35. Socket s = sf.createSocket("localhost", 8443);
36. return s;
37. }
38.
39. private Socket clientWithCert() throws Exception {
40. SSLContext context = SSLContext.getInstance("TLS");
41. KeyStore ks = KeyStore.getInstance("jceks");
42.
43. ks.load(new FileInputStream(CLIENT_KEY_STORE), null);
44. KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
45. kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
46. context.init(kf.getKeyManagers(), null, null);
47.
48. SocketFactory factory = context.getSocketFactory();
49. Socket s = factory.createSocket("localhost", 8443);
50. return s;
51. }
52.}
package org.bluedash.tryssl;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
public class SSLClient {
private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
private static String CLIENT_KEY_STORE_PASSWORD = "456456";
public static void main(String[] args) throws Exception {
// Set the key store to use for validating the server cert.
System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
System.setProperty("javax.net.debug", "ssl,handshake");
SSLClient client = new SSLClient();
Socket s = client.clientWithCert();
PrintWriter writer = new PrintWriter(s.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
writer.println("hello");
writer.flush();
System.out.println(reader.readLine());
s.close();
}
private Socket clientWithoutCert() tbhrows Exception {
SocketFactory sf = SSLSocketFactory.getDefault();
Socket s = sf.createSocket("localhost", 8443);
return s;
}
private Socket clientWithCert() throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance("jceks");
ks.load(new FileInputStream(CLIENT_KEY_STORE), null);
KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
context.init(kf.getKeyManagers(), null, null);
SocketFactory factory = context.getSocketFactory();
Socket s = factory.createSocket("localhost", 8443);
return s;
}
}
通過比對單向認證的日志輸出,我們可以發(fā)現(xiàn)雙向認證時,多出了服務(wù)端認證客戶端證書的步驟:
Bash代碼
1.*** CertificateRequest
2.Cert Types: RSA, DSS
3.Cert Authorities:
4.<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
5.<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
6.*** ServerHelloDone
*** CertificateRequest
Cert Types: RSA, DSS
Cert Authorities:
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
*** ServerHelloDone
Bash代碼
1.*** CertificateVerify
2.main, WRITE: TLSv1 Handshake, length = 134
3.main, WRITE: TLSv1 Change Cipher Spec, length = 1
*** CertificateVerify
main, WRITE: TLSv1 Handshake, length = 134
main, WRITE: TLSv1 Change Cipher Spec, length = 1
在 @*** ServerHelloDone@ 之前,服務(wù)端向客戶端發(fā)起了需要證書的請求 @*** CertificateRequest@ 。
在客戶端向服務(wù)端發(fā)出 @Change Cipher Spec@ 請求之前,多了一步客戶端證書認證的過程 @*** CertificateVerify@ 。
客戶端與服務(wù)端互相認證證書的情景
posted @ 2012-09-12 22:27 小胡子 閱讀(205) | 評論 (0) | 編輯 收藏
- 在項目中經(jīng)常遇見這樣的問題:修改應(yīng)用的配置文件web.xml后,無論重啟應(yīng)用還是重啟WebSphere服務(wù)器,都不能重新加載web.xml,導(dǎo)致修改的內(nèi)容無效。
- 這 個問題困擾了我好久,即使刪除了${was安裝目錄}\IBM\WebSphere\AppServer\profiles\AppSrv01\下的 temp和wstemp兩個緩存文件夾下的臨時文件,重啟后還是無效。幾經(jīng)折騰,后來終于找到了問題所在——還是由于was的緩存機制導(dǎo)致的。
- 找 到${was安裝目錄}\AppServer\profiles\AppSrv01\config\cells\xxxNode01Cell \applications\${應(yīng)用名}.ear\deployments\目錄下,有一個與應(yīng)用相同名稱的緩存文件夾,刪除或修改該文件夾的 web.xml,重啟was即可。
- 在websphere中修改了jsp后,有時會出現(xiàn)修改的jsp沒有起作用,特別是改變了某jsp的樣式后,在頁面中沒看到效果,這主要就是由于websphere中緩存的緣故,這就要清除WebSphere中jsp緩存,如我的應(yīng)用部署的目錄為:
- E:\IBM\WebSphere\AppServer\profiles\AppSrv01\installedApps\nbxzfwNode01Cell\項目名_war.ear\項目名.war
- 在這個目錄下更新了某個jsp頁面,后在瀏覽器里面查看的時候,發(fā)現(xiàn)頁面沒有改變?;诖?,我查看了一下目錄,存放應(yīng)用臨時文件的地方:
- E:\IBM\WebSphere\AppServer\profiles\AppSrv01\temp\nbxzfwNode01\server1\項目名_war\項目名.war
- 在這目錄下,可以看到有很多class文件,都是jsp編譯過來的,對應(yīng)我們應(yīng)用目錄下的jsp文件,于是找到對應(yīng)jsp的class文件刪除,再到瀏覽器中查看,發(fā)現(xiàn)已經(jīng)改變了。。
- 還有一種辦法,就是把這個jsp從項目中刪除或重命名,再到瀏覽器里面查看那個頁面,這時可能會報錯,之后,再把對應(yīng)的jsp添加上或名字改過來,再次到瀏覽器里面查看應(yīng)用的時候,就發(fā)現(xiàn)這jsp的更新效果出來了,呵呵…
- 前兩天去客戶那里給系統(tǒng)做升級,同時協(xié)助解決幾個使用中的問題。到了現(xiàn)場第一件事情是把以前的應(yīng)用導(dǎo)出做備份, 結(jié)果居然遇到返回null。查看日志發(fā)現(xiàn)系統(tǒng)報告空間不足,以前遇到這個問題是因為WAS出現(xiàn)oom(Out of Memory)之后,會生成javacore和dump文件供分析內(nèi)存,這兩個文件通常都比較大,30多M。如果多次出現(xiàn)oom,生成的文件就會占用大量空間。難道最近經(jīng)常內(nèi)存溢出?我心抽搐。
- 快馬趕到Profile目錄,沒有發(fā)現(xiàn)導(dǎo)出文件,詢問客戶也沒有出現(xiàn)系統(tǒng)宕機的情況,大石落地。仔細查看之后發(fā)現(xiàn)profile下的wstemp目錄體積巨大,接近1.7G。這是個臨時目錄,每當管理員通過console登錄之后,wstemp會生成一個新文件夾,保存管理員的所有操作記錄,在管理員登出之后會刪除該目錄。但是wstemp下一堆的臨時文件夾都沒有被刪掉,看來是was 5的bug又遺留到was 6了,真是搞不明白,was的這一堆補丁怎么都沒解決掉這么明顯的一個問題。N多RMB的was啊,越來越覺得還不如免費的jboss。
- 回公司之后查看了自己的was版本,wstemp目錄2.4G,不過分區(qū)夠大才沒出問題。直接刪之了事
轉(zhuǎn):
posted @ 2012-08-20 14:16 小胡子 閱讀(2214) | 評論 (0) | 編輯 收藏
在本期 函數(shù)式思維 的文章中,我將繼續(xù)研究 Gang of Four (GoF) 設(shè)計模式(參閱 參考資料)的函數(shù)式替代解決方案。在本文中,我將研究最少人了解,但卻是最強大的模式之一:解釋器 (Interpreter)。
解釋器的定義是:
給定一個語言,定義其語法表示,以及一個使用該表示來解釋語言中的句子的解釋器。
換句話說,如果您正在使用的語言不適用于解決問題,那么用它來構(gòu)建一個適用的語言。關(guān)于該方法的一個很好的示例出現(xiàn)在 Web 框架中,如 Grails 和 Ruby on Rails(參閱 參考資料),它們擴展了自己的基礎(chǔ)語言(分別是 Groovy 和 Ruby),使編寫 Web 應(yīng)用程序變得更容易。
這種模式最少人了解,因為構(gòu)建一種新的語言并不常見,需要專業(yè)的技能和慣用語法。它是最強大的 設(shè)計模式,因為它鼓勵您針對正在解決的問題擴展自己的編程語言。這在 Lisp(因此 Clojure 也同樣)世界是一個普遍的特質(zhì),但在主流語言中不太常見。
當使用禁止對語言本身進行擴展的語言(如 Java)時,開發(fā)人員往往將自己的思維塑造成該語言的語法;這是您的惟一選擇。然而,當您漸漸習(xí)慣使用允許輕松擴展的語言時,您就會開始將語言折向解決問題的方向,而不是其他折衷的方式。
Java 缺乏直觀的語言擴展機制,除非您求助于面向方面的編程。然而,下一代的 JVM 語言(Groovy、Scala 和 Clojure)(參閱 參考資料)均支持以多種方式進行擴展。通過這樣做,它們可以達到解釋器設(shè)計模式的目的。首先,我將展示如何使用這三種語言實現(xiàn)操作符重載,然后演示 Groovy 和 Scala 如何讓您擴展現(xiàn)有的類。
操作符重載 是函數(shù)式語言的一個常見特性,能夠重定義操作符(如 +
、-
或 *
)配合新的類型工作,并表現(xiàn)出新的行為。操作符重載的缺失是 Java 形成時期的一個有意識的決定,但現(xiàn)在幾乎每一個現(xiàn)代語言都具備這個特性,包括在 JVM 上 Java 的天然接班人。
Groovy 嘗試更新 Java 的語法,使其跟上潮流,同時保留其自然語義。因此,Groovy 通過將操作符自動映射到方法名稱實現(xiàn)操作符重載。例如,如果您想重載 Integer
的 +
操作符,那么您要重寫 Integer
類的 plus()
方法。完整的映射列表已在線提供(參閱 參考資料);表 1 顯示了列表的一部分:
表 1. Groovy 的操作符/方法映射列表的一部分
操作符 | 方法 |
---|---|
x + y | x.plus(y) |
x * y | x.multiply(y) |
x / y | x.div(y) |
x ** y | x.power(y) |
作為一個操作符重載的示例,我將在 Groovy 和 Scala 中都創(chuàng)建一個 ComplexNumber
類。復(fù)數(shù) 是一個數(shù)學(xué)概念,由一個實數(shù) 和虛數(shù) 部分組成,一般寫法是,例如 3 + 4i
。復(fù)數(shù)在許多科學(xué)領(lǐng)域中都很常用,包括工程學(xué)、物理學(xué)、電磁學(xué)和混沌理論。開發(fā)人員在編寫這些領(lǐng)域的應(yīng)用程序時,大大受益于能夠創(chuàng)建反映其問題域的操作符。(有關(guān)復(fù)數(shù)的更多信息,請參閱 參考資料。)
清單 1 中顯示了一個 Groovy ComplexNumber
類:
清單 1. Groovy 中的
ComplexNumber
package complexnums class ComplexNumber { def real, imaginary public ComplexNumber(real, imaginary) { this.real = real this.imaginary = imaginary } def plus(rhs) { new ComplexNumber(this.real + rhs.real, this.imaginary + rhs.imaginary) } def multiply(rhs) { new ComplexNumber( real * rhs.real - imaginary * rhs.imaginary, real * rhs.imaginary + imaginary * rhs.real) } String toString() { real.toString() + ((imaginary < 0 ? "" : "+") + imaginary + "i").toString() } } |
在 清單 1 中,我創(chuàng)建一個類,保存實數(shù)和虛數(shù)部分,并且我創(chuàng)建重載的 plus()
和 multiply()
操作符。兩個復(fù)數(shù)的相加是非常直觀的:plus()
操作符將兩個數(shù)各自的實數(shù)和虛數(shù)分別進行相加,并產(chǎn)生結(jié)果。兩個復(fù)數(shù)的相乘需要以下公式:
(x + yi)(u + vi) = (xu - yv) + (xv + yu)i |
在 清單 1 中的 multiply()
操作符復(fù)制該公式。它將兩個數(shù)字的實數(shù)部分相乘,然后減去虛數(shù)部分相乘的積,再加上實數(shù)和虛數(shù)分別彼此相乘的積。
清單 2 測試復(fù)數(shù)運算符:
清單 2. 測試復(fù)數(shù)運算符
package complexnums import org.junit.Test import static org.junit.Assert.assertTrue import org.junit.Before class ComplexNumberTest { def x, y @Before void setup() { x = new ComplexNumber(3, 2) y = new ComplexNumber(1, 4) } @Test void plus_test() { def z = x + y; assertTrue 3 + 1 == z.real assertTrue 2 + 4 == z.imaginary } @Test void multiply_test() { def z = x * y assertTrue(-5 == z.real) assertTrue 14 == z.imaginary } } |
在 清單 2 中,plus_test()
和 multiply_test()
方法對重載操作符的使用(兩者都以該領(lǐng)域?qū)<沂褂玫南嗤柎恚┡c類似的內(nèi)置類型用法沒什么區(qū)別。
Scala 通過放棄操作符和方法之間的區(qū)別來實現(xiàn)操作符重載:操作符僅僅是具有特殊名稱的方法。因此,要使用 Scala 重寫乘法運算,您要重寫 *
方法。在清單 3 中,我用 Scala 創(chuàng)建復(fù)數(shù)。
清單 3. Scala 中的復(fù)數(shù)
class ComplexNumber(val real:Int, val imaginary:Int) { def +(operand:ComplexNumber):ComplexNumber = { new ComplexNumber(real + operand.real, imaginary + operand.imaginary) } def *(operand:ComplexNumber):ComplexNumber = { new ComplexNumber(real * operand.real - imaginary * operand.imaginary, real * operand.imaginary + imaginary * operand.real) } override def toString() = { real + (if (imaginary < 0) "" else "+") + imaginary + "i" } } |
清單 3 中的類包括熟悉的 real
和 imaginary
成員,以及 +
和 *
操作符/方法。如清單 4 所示,我可以自然地使用 ComplexNumber
:
清單 4. 在 Scala 中使用復(fù)數(shù)
val c1 = new ComplexNumber(3, 2) val c2 = new ComplexNumber(1, 4) val c3 = c1 + c2 assert(c3.real == 4) assert(c3.imaginary == 6) val res = c1 + c2 * c3 printf("(%s) + (%s) * (%s) = %s\n", c1, c2, c3, res) assert(res.real == -17) assert(res.imaginary == 24) |
通過統(tǒng)一操作符和方法,Scala 使操作符重載變成一件小事。Clojure 使用相同的機制來重載操作符。例如,以下 Clojure 代碼定義了一個重載的 **
操作符:
(defn ** [x y] (Math/pow x y)) |
類似于操作符重載,下一代的 JVM 語言允許您擴展類(包括核心 Java 類),擴展的方式在 Java 語言本身是不可能實現(xiàn)的。這些設(shè)施通常用于構(gòu)建領(lǐng)域特定的語言 (DSL)。雖然 GOF 從來沒有考慮過 DSL(因為它們與當時流行的語言沒有共同點),DSL 卻體現(xiàn)了解釋器設(shè)計模式的初衷。
通過將計量單位和其他修飾符添加給 Integer
等核心類,您可以(就像添加操作符一樣)更緊密地對現(xiàn)實問題進行建模。Groovy 和 Scala 都支持這樣做,但它們使用不同的機制。
Groovy 包括兩種對現(xiàn)有類添加方法的機制:ExpandoMetaClass
和類別。(在 函數(shù)式思維:函數(shù)設(shè)計模式,第 2 部分 中,我在適配器模式的上下文中詳細介紹過 ExpandoMetaClass
。)
比方說,您的公司由于離奇的遺留原因,需要以浪(furlongs,英國的計量單位)/每兩周而不是以英里/每小時 (MPH) 的方法來表達速度,開發(fā)人員發(fā)現(xiàn)自己經(jīng)常要執(zhí)行這種轉(zhuǎn)換。使用 Groovy 的 ExpandoMetaClass
,您可以添加一個 FF
屬性給處理轉(zhuǎn)換的 Integer
,如清單 5 所示:
清單 5. 使用
ExpandoMetaClass
添加一個浪/兩周的計量單位給 Integer
static { Integer.metaClass.getFF { -> delegate * 2688 } } @Test void test_conversion_with_expando() { assertTrue 1.FF == 2688 } |
ExpandoMetaClass
的替代方法是,創(chuàng)建一個類別 包裝器類,這是從 Objective-C 借來的概念。在清單 6 中,我添加了一個(小寫) ff
屬性給 Integer
:
清單 6. 通過一個類別類添加計量單位
class FFCategory { static Integer getFf(Integer self) { self * 2688 } } @Test void test_conversion_with_category() { use(FFCategory) { assertTrue 1.ff == 2688 } } |
一個類別類是一個帶有一組靜態(tài)方法集合的普通類。每個方法接受至少一個參數(shù);第一個參數(shù)是這種方法增強的類型。例如,在 清單 6 中, FFCategory
類擁有一個 getFf()
方法,它接受一個 Integer
參數(shù)。當這個類別類與 use
關(guān)鍵字一起使用時,代碼塊內(nèi)所有相應(yīng)類型都被增強。在單元測試中,我可以在代碼塊內(nèi)引用 ff
屬性(記住,Groovy 自動將 get
方法轉(zhuǎn)換為屬性引用),如在 清單 6 的底部所示。
有兩種機制可供選擇,讓您可以更準確地控制增強的范圍。例如,如果整個系統(tǒng)使用 MPH 作為速度的默認單位,但也需要頻繁轉(zhuǎn)換為浪/每兩周,那么使用 ExpandoMetaClass
進行全局修改將是適當?shù)摹?
您可能對重新開放核心 JVM 類的有效性持懷疑態(tài)度,擔(dān)心會產(chǎn)生廣泛深遠的影響。類別類讓您限制潛在危險性增強的范圍。以下是一個來自真實世界的開源項目示例,它極好地利用了這一機制。
easyb 項目(參閱 參考資料)讓您可以編寫測試,以驗證正接受測試的類的各個方面。請研究清單 7 所示的 easyb 測試代碼片段:
清單 7. easyb 測試一個
queue
類it "should dequeue items in same order enqueued", { [1..5].each {val -> queue.enqueue(val) } [1..5].each {val -> queue.dequeue().shouldBe(val) } } |
queue
類不包括 shouldBe()
方法,這是我在測試的驗證階段所調(diào)用的方法。easyb 框架已為我添加了該方法;清單 8 中所顯示的在 easyb 源代碼中的 it()
方法定義,演示了該過程:
清單 8. easyb 的
it()
方法定義 def it(spec, closure) { stepStack.startStep(BehaviorStepType.IT, spec) closure.delegate = new EnsuringDelegate() try { if (beforeIt != null) { beforeIt() } listener.gotResult(new Result(Result.SUCCEEDED)) use(categories) { closure() } if (afterIt != null) { afterIt() } } catch (Throwable ex) { listener.gotResult(new Result(ex)) } finally { stepStack.stopStep() } } class BehaviorCategory { // ... static void shouldBe(Object self, value) { shouldBe(self, value, null) } //... } |
在 清單 8中,it()
方法接受了一個 spec (描述測試的一個字符串)和一個代表測試的主體的閉包塊。在方法的中間,閉包會在 BehaviorCategory
塊內(nèi)執(zhí)行,該塊出現(xiàn)在清單的底部。BehaviorCategory
增強 Object
,允許 Java 世界中的任何 實例驗證其值。
通過允許選擇性增強駐留在層次結(jié)構(gòu)頂層的 Object
,Groovy 的開放類機制可以輕松地實現(xiàn)為任何實例驗證結(jié)果,但它限制了對 use
塊主體的修改。
Scala 使用隱式轉(zhuǎn)換 來模擬現(xiàn)有類的增強。隱式轉(zhuǎn)換不會對類添加方法,但允許語言自動將一個對象轉(zhuǎn)換成擁有所需方法的相應(yīng)類型。例如,我不能將 isBlank()
方法添加到 String
類中,但我可以創(chuàng)建一個隱式轉(zhuǎn)換,將 String
自動轉(zhuǎn)換為擁有這種方法的類。
作為一個示例,我想將 append()
方法添加到 Array
,這讓我可以輕松地將 Person
實例添加到適當類型的數(shù)組,如清單 9 所示:
清單 9.將一個方法添加到
Array
中,以增加人員case class Person (firstName: String, lastName: String) {} class PersonWrapper(a: Array[Person]) { def append(other: Person) = { a ++ Array(other) } def +(other: Person) = { a ++ Array(other) } } implicit def listWrapper(a: Array[Person]) = new PersonWrapper(a) |
在 清單 9中,我創(chuàng)建一個簡單的 Person
類,它帶有若干個屬性。為了使 Array[Person]
(在 Scala 中,一般使用 [ ]
而不是 < >
作為分隔符)Person
可知,我創(chuàng)建一個 PersonWrapper
類,它包括所需的 append()
方法。在清單的底部,我創(chuàng)建一個隱式轉(zhuǎn)換,當我在數(shù)組上調(diào)用 append()
方法時,隱式轉(zhuǎn)換會自動將一個 Array[Person]
轉(zhuǎn)換為 PersonWrapper
。清單 10 測試該轉(zhuǎn)換:
清單 10. 測試對現(xiàn)有類的自然擴展
val p1 = new Person("John", "Doe") var people = Array[Person]() people = people.append(p1) |
在 清單 9中,我也為 PersonWrapper
類添加了一個 +
方法。清單 11 顯示了我如何使用操作符的這個漂亮直觀的版本:
清單 11. 修改語言以增強可讀性
people = people + new Person("Fred", "Smith") for (p <- people) printf("%s, %s\n", p.lastName, p.firstName) |
Scala 實際上并未對原始的類添加一個方法,但它通過自動轉(zhuǎn)換成一個合適的類型,提供了這樣做的外觀。使用 Groovy 等語言進行元編程所需要的相同工作在 Scala 中也需要,以避免過多使用隱式轉(zhuǎn)換而產(chǎn)生由相互關(guān)聯(lián)的類所組成的令人費解的網(wǎng)。但是,在正確使用時,隱式轉(zhuǎn)換可以幫助您編寫表達非常清晰的代碼。
來自 GoF 的原始解釋器設(shè)計模式建議創(chuàng)建一個新語言,但其基礎(chǔ)語言并不支持我們今天所掌握的良好擴展機制。下一代 Java 語言都通過使用多種技術(shù)來支持語言級別的可擴展性。在本期文章中,我演示了操作符重載如何在 Groovy、Scala 和 Clojure 中工作,并研究了在 Groovy 和 Scala 中的類擴展。
在下期文章中,我將展示 Scala 風(fēng)格的模式匹配和泛型的組合如何取代一些傳統(tǒng)的設(shè)計模式。該討論的中心是一個在函數(shù)式錯誤處理中也起著作用的概念,這一概念將是我們下期文章的主題。
學(xué)習(xí)
- The Productive Programmer(Neal Ford,O'Reilly Media,2008 年):Neal Ford 的新書討論了幫助您提高編碼效率的工具和實踐。
- Design Patterns: Elements of Reusable Object-Oriented Software(Erich Gamma 等人,Addison-Wesley,1994 年):關(guān)于 Gang of Four 在設(shè)計模式方面的經(jīng)典之作。
- Complex number:復(fù)數(shù)是數(shù)學(xué)抽象,在許多科學(xué)領(lǐng)域中發(fā)揮作用。
- Scala:Scala 是一種現(xiàn)代函數(shù)編程語言,適用于 JVM。
- Clojure:Clojure 是一種現(xiàn)代函數(shù)式 Lisp,適用于 JVM。
- Groovy:Groovy 是一種現(xiàn)代動態(tài) JVM 語言,具有多種函數(shù)方面。
- Operator overloading in Groovy:此頁顯示 Groovy 中支持的操作符以及其映射方法的完整列表。
- "實戰(zhàn) Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程"(Scott Davis,developerWorks,2009 年 6 月):了解有關(guān)在 Groovy 中元編程的更多信息。
- easyb:easyb 是一個使用 Groovy 開發(fā)的行為驅(qū)動開源開發(fā)工具,適用于 Groovy 和 Java 項目。
- "Drive development with easyb"(Andrew Glover,developerWorks,2008 年 11 月):了解 easyb 如何幫助開發(fā)人員及利益相關(guān)者進行協(xié)作。
- Grails:Grails 是使用 Java 和 Groovy 編寫的一種開源 Web 框架。
- Ruby on Rails:Rails 是使用 Ruby 編寫的一種開源 Web 框架,運行于 JRuby 上。
- 瀏覽 技術(shù)書店,閱讀有關(guān)這些主題和其他技術(shù)主題的圖書。
- developerWorks 中國網(wǎng)站 Java 技術(shù)專區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個方面的文章。
獲得產(chǎn)品和技術(shù)
- 下載 IBM 產(chǎn)品評估試用版軟件 或 IBM SOA 人員沙箱,并開始使用來自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的應(yīng)用程序開發(fā)工具和中間件產(chǎn)品。
討論

Neal Ford 是一家全球性 IT 咨詢公司 ThoughtWorks 的軟件架構(gòu)師和 Meme Wrangler。他的工作還包括設(shè)計和開發(fā)應(yīng)用程序、教材、雜志文章、課件和視頻/DVD 演示,而且他是各種技術(shù)書籍的作者或編輯,包括最近的新書 The Productive Programmer 。他主要的工作重心是設(shè)計和構(gòu)建大型企業(yè)應(yīng)用程序。他還是全球開發(fā)人員會議上的國際知名演說家。請訪問他的 Web 站點。
posted @ 2012-08-20 13:37 小胡子 閱讀(305) | 評論 (0) | 編輯 收藏
posted @ 2012-08-16 15:06 小胡子 閱讀(2401) | 評論 (0) | 編輯 收藏
posted @ 2012-08-08 10:19 小胡子 閱讀(1094) | 評論 (0) | 編輯 收藏
可以統(tǒng)一設(shè)置一個默認字體,步驟如下:
1.選擇格式選項卡,點擊文本樣式
2.選擇需要的樣式,并將要修改的項選擇全部,單擊確定就搞定了
posted @ 2012-08-07 10:38 小胡子 閱讀(10494) | 評論 (0) | 編輯 收藏
好的網(wǎng)站設(shè)計需要有好的效果技巧組合,無論你是一個自由者,設(shè)計師,要想使網(wǎng)站效果突出,絢麗,jquery是你一個不二的選擇,利用jQuery,你可以很容易地創(chuàng)建自己的圖像幻燈片,UI設(shè)計界面,完全與眾不同的風(fēng)格以達到驚人的過渡效果。有了這些功能,它可以幫助您的網(wǎng)上脫穎而出,提升您的品牌標識。今天的這些jquery插件,今天他被在60%的網(wǎng)站上使用,如果你正在創(chuàng)建web項目,這個是不錯的選擇
TN3 Gallery – jQuery Slider and Image Gallery
Easy Slider 1.7 – Numeric Navigation jQuery Slider
Easy Slider 是一個滑動門插件,支持任何圖片或內(nèi)容,當點擊時實現(xiàn)橫向或縱向滑動。它擁有一系列豐富的參數(shù)設(shè)置,可通過CSS來進行完全的控制。所以,基本上你只需要鏈接這個插件文件后,設(shè)置好內(nèi)容,然后樣式化CSS就可以了。
RoyalSlider – Touch-Enabled jQuery Image Gallery
SlideDeck
slideViewer (a jQuery image slider built on a single unordered list) 1.2
利用這個jQuery插件,只需幾行代碼就能夠?qū)⒁唤M無序的圖片列表轉(zhuǎn)換成可導(dǎo)航控制的相冊。
jqFancyTransitions – slideshow with strip effects
jqFancyTransitions這個jQuery插件能夠以奇特的切換效果幻燈展示圖片。它支持三種切換效果包括:波浪、拉鏈和卷簾??梢詾閳D片設(shè)置標題,切換速度等。
AviaSlider – jQuery Slideshow
AviaSlider是一個獨特的slidershow插件,支持8種不同的圖片切換效果。支持圖片預(yù)加載,圖片自動播放當用戶交互時停止。
Presentation Cycle – Cycle with a progressbar
Nivo Slider
Nivo Slider是一款出色的jQuery幻燈片插件,支持多種切換效果,可定制性強.
jQuery Easy Slides v1.1
Advanced Slider – jQuery XML slider
Horinaja
Horinaja是一個可定制、易于安裝、跨瀏覽器的Slideshow控件。當鼠標移到Slideshow區(qū)域時,內(nèi)容項目停止?jié)L動切換。當鼠標移開時,內(nèi)容項目自動滾動切換。提供基于scriptaculous/prototype和jQuery兩種版本
mbSlider
by mike182uk
Dragdealer JS
Smooth Div Scroll
Supersized jQuery Plugin
Pikachoose
PikaChoose是一個超輕量級的圖片畫廊jQuery插件,它是可以輕而易舉地使用在您的網(wǎng)站。 有許多的網(wǎng)站使用了他,在他的主頁上可以看到這些網(wǎng)站。
UnoSlider – Responsive Touch Enabled Slider
bxSlider
bxSlider是一個jQuery插件用于創(chuàng)建簡單的內(nèi)容滑動變換容器??梢栽O(shè)置是否自動滑動,滑動速度等。
slideViewerPro 1.5
Slides
Simple Controls Gallery v1.4
jQuery OneByOne Slider Plugin
s3Slider jQuery plugin
jQuery s3Slider 就是一個效果非常棒的產(chǎn)品展示用 Plugin ,它讓我們單純使用 HTML 就能達到如同 Flash 般的動畫效果,而且配合適當?shù)?CSS 時,更能在展示產(chǎn)品呈現(xiàn)一流的設(shè)計感。
JCoverflip
jCoverflip是一個jQuery插件用于創(chuàng)建類似于Coverflow的界面,可以展示圖片或其它任意內(nèi)容。內(nèi)容既可以單擊其中的某個項目進行瀏覽,也可以拖動Slider(jQuery UI slider)瀏覽。
Elastislide – A Responsive jQuery Carousel Plugin
Orbit: A Slick jQuery Image Slider Plugin
LayerSlider – The Parallax Effect Slider
jQuery UI Slider
FLEXSLIDER
simpleSlide
Sliding Image Gallery jQuery Plugin
Translucent – jQuery Banner Rotator / Slideshow
Blueberry Slider
Coin Slider
jQuery.popeye
Query.popeye這個插件能夠?qū)⒁唤M無序的圖片列表轉(zhuǎn)換成一個簡單的相冊。當點擊圖片時將以Lightbox風(fēng)格放大圖片。圖片展示框提供 向前/ 向后控制并能夠為每一張圖片添加備注說明信息。jQuery.popeye能夠根據(jù)圖片大小自動調(diào)整展示框的高度和寬度。
Colorbox
這個是jquery的彈窗效果插件
jQuery Slider Evolution
Coda-Slider
AnythingSlider jQuery Plugin
jQuery Multimedia Portfolio
jQuery Multimedia Portfolio支持多種多媒體格式,包括:圖片,視頻(flv), 音頻(mp3), 并能自動偵測每個文件的擴展名再分別調(diào)用合適的播放。
jQuery Cycle Plugin
jCycle 是一種新的插件jQuery致力于添加影像的平穩(wěn)過渡。它支持6個不同的過渡類型而且很容易使用。自由職業(yè)者,相信您的客戶會喜歡它的!
Estro – jQuery Ken Burns & swipe effect slider
ResponsiveSlides.js
ResponsiveSlides.js 是一個微型的 jQuery 插件用來創(chuàng)建響應(yīng)式的幻燈展示效果,對 <ul> 標簽中的圖片進行自動幻燈展示,支持幾乎所有瀏覽器包括 IE6。也可設(shè)置 max-width 屬性并對 IE6 有效。
wmuSlider
FSS – Full Screen Sliding Website Plugin
Agile Carousel
jQuery Slider plugin
jQuery Carousel Evolution
Skitter Slideshow
posted @ 2012-08-03 23:30 小胡子 閱讀(2971) | 評論 (0) | 編輯 收藏
posted @ 2012-08-03 17:06 小胡子 閱讀(218) | 評論 (0) | 編輯 收藏