Sử dụng AOP với Spring.Net

I. AOP là cái gì?


- Ai chẳng biết AOP là từ viết tắt của Aspect Oriented Programming, mấy bác thích dịch ra tiếng Việt thì bảo rằng AOP là “phương pháp lập trình hướng khiá cạnh”. Có lẽ theo cách hiểu ấy nên không ít lần tôi nhìn thấy mấy cái slide kiểu trò chơi ghép hình trong các seminar về AOP hồi học đại học. Lúc đó thì OOP còn chưa lĩnh hội hết chứ đừng nói AOP, bởi vậy xem xong cũng chẳng hiểu chi cả. :bbpcuoi3:Đến bây giờ khi đã hiểu được những khái niệm cơ bản, tôi thích gọi AOP là “A Ô Pê” hơn, khỏi dịch ra chi cho lôi thôi. Trong bài viết này, tôi sẽ giới thiệu về AOP và cách sử dụng AOP với Spring.NET.

AOP http://nthoai.blogspot.com

- Trong khi xây dựng các chương trình ứng dụng, chúng ta sẽ có rất nhiều những vấn đề liên quan đến phần mềm mà ta phải quan tâm. Tôi xin lấy ví dụ sau đây: tôi phải viết một chương trình để upload files, folders lên một server. Vấn đề khá đơn giản phải không? Nếu object được chọn là file thì chỉ việc upload cái file đó lên. Còn nếu object được chọn là một folder thì upload hết các file con bên trong folder đó, sau đó lại duyệt các folder con của nó và gọi hàm đệ quy. Hê hê, nhưng công việc không chỉ có vậy, tôi được yêu cầu phải thêm nhiều cái linh tinh khác như phải kiểm tra xem 1 folder, 1 file có tồn tại chưa, phải kiểm tra xem user hiện tại có đủ quyền hay không, phải ghi mọi thao tác ghi xóa hay thông báo lỗi xuống file log, phải hiển thị process bar, phải thế này, phải thế kia. Kết quả là hàm upload của tui trở thành một đống bùi nhùi, phần code phục vụ cho việc đọc, ghi file chỉ có vài dòng trong khi những code cho các việc kiểm tra, thông báo lỗi, hiển thị process bar chiếm rất nhiều nội dung của một hàm. Ngoài ra code của tôi bị phụ thuộc lẫn nhau ở nhiều nơi, chẳng hạn như những xử lý đọc ghi file do cần phải update process trên process bar nên nó lại phải reference đến project winform, đúng là rồi hơn chữ “RỐI”.:bbptuc:

- Trong chương trình này, vấn đề quan tâm chính của tôi là upload folder/files và bên cạnh đó sẽ có một số vấn đề khác cần phải thực hiện. Các bạn có thể thấy rằng trong khi viết phần mềm, ta thường gặp hai loại “concern”: core concerns và cross-cutting concerns:

+ Core concern: là requirement chính của chương trình, ví dụ như upload file/folder, đọc danh sách user, …

+ Cross-cutting concerrns: là những xử lý phụ cần được thực hiện khi “core concern” được gọi. Cross-cuttong concerns thường xảy ra nhiều nơi trong chương trình, nó có thể xảy ra trong nhiều layer của ứng dụng, nhiều class, nhiều method; chẳng hạn như tính toán thời gian chạy các hàm đọc ghi database, ghi log lại mỗi lần cập nhật thông tin user, …

- Đã Từ lâu, người ta đã nghĩ ra chiêu thức AOP để giải quyết vấn đề trên. Aspect-Oriented Programming(AOP) còn được gọi là Aspect-Oriented Software Development (AOSD) là một nguyên tắc thiết kế giúp tách rời các yêu cầu hay các vấn đề được quan tâm (separation of concerns) trong chương trình thành các thành phần độc lập và từ đó tăng tính uyển chuyển cho chương trình. “Separation of concerns” là một trong những kĩ thuật được quan tâm nhất trong ngành phần mềm. Người ta cho rằng những vấn đề tương tự nhau nên được giải quyết trong một “unit of code”. Khi lập trình thủ tục, một “unit of code” là 1 function, 1 method. Còn trong lập trình hướng đối tượng thì “unit of code” là một class.:bbpraroi:

II. Những lợi ích của “separate of concerns”


