Reading Time: 12 minutes

Spring Framework được phát triển dựa trên hai khái niệm cốt lõi: Dependency Injection và Lập trình hướng khía cạnh (Spring AOP).

Spring AOP toàn tập

Spring AOP

Chúng ta đã tìm hiểu cách thức hoạt động của Dependency Injection trong Spring. Hôm nay, chúng ta sẽ xem xét các khái niệm nền tảng của Lập trình hướng khía cạnh và cách triển khai nó bằng Spring Framework.

Tổng quan về Spring AOP

Hầu hết các ứng dụng doanh nghiệp đều có một số crosscutting concerns (mối quan tâm cắt ngang) phổ biến, được áp dụng cho nhiều đối tượng và module khác nhau. Một số ví dụ điển hình của các crosscutting concerns bao gồm ghi log, quản lý giao dịch, kiểm tra dữ liệu hợp lệ,…

Trong lập trình hướng đối tượng, tính mô-đun của ứng dụng được hiện thực thông qua các lớp. Trong khi đó, lập trình hướng khía cạnh đạt được tính mô-đun thông qua các khía cạnh, và các khía cạnh này được cấu hình để áp dụng lên nhiều lớp khác nhau.

Spring AOP giúp loại bỏ sự phụ thuộc trực tiếp của các tác vụ xuyên suốt ra khỏi các lớp, điều mà mô hình lập trình hướng đối tượng thông thường không thể thực hiện được.

Chẳng hạn, ta có thể tạo một lớp riêng để thực hiện chức năng ghi log, tuy nhiên trong lập trình hướng đối tượng, các lớp chức năng vẫn cần phải gọi trực tiếp đến các phương thức ghi log nếu muốn triển khai tính năng này xuyên suốt toàn bộ ứng dụng.

Các khái niệm cốt lõi của AOP

Trước khi đi sâu vào cách triển khai Spring AOP, chúng ta cần hiểu rõ các khái niệm cơ bản của AOP.

  1. Aspect:

Aspect là một lớp triển khai các mối quan tâm chung trong ứng dụng doanh nghiệp, những mối quan tâm này thường cắt ngang nhiều lớp, chẳng hạn như quản lý giao dịch (transaction management).

Aspect có thể là một lớp thông thường được cấu hình thông qua file cấu hình XML của Spring, hoặc bạn cũng có thể sử dụng tích hợp Spring với AspectJ để định nghĩa một lớp là Aspect bằng cách sử dụng annotation @Aspect.

  1. Join Point:

Join point là một điểm cụ thể trong ứng dụng như việc thực thi một phương thức, xử lý ngoại lệ, thay đổi giá trị biến của đối tượng… Trong Spring AOP, join point luôn đảm nhận việc thực thi một phương thức.

  1. Advice:

Advice là hành động được thực thi tại một join point cụ thể. Xét về mặt lập trình, chúng là các phương thức được thực thi khi ứng dụng đi đến một join point khớp với pointcut đã định nghĩa**.** Có thể hình dung Advice tương tự như interceptor trong Struts2 hoặc filter trong Servlet.

  1. Pointcut:

Pointcut là các biểu thức được so khớp với các join point nhằm xác định xem advice có cần được thực thi hay không. Pointcut sử dụng nhiều loại biểu thức khác nhau để so khớp với join point và Spring framework sử dụng ngôn ngữ biểu thức pointcut của AspectJ.

  1. Target Object (Đối tượng đích):

Đây là đối tượng mà các advice sẽ được áp dụng lên. Spring AOP được triển khai thông qua các proxy tại thời điểm chạy, vì vậy đối tượng này luôn là một đối tượng proxy. Điều này có nghĩa là một lớp con sẽ được tạo tại thời điểm chạy, trong đó phương thức đích được ghi đè và các advice được chèn vào dựa trên cấu hình.

  1. AOP proxy:

Triển khai Spring AOP sử dụng proxy động của JDK để tạo các lớp proxy kết hợp giữa lớp đích và các lời gọi advice. Các lớp này được gọi là AOP proxy. Ngoài ra, cũng có thể sử dụng proxy CGLIB bằng cách thêm nó làm dependency trong dự án Spring AOP.

  1. Weaving:

Weaving là quá trình kết hợp các aspect với các đối tượng khác để tạo ra các đối tượng proxy đã được áp dụng advice. Quá trình này có thể diễn ra tại thời điểm biên dịch, nạp lớp hoặc chạy. Spring AOP thực hiện quá trình weaving tại thời điểm chạy.

Các loại Advice trong AOP

Dựa trên chiến lược thực thi, các advice được phân loại như sau:

  1. Before Advice:

Advice này được thực thi trước khi phương thức tại join point được gọi. Có thể sử dụng annotation @Before để đánh dấu một phương thức là Before advice.

  1. After (finally) Advice:

Advice này được thực thi sau khi phương thức tại join point kết thúc, bất kể là kết thúc bình thường hay ném ra exception. Sử dụng annotation @After để định nghĩa loại advice này.

  1. After Returning Advice:

Trong một số trường hợp, chúng ta chỉ muốn advice được gọi khi phương thức tại join point kết thúc thành công. Khi đó, sử dụng annotation @AfterReturning để đánh dấu phương thức là after returning advice.

  1. After Throwing Advice:

Advice này chỉ được thực thi khi phương thức tại join point ném ra exception. Nó thường được dùng để rollback giao dịch theo cách khai báo. Annotation @AfterThrowing được sử dụng để định nghĩa advice này.

  1. Around Advice:

Đây là loại advice quan trọng và mạnh mẽ nhất. Advice này bao quanh phương thức tại join point và cho phép chúng ta quyết định có thực thi phương thức đó hay không. Chúng ta có thể viết mã advice được thực thi trước và sau khi phương thức join point được gọi. Việc gọi thực thi phương thức join point cũng như trả về giá trị (nếu phương thức có trả về) là trách nhiệm của advice kiểu around. Để tạo advice dạng này, chúng ta sử dụng annotation @Around.

Nếu chỉ đọc lý thuyết, bạn có thể nhầm lẫn các khái niệm trên nhưng đừng lo, đến khi triển khai Spring AOP thực tế, mọi thứ sẽ rõ ràng hơn.

Bây giờ, hãy bắt đầu xây dựng một dự án Spring đơn giản có tích hợp AOP. Spring hỗ trợ sử dụng các annotation của AspectJ để tạo aspect và chúng ta sẽ sử dụng cách này cho đơn giản. Tất cả các annotation AOP ở trên đều được định nghĩa trong package org.aspectj.lang.annotation.

Spring Tool Suite (STS) cung cấp thông tin hữu ích về aspect, vì vậy bạn nên sử dụng công cụ này. Nếu chưa quen với STS, bạn có thể xem qua bài viết Spring MVC Tutorial để biết cách sử dụng.

Ví dụ Spring AOP

Tạo một dự án Spring Maven đơn giản để các thư viện Spring Core được tự động thêm tập tin pom.xml, do đó chúng ta không cần khai báo thủ công. Cấu trúc dự án cuối cùng sẽ giống như hình minh họa bên dưới, trong đó chúng ta sẽ tìm hiểu chi tiết về các thành phần cốt lõi của Spring và các triển khai Aspect.

Spring-aop

Khai báo dependency cho AspectJ trong Spring AOP

Mặc dù Spring đã hỗ trợ AOP mặc định, nhưng vì chúng ta sử dụng annotation của AspectJ để cấu hình aspect và advice, nên cần khai báo các dependency tương ứng trong pom.xml.

