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

- Trong bài viết trước, chúng ta đã có cơ hội làm quen với AOP và cách sử dụng AOP trong Spring.NET với những khái niệm về Advice, Pointcut. Tất nhiên còn nhiều thứ khác được hỗ trợ để lập trình AOP với Spring.NET. Bài viết này sẽ tiếp tục giới thiệu các loại Advice khác nhau, các Pointcuts khác mà ta có thể sử dụng.

I.Có nhiều loại Advice được hỗ trợ sẵn trong Spring.Net



- Ở code sample trong bài viết trước, Advice được sử dụng là “around advice”. Đôi lúc, chúng ta chỉ muốn thêm “custom action” vào trước hoặc sau khi xử lý logic chính trong function của chúng ta được thực hiện. Tất nhiên sử dụng “around advice” cũng được thôi, nhưng có thể gây “confused” cho người khác khi đọc code. Nếu xem lại code của class ConsoleLoggingAroundAdvice và muốn sửa lại để nó chỉ thực hiện việc thông báo mỗi lần method chính được gọi thì sao? Thật ra ta chỉ cần thực hiện một “custom action” trước khi xử lý chính được gọi (trong trường hợp này, ta cần print ra màn hình một message thông báo tên của method sắp được gọi chẳng hạn). Người ta thường khuyên rằng chỉ nên sử dụng những gì ta cần (Ý tưởng gần giống với nguyên tắc ISP – Interface Segregation Principle) và Spring.NET có một loại Advice khác với “around advice” mà ta có thể dùng được. Nếu chỉ cần làm gì đó trược ghi gọi hàm chính, cách đơn giản nhất là sử dụng “before advice”.

I.1 Before advice


- “before advice” là … một advice chạy trước khi method chính được gọi. Vì thế nó không thể và không nên return cái gì cả. Thực sự thì đó là một điều tốt bời vì bạn sẽ không bao giờ vô ý quên gọi function Proceed() để thực hiện xử lý logic chính; và bạn cũng sẽ không bao giờ quên return kết quả trả về của method chính. Nếu ta không cần đọc hoặc sửa đổi giá trị trả về và cũng không cần làm một thao tác gì đặc biệt sau khi hàm chính thực hiện thành công thì “before advice” là tất cả những gì ta cần.

- “before advice” trong Spring.NET được sử dụng bằng cách implement ImethodBehoreAdvice. Hãy xem code sample sau đây:
public class ConsoleLoggingBeforeAdvice : IMethodBeforeAdvice
{

public void Before(MethodInfo method, object[] args, object target)
{

Console.Out.WriteLine("Intercepted call to this method : " + method.Name);
Console.Out.WriteLine(" The target is : " + target);
Console.Out.WriteLine(" The arguments are : ");
if ( args != null )
{

foreach (object arg in args)
{

Console.Out.WriteLine("\t: " + arg);

}

}

}

}

Code 1

- Tương tự như lần trước, ta xem cách “apply” advice mới này vào lớp ServiceCommand bằng code như thế nào:
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingBeforeAdvice());
ICommand command = (ICommand) factory.GetProxy();
command.Execute();

Code 2

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

Intercepted call to this method : Execute
The target is : Spring.Examples.AopQuickStart.ServiceCommand
The arguments are :

Hình 1

- Kết quả hiện đúng như những gì chúng ta mong muốn. Như vậy sử dụng “before advice” sẽ dễ dàng hơn và chúng ta sẽ không phải nhớ gọi Proceed() ở hàm chính cũng như return value nếu có. Sau đây là config XML cho “before advice”:
<object id="beforeAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/>

<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>beforeAdvice</value>

</list>

</property>

</object>

Code 3

I.2 After advice


- Tương tự như “before advice”, “after advice” là advice được thực hiện sau khi method chính của chúng ta được thực thi. Khi sử dụng “after advice” trong Spring.NET, ta phải implement IAfterReturningAdvice. Hãy xem đoạn code sample cho lớp ConsoleLoggingAfterAdvice
public class ConsoleLoggingAfterAdvice : IAfterReturningAdvice
{

public void AfterReturning(
object returnValue, MethodInfo method, object[] args, object target)
{

Console.Out.WriteLine("This method call returned successfully : " + method.Name);
Console.Out.WriteLine(" The target was : " + target);
Console.Out.WriteLine(" The arguments were : ");
if ( args != null )
{

foreach (object arg in args)
{

Console.Out.WriteLine("\t: " + arg);

}

}
Console.Out.WriteLine(" The return value is : " + returnValue);

}

}

Code 4

ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingAfterAdvice());
ICommand command = (ICommand) factory.GetProxy();
command.Execute();

Code 5

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

This method call returned successfully : Execute
The target was : Spring.Examples.AopQuickStart.ServiceCommand
The arguments were :
The return value is : null

Hình 2

- Output của console cho ta thấy advice trên đã được gọi sau khi method chính được gọi. Cũng như “before advice” khi sử dụng “after advice” chúng ta cũng không sợ phải quên gọi method Proceed() để thực hiện xử lý chính. Tuy nhiên chúng ta có thể access đến giá trị trả về của Method chính (nếu có). Mặc dù không cần phải return giá trị trả về này như khi dùng “around advice”, chúng ta vẫn có thể thay đổi giá trị của nó.
- Có thể nói sử dụng kết hợp “before advice” và “after advice” sẽ tránh những lỗi vô ý như khi dùng “around advice”. Sau đây là config XML để khai báo “after advice”:
<object id="afterAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/>

<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>afterAdvice</value>

</list>

</property>

</object>

Code 6

I.3 Throws advice


- 3 loại advice đã bàn tới có thể nói đã thõa mãn mọi yêu cầu khi lập trình AOP. Tuy nhiên, vẫn còn một loại khác cũng rất hữu ích: “throws advice”
public interface IThrowsAdvice : IAdvice
{
}

Code 7

- Như tên gọi của nó, “throws advice” sẽ được gọi khi một “advised method” tung ra một exception. Do đó nếu “advised method” của chúng ta chạy trơn tru không văng lỗi gì thì Advice loại này sẽ không được gọi đến. Chúng ta có thể sử dụng “throws advice” để quản lý lỗi trong chương trình bằng cách apply nó cho tất cả các class ta muốn; chẳng hạn ta có thể ghi log lại khi có lỗi, hoặc email cho support team khi có lỗi xảy ra…
- Để sử dụng “throws advice” trong Spring.NET, ta phải implement IthrowsAdvice. Hãy xem code sample dưới đây:
public class ConsoleLoggingThrowsAdvice : IThrowsAdvice
{

public void AfterThrowing(Exception ex)
{

Console.Out.WriteLine("Advised method threw this exception : " + ex);

}

}

Code 8

- Để test được Advice loại này, hãy thử sửa lại code của function Excute() như sau:
public class ServiceCommand : ICommand
{

public void Execute()
{

throw new UnauthorizedAccessException();

}

}

Code 9

- Cách apply “throws advice” của chúng ta bằng code cũng tương tự như những loại khác:
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingThrowsAdvice());
ICommand command = (ICommand) factory.GetProxy();
command.Execute();

Code 10

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

Advised method threw this exception : System.UnauthorizedAccessException:
Attempted to perform an unauthorized operation.

Hình 3

- Trong Spring.NET, sử dụng “throws advice” có nghĩa là bạn phải tạo một lớp implement IthrowsAdvice, sau đó, cho mỗi loại Exception bạn muốn lớp advice này có thể bắt được (tương tự như ý tưởng try – catch), ta phải khai báo một method với tham số Exception như sau:
void AfterThrowing(Exception ex)

Code 11

- Tên của method là AfterThrowing là cố định và không thể thay đổi, nhưng ta có thể tạo các overloading khác nhau tương ứng với các loại Exception ta muốn advice của mình có thể bắt được:
void AfterThrowing(ArgumentException ex)

Code 12

- Sau đây là config XMl cho throwsAdvice:
<object id="throwsAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/>

<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>throwsAdvice</value>

</list>

</property>

</object>

Code 13

I.4 Layering advice


- Nếu chúng ta apply nhiều advice vào trong cùng một đối tượng mong muốn, các advice đó sẽ được gọi theo đúng thứ tự được thêm vào, điều này hiển nhiên đúng với các advice cùng một loại. Nếu nhiều advice với các loại khác nhau được sử dụng, thì những advice sẽ được gọi theo nguyên tắc đơn giản “before advice” sẽ gọi trước “after advice” cho dù after around được thêm vào trước. Nếu xét ví dụ code sample dưới đây thì thứ tự được gọi sẽ là BeforeAdvice, AroundAdvice, xử lý chính, AroundAdvice, AfterAdvice:
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingBeforeAdvice());
factory.AddAdvice(new ConsoleLoggingAfterAdvice());
factory.AddAdvice(new ConsoleLoggingThrowsAdvice());
factory.AddAdvice(new ConsoleLoggingAroundAdvice());
ICommand command = (ICommand) factory.GetProxy();
command.Execute();

Code 14

- Và sau đây là config xml cho tất cả các advice, các advcie sẽ được “gắn” theo đúng thứ tự ta khai báo trong file congig:
<object id="throwsAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/>
<object id="afterAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/>
<object id="beforeAdvice"
type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/>
<object id="aroundAdvice"
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>throwsAdvice</value>
<value>afterAdvice</value>
<value>beforeAdvice</value>
<value>aroundAdvice</value>

</list>

</property>

</object>

Code 15

- Ngoài ra, ta có thể implement thêm IOrdered cho các lớp Advice để buộc chúng thực thi theo thứ tự mong muốn.

II. Sử dụng Attributes để định nghĩa Pointcuts


- Các Pointcut có thể được xác định dựa vào một Attribute đánh dấu trên một “advised method”. Nhờ đó, một Advice loại này có thể đọc thông tin từ Attribute của Method và thực hiện một số thao tác nào đó. Lớp AttributeMatchMethodPointcut của Spring.NET đã hỗ trợ chúng ta chức năng này, ví dụ sau đây sẽ tìm tất cả các methods nào được khai báo với attribute ConsoleLogging (đây là lớp Attribute người dùng tạo ra, không phải lớp built-in của Spring hay của .NET framework) và gọi thực hiện xử lý của ConsoleLoggingAdvice trước khi thực hiện xử lý logic chính.
Sau đây là code của class ConsoleLoggingAttribute là một class Attribute được sử dụng khi khai báo các method trong chương trình AOP:
public class ConsoleLoggingAttribute : Attribute
{

private ConsoleColor _color = ConsoleColor.Gray;

// Gets or sets the foreground color of the console.
public ConsoleColor Color
{

get { return _color; }
set { _color = value; }

}

public ConsoleLoggingAttribute(ConsoleColor color)
{

_color = color;

}
public ConsoleLoggingAttribute()
{
}

}

Code 16

- Ta tạo một lớp command khác tên là AnotherCommandService, method Excute của nó sẽ được đánh dấu bằng Attribute “ConsoleLogging” vừa được tạo ra ở trên:
public class AnotherServiceCommand : ICommand
{

[ConsoleLogging(Color = ConsoleColor.Green )]
public void Execute()
{

Console.Out.WriteLine("--- AnotherServiceCommand implementation : Execute()... ---");

}

}

Code 17

- Kế tiếp, ta sẽ tạo ra một lớp Advice khác, lớp Advice này sẽ đọc thông tin từ Attribute của “advised method” tương ứng và từ đó print text ra console với các màu sắc khác nhau:
public class ConsoleLoggingAdvice : IMethodInterceptor
{


private ConsoleColor _color = ConsoleColor.Gray;
public ConsoleColor Color
{

get { return _color; }
set { _color = value; }

}

public object Invoke(IMethodInvocation invocation)
{

ConsoleLoggingAttribute[] consoleLoggingInfo =
(ConsoleLoggingAttribute[])invocation.Method.GetCustomAttributes(typeof(ConsoleLoggingAttribute), false);

if (consoleLoggingInfo.Length > 0)
{

Color = consoleLoggingInfo[0].Color;

}

ConsoleColor currentColor = Console.ForegroundColor;

Console.ForegroundColor = Color;

Console.Out.WriteLine(String.Format(
"Intercepted call : a.Out to invoke method '{0}'", invocation.Method.Name));

Console.ForegroundColor = currentColor;

object returnValue = invocation.Proceed();

Console.ForegroundColor = Color;

Console.Out.WriteLine(String.Format(
"Intercepted call : after excuting method, returned '{0}'", returnValue));

Console.ForegroundColor = currentColor;

return returnValue;

}

}

Code 18

- Và đây là config xml để kết hợp tất cả các thành phần trên:
<object id="aroundAdvisor"
type="Spring.Aop.Support.AttributeMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="Spring.AopQuickStart.Aspects.ConsoleLoggingAdvice, Spring.AopQuickStart.AttributePointcut" />

</property>
<property name="Attribute"
value="Spring.AopQuickStart.Attributes.ConsoleLoggingAttribute, Spring.AopQuickStart.AttributePointcut" />

</object>


<object id="myServiceObject"
type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget" type="Spring.AopQuickStart.Commands.AnotherServiceCommand, Spring.AopQuickStart.AttributePointcut"/>

</property>

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

</list>

</property>

</object>

Code 19

- Kết quả trên màn hình khi chạy chương trình:

Intercepted call : about to invoke method 'Excute'
--- AnotherServiceCommand implementation : Execute()... ---
Intercepted call : after excuting method, returned ''

Hình 4

- Download sample code ở đây: http://nthoaiblog.googlepages.com/SampleSpringAOPAttributePointCut.zip

III. Custom pointcuts:


- Trong Spring.NET ta có thể định nghĩa những pointcut cho chính mình bằng cách overwrite method Match của superclass StaticMethodMatcherPointcut:
public class TestStaticPointcut : StaticMethodMatcherPointcut
{


public override bool Matches(MethodInfo method, Type targetType)
{

// return true if YOUR custom criteria match

}

}

Code 20

IV.

Spring.NET có nhiều chức năng rất hay mà tôi vẫn đang học. Bài viết trước và bài này tôi dịch và có thêm một số ý khác để người mới tìm hiểu có thể nắm bắt nhanh hơn. Tuy vậy vẫn có nhiều chức năng khác như: Introduction, TargetSource, … trong Spring AOP mà tôi không đề cập trong 2 bài viết này và xin để bạn tự tìm hiểu. Cuối cùng, xin cám ơn những ai đã đọc và comment.

Posted in Labels: , , |

3 comments:

  1. DragonIT Says:

    Tuyệt bài viết rất hay....và dễ hiểu

  2. fatcatgz Says:

    Cảm ơn anh, nhờ bài dịch của anh mà em dễ dàng hiểu được cách sử dụng AOP :)

  3. Supez Chicken Says:

    Cám ơn anh rất nhiều về loạt bài dịch này.

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)