- Trong AOP, từ “Aspect” chính là vấn đề [concern] người lập trình quan tâm và nó xuất hiện trong rất nhiều class cũng như nhiều method khác nhau. Kĩ thuật AOP thường được sử dụng để giải quyết các vấn đề như caching, tracking, security hay failure injections. Vì thế, nhiều tài liệu nói rằng AOP giúp module hóa ứng dụng, biến chương trình thành các module hoạt động độc lập, mỗi module làm một chức năng riêng, từ đó dễ bảo trì và nâng cấp.:bbpraroi:

- Ở vai trò của người thiết kế phần mềm, chúng ta nên đưa ra các cách làm đơn giản nhất. Để thỏa mãn yêu cầu của chương trình, người ta sẽ tạo ra thành phần chính của chương trình (gồm các class/component/method); các chức năng bổ sung như loging, tính toán performance, .. cũng sẽ được xem xét để tạo ra. Vì do các chứng năng bổ xung này không phải là yêu cầu chính của hệ thống nên người ta sẽ có yêu cầu bật tắt chúng theo ý muốn. Vậy làm thế nào để có thể tạo ra chương trình có thể linh hoạt được như thế? Câu trả lời là “Separate of concerns”

- Ở vai trò của người lập trình, chúng ta có hai vấn đề cần quan tâm là xử lý logic chính của chương trình và các xử lý logic cho những thành phần phụ. Do đó tất nhiên sẽ phải tạo ra các class/method cho các yêu cầu thực sự, và tạo ra những class/method khác độc lập để thực hiện các yêu cầu phụ kia. Tất cả các class/method này có thể được kết hợp lúc runtime theo ý muốn. Chẳng hạn như trong môi trường test, người ta có thể bật chức năng log, đo đạc performance để theo dõi nhưng khi cài đặt trên môi trường Production, các chức năng phụ này có thể được disable. Và trên nguyên tắc, trong code của những xử lý logic chính sẽ không có code để thực hiện các yêu cầu phụ.

- Tóm lại, ta có thể liệt kê một số lợi ích như sau:
+ Chức năng chính của chương trình không cần biết đến các chức năng phụ khác
+ Các chức năng phụ có thể được thêm thắt, bật tắt lúc runtime tùy theo yêu cầu
+ Các thay đổi, sửa lỗi, nâng cấp nếu có đối với các chức năng phụ sẽ không ảnh hưởng đến chương trình chính.
+ Hệ thống sẽ uyển chuyển và giảm thiểu tính phụ thuộc lẫn nhau của các module:bbpxtay:

III. Các thuật ngữ thường gặp trong AOP


+ Join point: Một điểm trong chương trình. Ví dụ như ta cần ghi log lại sau khi chạy method thì điểm ngay sau method đó được thực thi gọi là một jointpoint để có thể chạy những xử lý ghi file log. Join point theo nghĩa của nó có thể hiểu là những nơi có thể được chèn những “custom action” của bạn.

+ Pointcut: Có nhiều cách để xác định joinpoint, những cách như thế được gọi là pointcut.

+ Advice: là những xử lý phụ được thêm vào xử lý chính. Code để thực hiện các xử lý đó được gọi là Advice. Vì các xử lý phụ có thể thêm vào trước hoặc sau hoặc cả hai đối với những xử lý chính nên có nhiều loại Advice khác nhau mà ta sẽ đề cập trong những phần sau.

IV. AOP trong Spring.NET


- AOP là một trong những chức năng chính được hỗ trợ trong Spring.NET. Với AOP framework của Spring.NET bạn có thể thoải mái thêm thắt các “custom action” (Advice) của mình vào một hay nhiều function tại những vị trí (Joinpoint) mong muốn tùy theo những trường hợp cụ thể (Pointcut). Trong bài viết này, tôi sẽ trình bày một sample code thể hiện ví dụ đơn giản để sử dụng AOP framework của Spring.NET. Nếu bạn đã biết về Dependency Injection sử dụng Spring.NET thì bạn sẽ thấy rằng AOP của Spring.NET cách sử dụng gần như tương tự.:bbpskien:

IV.1 Sử dụng Advice


- Xin nhắc lại điều này: tôi gọi Advice là những “custom action” mà bạn thêm vào chương trình của mình. Cụ thể hơn nữa, Advice là code bạn viết để xử lý những “custom action” đó, và tất nhiên theo nguyên tắc của AOP thì những code này không hề phụ thuộc vào chương trình chính. Có thể nói rằng bạn sẽ tạo một project độc lập với các project có sẵn trong solution của bạn, sau đó thêm vào các class, các method để xử lý các “custom action”. Tất nhiên các class Advice sử dụng AOP của Spring phải được viết theo quy tắc của Spring.NET AOP Framework, nó sẽ phải implement interface IMethodInterceptor (Có nhiều interface tương tự ứng với các loại Advice khác nhau). Và cuối cùng, các Advice mà bạn viết sẽ được gắn vào nơi cần thiết dựa vào xml config của Spring.NET.

- Chúng ta hãy xem một ví dụ đơn giản để thấy Advice hoạt động như thế nào. Code sample dưới đây sẽ thêm chức năng print vài messages ra console trước và sau khi method chính được gọi. Do Advice được hiểu như một “custom action” cho một method nào đó (các bạn chú ý đối tượng được thêm “custom action” thường là các method, và chúng được hiểu là các “target of the advice”), nên trong các tài liệu ta sẽ thấy ghi là “advised method”. Như vậy “advised method” sẽ là method chính, còn các “Advice” chứa các xử lý được thêm vào “advised method”. Cũng phải thừa nhận rằng sample dưới đây không thể hiện hết tất cả khả năng của AOP, nhưng nó sẽ giúp bạn hiểu rõ về khái niệm Advice trong AOP. Và khi đã nắm được rồi, bạn sẽ có thể apply những Advice của chính bạn viết cho những chương trình của bạn.

- Trước khi xem code của Advice trong sample này, chúng ta hãy xem qua các domain classes dưới đây. Khi method Excute() trong class ServiceCommand được gọi, nó sẽ print ra console một thông báo đơn giản. Class ServiceCommand này sẽ là “target of the advice”, method “Excute()” sẽ là “advised method”; trong các thuật ngữ của Spring.NET AOP, một instance của class ServiceCommand sẽ được gọi là “advised object”. Các bạn chú ý rằng interface ICommand và class ServiceCommand là những class và interface dùng để làm ví dụ chúng không phải là các type của Spring.AOP. Có thể bạn sẽ thắc mắc tại sao phải tạo interface ICommand rồi để class ServiceCommand implement nó, câu trả lời là để tăng tính “Loosed coupling”. Ngoài ra, cách sử dụng AOP trong Spring.NET rất giống với DI trong Spring, các object (trong trường hợp này là instance của ServiceCommand”) được Spring tạo ra theo cách bạn config trong file xml nên trong chương trình chính ta sẽ sử dụng ICommand để khai báo thay vì khai báo một class cụ thể ServiceCommand.
public interface ICommand
{

object Execute(object context);

}

public class ServiceCommand : ICommand
{

public object Execute(object context)
{

Console.Out.WriteLine("Service implementation : [{0}]", context);
return null;

}

}

Code 1

- Đoạn code phía dưới đây sẽ là code của một Advice, Advice này sau đó sẽ được “gắn” vào method Excute() của class ServiceCommand. Như tôi đã giới thiệu, có nhiều loại Advice khác nhau, trong ví dụ này ta sẽ tạo một “Around Advice”, nghĩa là ta có thể thêm những “custom action” vào cả trước và sau khi gọi method Excute() của class ServiceCommand. (Tham khảo Spring.NET Section 13.3.2 “Advice types”)

public class ConsoleLoggingAroundAdvice : IMethodInterceptor
{

public object Invoke(IMethodInvocation invocation)
{

Console.Out.WriteLine("Advice executing; calling the advised method...");
object returnValue = invocation.Proceed();
Console.Out.WriteLine("Advice executed; advised method returned " + returnValue);
return returnValue;

}

}

Code 2

- Đến lúc này ta đã tạo ra 3 thành phần: interface (ICommand); class (ServiceCommand); và một Advice: class (ConsoleLoggingAroundAdvice). Phần còn lại là làm sao để “gắn” Advice này vào method Excute() của class ServiceCommand. Sau đây là cách kết nối các thành phần trên bằng code.

ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingAroundAdvice());
ICommand command = (ICommand) factory.GetProxy();
command.Execute("This is the argument");

Code 3

Và đây là kết quả khi chạy chương trình


Advice executing; calling the advised method...
Service implementation : [This is the argument]
Advice executed; advised method returned

Hình 1
Các bạn đang xem bài viết về Sử dụng AOP với Spring.NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Output của chương trình cho ta thấy rằng Advice của chúng ta đã được applied “around” phần xử lý logic của “advised method” Excute()

- Có thể bạn sẽ thắc mắc ProxyFactory là cái gì ở đây? Constructor của class ProxyFactory sử dụng một argument đầu vào là kiểu của một object ta muốn thêm vào những Advice (trong sample này, thì argument cho hàm khởi tạo là instance của class ServiceCommand). Sau đó ta sẽ thêm các Advice (một instance của class ConsoleLoggingAroundAdvice) bằng cách sử dụng method AddAdvice(). Kế tiếp ta gọi method GetProxy() để trả về một proxy (AOP proxy), proxy này có thể gọi là một object hành nhái thuộc kiểu ICommand. Khi gọi method Excute() của proxy này, các xử lý của Advice và “advised method” sẽ lần lược được thực hiện. Hình bên dưới thể hiện quy trình hoạt động của Spring.NET AOP proxy

Spring.NET AOP
Hình 2
Các bạn đang xem bài viết về Sử dụng AOP với Spring.NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Có một điểm đáng lưu ý là AOP proxy trả về khi gọi method GetProxy() được cast về kiểu ICommand. Hiện nay, có vẽ như chúng ta buộc phải làm như vậy cho các “advised object”. Nói theo cách khác để cho các class của bạn có thể sử dụng được với AOP của Spring.NET, các class đó phải implement ít nhất một interface. Trong thực tế thì đòi hỏi này không đến nỗi phiền hà như vậy vì nói chung ta nên sử dụng interface khi có thể. (Chức năng hỗ trợ AOP cho class mà không cần implement interface đang được Spring hứa hẹn đưa ra trong các phiên bản sau).

- Khi sử dụng AOP của Spring.NET, ta sẽ không cần phải “gắn” các Advice bằng code theo kiểu ở trên. Đoạn config xml dưới đây sẽ làm y chang những gì phần code 3 thực hiện:


<object id="consoleLoggingAroundAdvice"
type="Spring.Examples.AopQuickStart.consoleLoggingAroundAdvice"/>

<object id="myServiceObject"
type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/>

</property>

<property name="interceptorNames">
<list>
<value>consoleLoggingAroundAdvice</value>

</list>

</property>

</object>

Code 4

ICommand command = (ICommand) ctx["myServiceObject"];
command.Execute();

Code 5

- Lại có một số điểm cần lưu ý đối với đoạn config xml này; đoạn config này được thêm vào file config của chương trình (app.config hoặc web.config). Trong toàn bộ hệ thống thì ConsoleLoggingAroundAdvice bản thân nó cũng chỉ là một object thông thường, và vì thế nó được config theo cách thông thường như những class khác. Do đó nếu chúng ta muốn “inject” nó bằng những kĩ thuật như DI của Spring.NET thì cách làm cũng bình thường.

- Điểm lưu ý thứ 2: Class ProxyFactoryObject là một class implement từ IFactoryObject interface, nó có nhiệm vụ tạo ra một proxy object tương ứng với “target” được config. Trong trường hợp này, ta muốn chương trình của mình sẽ tạo ra một proxy của class ServiceCommand nên “target” sẽ được khai báo là namespace class ServiceCommand.

- Cuối cùng, ta nên lưu ý rằng Advice sẽ được “gắn” vào target object bằng cách khai báo trong phần interceptorNames. Trong ví dụ này, chỉ có một Advice được khai báo, nếu như có thêm các class Advice khác, chỉ việc thêm xml config cho chúng và thêm vào list của interceptorNames.

Các bạn có thể download code sample ở đây:
http://nthoaiblog.googlepages.com/SampleSpringAOP.zip

IV.2 Sử dụng Pointcut


- Trong ví dụ trước, ConsoleLoggingAroundAdvice được khai báo dù trong code hay trong file xml đều sẽ được “gắn” vào mọi method của ServiceCommand. Nếu chúng ta muốn nó chỉ “gắn” vào một hoặc nhiều method của class này theo một số tiêu chí nào đó thì đã đến lúc ta nghĩ đến việc sử dụng Pointcut. Các tiêu chí ở đây khá đa dạng: có thể bạn muốn những method với tên method bắt đầu bằng “Start” sẽ được “advised”; hoặc bạn chỉ muốn những method được gọi với các giá trị argument đặc biệt lúc runtime; hay là rất có thể bạn chỉ muốn các Advice được thêm vào những method được đánh dấu bằng attribute đặc biệt nào đó.

- Tất cả những yêu cầu trên được Spring.NET đóng gói trong interface IPointcut (tham khảo Spring AOP Section 13.2 “Pointcut API in Spring.NET"). Bây giờ chúng ta sửa class
ServiceCommand, thêm vào method DoExcute(), sau đó sẽ thực hiện một số thay đổi để chỉ method DoExcute() được “advised”:

public interface ICommand
{

void Execute();

void DoExecute();

}

public class ServiceCommand : ICommand
{

public void Execute()
{

Console.Out.WriteLine("Service implementation : Execute()...");

}

public void DoExecute()
{

Console.Out.WriteLine("Service implementation : DoExecute()...");

}

}

Code 6

- Tất nhiên class ConsoleLoggingAroundAdvice sẽ không cần phải thay đỏi gì cả. Tiếp đến chúng ta sẽ config cho class proxy như sau:

ProxyFactory factory = new ProxyFactory(new ServiceCommand());

factory.AddAdvisor(new DefaultPointcutAdvisor(

new SdkRegularExpressionMethodPointcut("Do"),
new ConsoleLoggingAroundAdvice()));


ICommand command = (ICommand) factory.GetProxy();
command.DoExecute();

Code 7


- Và đây là kết quả khi chạy chương trình


Intercepted call : about to invoke next item in chain...
Service implementation...
Intercepted call : returned

Hình 3
Các bạn đang xem bài viết về Sử dụng AOP với Spring.NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Tất nhiên khi ta gọi Excute() thay vì DoExcute(), những xử lý của Advice trên sẽ không hiện ra:

ProxyFactory factory = new ProxyFactory(new ServiceCommand());

factory.AddAdvisor(new DefaultPointcutAdvisor(

new SdkRegularExpressionMethodPointcut("Do"),
new ConsoleLoggingAroundAdvice()));


ICommand command = (ICommand) factory.GetProxy();
// Note that there is no 'Do' in this method name
command.Execute();

Code 8


Service implementation...

Hình 4
Các bạn đang xem bài viết về Sử dụng AOP với Spring.NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Sau đây là đoạn XML config cho tất cả những yêu cầu trên:


<object id="consoleLoggingAroundAdvice"
type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor">
<property name="pattern" value="Do"/>
<property name="advice">
<object type="Spring.Examples.AopQuickStart.consoleLoggingAroundAdvice"/>

</property>

</object>

<object id="myServiceObject"
type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/>

</property>

<property name="interceptorNames">
<list>
<value>consoleLoggingAroundAdvice</value>

</list>

</property>

</object>

Code 9

- Như ta thấy đoạn config chỉ thay đổi ở phần consoleLoggingAroundAdvice. RegularExpressionMethodPointcutAdvisor đã được thay vào Advisor ban đầu; các property “pattern” và “Advice” được config để xác định Advice tương ứng và pattern tương ứng để match với method có chứa text “Do” trong các method của đối tượng được “advised”.

Download code sample cho phần Pointcut ở đây:
http://nthoaiblog.googlepages.com/SampleSpringAOPWithPointcut.zip

V. Kết luận


- Đây không phải là bài viết tốt nhất về AOP cũng như AOP trong Spring.NET. Tôi đã dịch lại và bổ xung một số ý từ các tài liệu ở phần tham khảo phía dưới. Ban đầu tôi cũng gặp khó khăn khi tìm hiểu cách sử dụng vì Spring.NET đưa ra khá nhiều các khái niệm, thuật ngữ về AOP. Nhưng cuối cùng tôi thấy cách sử dụng AOP với Spring.NET khá giống như chức năng Dependency Injection. Bài viết này chỉ nói đến những chức năng đơn giản của Spring.NET AOP nên trong bài viết sau ta sẽ nói đến các chức năng khác của AOP trong Spring.NET như các loại Advice khác, cách sử dụng Attribute để xác định Pointcuts, … Đối với ai mới research cách sử dụng AOP của Spring.NET, ngoài việc đọc bài này hehe :bbpcuoi3:, tôi đề nghị download Spring.NET framework về và chạy các sample AOP của nó, vô cùng dễ hiểu.

(Xem Phần 2)

Posted in Labels: , , |

0 comments:

rss
 

About Me

Place I've live
Near Bossley Park, Sydney, NSW, Australia
Place I've work
  • Freelancer (from 06/2010 to present)
  • Harvey Nash (from 05/2008 to 06/2010)
  • DataDesign Vietnam (10/2005 to 04/2008)
Place I've studied
  • University of Natural Science (Bachelor of Science HoChiMinh City Vietnam From 2001 to 2005)
  • Le Hong Phong High School (HoChiMinh City Vietnam From 1997 to 2000)