<project xmlns="<https://maven.apache.org/POM/4.0.0>" xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>"
	xsi:schemaLocation="<https://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.springframework.samples</groupId>
	<artifactId>SpringAOPExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<java.version>1.6</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>
		<junit.version>4.11</junit.version>
		<aspectj.version>1.7.4</aspectj.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${aspectj.version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjtools</artifactId>
			<version>${aspectj.version}</version>
		</dependency>
	</dependencies>
</project>

Lưu ý, các dependency aspectjrtaspectjtools (phiên bản 1.7.4) đã được thêm vào dự án. Ngoài ra, Spring framework cũng đã được cập nhật phiên bản mới nhất tại thời điểm hiện tại, cụ thể là 4.0.2.RELEASE.

Lớp Model

Hãy tạo một Java bean đơn giản mà chúng ta sẽ sử dụng cho ví dụ này với một số phương thức bổ sung. Mã nguồn Employee.java như sau:

package com.journaldev.spring.model;

import com.journaldev.spring.aspect.Loggable;

public class Employee {

	private String name;

	public String getName() {
		return name;
	}

	@Loggable
	public void setName(String nm) {
		this.name = nm;
	}

	public void throwException(){
		throw new RuntimeException("Dummy Exception");
	}
}

Bạn có để ý thấy phương thức setName() được chú thích bằng annotation @Loggable. Đây là một Java annotation do chúng ta tự định nghĩa trong dự án. Chúng ta sẽ tìm hiểu cách sử dụng annotation này sau.

Lớp Service

Hãy tạo một lớp dịch vụ để làm việc với bean Employee. Mã nguồn EmployeeService.java như sau:

package com.journaldev.spring.service;

import com.journaldev.spring.model.Employee;

public class EmployeeService {

	private Employee employee;

	public Employee getEmployee(){
		return this.employee;
	}

	public void setEmployee(Employee e){
		this.employee = e;
	}
}

Chúng ta có thể sử dụng annotation của Spring để cấu hình lớp này như một Spring Component, nhưng trong dự án này chúng ta sẽ sử dụng cấu hình dựa trên XML. Lớp EmployeeService là một lớp tiêu chuẩn, cung cấp điểm truy cập đến các bean Employee.

Cấu hình Bean Spring với AOP

Nếu bạn đang sử dụng STS, bạn có thể tạo “Spring Bean Configuration File” và chọn không gian tên (namespace) cho AOP. Nếu bạn sử dụng IDE khác, bạn chỉ cần thêm không gian tên đó vào tập tin cấu hình bean. Tập tin cấu hình bean của tôi có nội dung như sau (spring.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="<https://www.springframework.org/schema/beans>"
	xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
	xmlns:aop="<https://www.springframework.org/schema/aop>"
	xsi:schemaLocation="<https://www.springframework.org/schema/beans> <https://www.springframework.org/schema/beans/spring-beans-4.0.xsd>
		<https://www.springframework.org/schema/aop> <https://www.springframework.org/schema/aop/spring-aop-4.0.xsd>">

	<!-- Kích hoạt kiểu AOP của Spring theo phong cách AspectJ -->
	<aop:aspectj-autoproxy />

	<!-- Cấu hình bean Employee và khởi tạo -->
	<bean name="employee" class="com.journaldev.spring.model.Employee">
		<property name="name" value="Dummy Name"></property>
	</bean>

	<!-- Cấu hình bean EmployeeService -->
	<bean name="employeeService" class="com.journaldev.spring.service.EmployeeService">
		<property name="employee" ref="employee"></property>
	</bean>

	<!-- Cấu hình các bean Aspect, nếu không thì advice của Aspect sẽ không được thực thi -->
	<bean name="employeeAspect" class="com.journaldev.spring.aspect.EmployeeAspect" />
	<bean name="employeeAspectPointcut" class="com.journaldev.spring.aspect.EmployeeAspectPointcut" />
	<bean name="employeeAspectJoinPoint" class="com.journaldev.spring.aspect.EmployeeAspectJoinPoint" />
	<bean name="employeeAfterAspect" class="com.journaldev.spring.aspect.EmployeeAfterAspect" />
	<bean name="employeeAroundAspect" class="com.journaldev.spring.aspect.EmployeeAroundAspect" />
	<bean name="employeeAnnotationAspect" class="com.journaldev.spring.aspect.EmployeeAnnotationAspect" />

</beans>

Để sử dụng Spring AOP trong các Spring bean, chúng ta cần thực hiện các bước sau:

  • Khai báo không gian tên AOP như xmlns:aop="<https://www.springframework.org/schema/aop>"
  • Thêm phần tử <aop:aspectj-autoproxy /> để kích hoạt hỗ trợ AspectJ của Spring với khả năng proxy tự động trong thời gian chạy
  • Cấu hình các lớp Aspect như những bean thông thường trong Spring

Bạn có thể thấy tôi đã định nghĩa nhiều aspect trong tập tin cấu hình bean, và bây giờ là lúc để tìm hiểu từng cái một.

Ví dụ về Before Aspect trong Spring AOP

Mã nguồn EmployeeAspect.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class EmployeeAspect {

	@Before("execution(public String getName())")
	public void getNameAdvice(){
		System.out.println("Executing Advice on getName()");
	}

	@Before("execution(* com.journaldev.spring.service.*.get*())")
	public void getAllAdvice(){
		System.out.println("Service method getter called");
	}
}

Các điểm quan trọng trong lớp aspect ở trên:

  • Các lớp Aspect bắt buộc phải có annotation @Aspect.
  • Annotation @Before được sử dụng để tạo advice loại Before.
  • Chuỗi truyền vào @Before là biểu thức Pointcut.
  • Advice getNameAdvice() sẽ được thực thi đối với bất kỳ phương thức nào của Spring bean có chữ ký public String getName(). Đây là điểm rất quan trọng: nếu bạn tạo bean Employee bằng toán tử new thì advice sẽ không được áp dụng. Advice chỉ có hiệu lực khi bean được lấy từ ApplicationContext.
  • Có thể sử dụng dấu hoa thị (*) làm ký tự đại diện trong biểu thức Pointcut. Phương thức getAllAdvice() sẽ được áp dụng cho tất cả các lớp trong package com.journaldev.spring.service có phương thức bắt đầu bằng get và không nhận tham số.

Chúng ta sẽ quan sát các advice hoạt động thực tế trong một lớp kiểm thử sau khi tìm hiểu tất cả các loại advice khác.

Phương thức Pointcut và Tái sử dụng trong Spring AOP

Đôi khi cần dùng lại biểu thức Pointcut ở nhiều nơi, chúng ta có thể tạo một phương thức rỗng với annotation @Pointcut và sử dụng tên phương thức này trong các advice. Mã nguồn EmployeeAspectPointcut.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class EmployeeAspectPointcut {

	@Before("getNamePointcut()")
	public void loggingAdvice(){
		System.out.println("Executing loggingAdvice on getName()");
	}

	@Before("getNamePointcut()")
	public void secondAdvice(){
		System.out.println("Executing secondAdvice on getName()");
	}

	@Pointcut("execution(public String getName())")
	public void getNamePointcut(){}

	@Before("allMethodsPointcut()")
	public void allServiceMethodsAdvice(){
		System.out.println("Before executing service method");
	}

	// Pointcut áp dụng cho tất cả các phương thức của các lớp trong một package
	@Pointcut("within(com.journaldev.spring.service.*)")
	public void allMethodsPointcut(){}
}

Ví dụ trên rất rõ ràng, thay vì sử dụng biểu thức, chúng ta sử dụng tên phương thức trong đối số của annotation advice.

JoinPoint và đối số Advice trong Spring AOP

Chúng ta có thể sử dụng JoinPoint như một tham số trong các phương thức advice để lấy thông tin chữ ký phương thức hoặc đối tượng đích. Có thể dùng biểu thức args() trong Pointcut để áp dụng cho bất kỳ phương thức nào khớp với kiểu đối số. Nếu sử dụng cách này, tên biến trong phương thức advice phải trùng với tên định danh trong biểu thức Pointcut để xác định kiểu tham số. Chúng ta cũng có thể sử dụng kiểu Object chung cho đối số. Mã nguồn EmployeeAspectJoinPoint.java:

package com.journaldev.spring.aspect;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class EmployeeAspectJoinPoint {

	@Before("execution(public void com.journaldev.spring.model..set*(*))")
	public void loggingAdvice(JoinPoint joinPoint){
		System.out.println("Before running loggingAdvice on method=" + joinPoint.toString());
		System.out.println("Agruments Passed=" + Arrays.toString(joinPoint.getArgs()));
	}

	// Advice có tham số, áp dụng cho các phương thức nhận một tham số kiểu String
	@Before("args(name)")
	public void logStringArguments(String name){
		System.out.println("String argument passed=" + name);
	}
}

Ví dụ về After Advice trong Spring AOP

Hãy xem một lớp aspect đơn giản sử dụng các loại advice: After, After Throwing và After Returning. Mã nguồn EmployeeAfterAspect.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class EmployeeAfterAspect {

	@After("args(name)")
	public void logStringArguments(String name){
		System.out.println("Running After Advice. String argument passed=" + name);
	}

	@AfterThrowing("within(com.journaldev.spring.model.Employee)")
	public void logExceptions(JoinPoint joinPoint){
		System.out.println("Exception thrown in Employee Method=" + joinPoint.toString());
	}

	@AfterReturning(pointcut = "execution(* getName())", returning = "returnString")
	public void getNameReturningAdvice(String returnString){
		System.out.println("getNameReturningAdvice executed. Returned String=" + returnString);
	}
}

Chúng ta có thể sử dụng từ khóa within trong biểu thức Pointcut để áp dụng advice cho tất cả phương thức trong một lớp.

Advice @AfterReturning cho phép truy cập vào giá trị được trả về từ phương thức được áp dụng advice.

Trong bean Employee, phương thức throwException() được sử dụng để minh họa cách hoạt động của After Throwing Advice.

Ví dụ về Around Aspect trong Spring AOP

Như đã giải thích trước đó, chúng ta có thể sử dụng advice Around để bao quanh quá trình thực thi của một phương thức, bao gồm cả trước và sau khi phương thức được gọi. Advice này cho phép kiểm soát việc phương thức có được thực thi hay không, cũng như can thiệp vào giá trị trả về. Đây là loại advice mạnh mẽ nhất và cần được sử dụng đúng cách. Mã nguồn EmployeeAroundAspect.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class EmployeeAroundAspect {

	@Around("execution(* com.journaldev.spring.model.Employee.getName())")
	public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
		System.out.println("Before invoking getName() method");
		Object value = null;
		try {
			value = proceedingJoinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("After invoking getName() method. Return value=" + value);
		return value;
	}
}

Advice kiểu Around luôn yêu cầu đối số là ProceedingJoinPoint, và bắt buộc phải sử dụng phương thức proceed() để gọi phương thức đích đã được áp dụng advice.

Nếu phương thức đích có trả về dữ liệu, thì nhiệm vụ của advice là phải trả lại kết quả đó cho chương trình gọi. Với các phương thức void, advice có thể trả về null.

Do advice này bao quanh hoàn toàn quá trình thực thi nên chúng ta có thể kiểm soát cả đầu vào, đầu ra cũng như hành vi thực thi của phương thức mục tiêu.

Advice trong Spring với Pointcut sử dụng Annotation tùy chỉnh

Nếu bạn quan sát tất cả các biểu thức pointcut trong các advice ở trên, sẽ thấy có khả năng chúng được áp dụng cho một số bean khác dù bạn không có ý định ngay từ đầu. Ví dụ, nếu ai đó định nghĩa một Spring bean mới có phương thức getName(), thì các advice hiện tại sẽ bắt đầu áp dụng cho bean đó, mặc dù điều này không nằm trong chủ đích ban đầu.

Vì vậy cho nên chúng ta nên giới hạn phạm vi của biểu thức Pointcut càng hẹp càng tốt.

Một cách tiếp cận thay thế là định nghĩa một annotation tùy chỉnh và gắn annotation đó vào các phương thức mà ta muốn áp dụng advice.

Đây chính là lý do tại sao phương thức setName() trong lớp Employee được gắn annotation @Loggable.

Annotation @Transactional của Spring Framework là một ví dụ điển hình của cách tiếp cận này trong việc quản lý giao dịch.

Mã nguồn Loggable.java:

package com.journaldev.spring.aspect;

public @interface Loggable {

}

Mã nguồn EmployeeAnnotationAspect.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class EmployeeAnnotationAspect {

	@Before("@annotation(com.journaldev.spring.aspect.Loggable)")
	public void myAdvice(){
		System.out.println("Executing myAdvice!!");
	}
}

Phương thức myAdvice() chỉ áp dụng cho setName().

Đây là một cách tiếp cận rất an toàn: bất cứ khi nào cần áp dụng advice cho một phương thức cụ thể, chúng ta chỉ cần gắn annotation @Loggable.

Cấu hình Spring AOP bằng XML

Mặc dù tôi luôn ưu tiên sử dụng annotation nhưng chúng ta vẫn có thể dùng cách cấu hình các aspect trong file cấu hình của Spring. Giả sử chúng ta có một lớp như sau. Mã nguồn EmployeeXMLConfigAspect.java:

package com.journaldev.spring.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class EmployeeXMLConfigAspect {

	public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
		System.out.println("EmployeeXMLConfigAspect:: Before invoking getName() method");
		Object value = null;
		try {
			value = proceedingJoinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("EmployeeXMLConfigAspect:: After invoking getName() method. Return value=" + value);
		return value;
	}
}

Chúng ta có thể cấu hình lớp trên bằng cách thêm đoạn cấu hình sau vào tập tin bean cấu hình của Spring:

<bean name="employeeXMLConfigAspect" class="com.journaldev.spring.aspect.EmployeeXMLConfigAspect" />

<!-- Cấu hình AOP bằng XML -->
<aop:config>
	<aop:aspect ref="employeeXMLConfigAspect" id="employeeXMLConfigAspectID" order="1">
		<aop:pointcut expression="execution(* com.journaldev.spring.model.Employee.getName())" id="getNamePointcut"/>
		<aop:around method="employeeAroundAdvice" pointcut-ref="getNamePointcut" arg-names="proceedingJoinPoint"/>
	</aop:aspect>
</aop:config>

Các phần tử cấu hình AOP trong XML khá rõ ràng về mục đích sử dụng, nên không cần trình bày chi tiết thêm.

Ví dụ về Spring AOP

Hãy tạo một chương trình Spring đơn giản để xem các aspect hoạt động như thế nào khi được áp dụng lên các phương thức trong bean. Mã nguồn SpringMain.java:

package com.journaldev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.service.EmployeeService;

public class SpringMain {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
		EmployeeService employeeService = ctx.getBean("employeeService", EmployeeService.class);

		System.out.println(employeeService.getEmployee().getName());

		employeeService.getEmployee().setName("Pankaj");

		employeeService.getEmployee().throwException();

		ctx.close();
	}
}

Khi thực thi chương trình trên, chúng ta sẽ nhận được kết quả đầu ra như sau:

Mar 20, 2014 8:50:09 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Thu Mar 20 20:50:09 PDT 2014]; root of context hierarchy
Mar 20, 2014 8:50:09 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Service method getter called
Before executing service method
EmployeeXMLConfigAspect:: Before invoking getName() method
Executing Advice on getName()
Executing loggingAdvice on getName()
Executing secondAdvice on getName()
Before invoking getName() method
After invoking getName() method. Return value=Dummy Name
getNameReturningAdvice executed. Returned String=Dummy Name
EmployeeXMLConfigAspect:: After invoking getName() method. Return value=Dummy Name
Dummy Name
Service method getter called
Before executing service method
String argument passed=Pankaj
Before running loggingAdvice on method=execution(void com.journaldev.spring.model.Employee.setName(String))
Agruments Passed=[Pankaj]
Executing myAdvice!!
Running After Advice. String argument passed=Pankaj
Service method getter called
Before executing service method
Exception thrown in Employee Method=execution(void com.journaldev.spring.model.Employee.throwException())
Exception in thread "main" java.lang.RuntimeException: Dummy Exception
	at com.journaldev.spring.model.Employee.throwException(Employee.java:19)
	at com.journaldev.spring.model.Employee$$FastClassBySpringCGLIB$$da2dc051.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
	at com.journaldev.spring.model.Employee$$EnhancerBySpringCGLIB$$3f881964.throwException(<generated>)
	at com.journaldev.spring.main.SpringMain.main(SpringMain.java:17)

Như bạn thấy, các advice được thực thi lần lượt theo cấu hình Pointcut đã định nghĩa.

Bạn nên cấu hình từng advice một cách riêng biệt để tránh nhầm lẫn.

Đó là toàn bộ ví dụ hướng dẫn về Spring AOP. Hy vọng bạn đã nắm được những kiến thức cơ bản về AOP trong Spring và có thể học thêm thông qua các ví dụ. Bạn có thể tải toàn bộ dự án mẫu từ liên kết bên dưới và tự thực hành ngay!

Download Spring AOP Project.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất