欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring与AOP之AspectJ对DAO的实现

程序员文章站 2022-07-12 14:10:35
...

对于 AOP 这种编程思想,很多框架都进行了实现。 Spring 就是其中之一, 可以完成面向切面编程。 然而, AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发(在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式)。

AspectJ 简介

AspectJ 是一个面向切面的框架,它扩展了 Java 语言。 AspectJ 定义了 AOP 语法,它有一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。——百度百科《AspectJ》

AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:

(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于 try…catch 中的 finally 代码块。

AspectJ 的切入点表达式

AspectJ 除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原型是:

execution ( [modifiers-pattern]  访问权限类型
		  ret-type-pattern 返回值类型(不可省)
		  [declaring-type-pattern] 全限定性类名
		  name-pattern(param-pattern) 方法名(参数名) (不可省)
		  [throws-pattern] 抛出异常类型
		  )

注意:
切入点表达式要匹配的对象就是目标方法的方法名。所以, execution 表达式中明显就是方法的签名。表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

符 号 意义
* 0至多个任意字符
1. 用在方法参数中,表示任意多个参数。2. 用在包名后,表示当前及其子类
+ 1. 用在类名后,表示当前类及其子类。2. 用在接口后,表示当前接口及其实现类

举例:

//指定所有包下的 serivce 子包下所有类中的 doSome()方法为切入点
execution(* *..service.*.doSome())  
//指定只有一级包下的 serivce 子包下所有类中的 doSome()方法为切入点
execution(* *.service.*.doSome())  

AspectJ 基于注解的 AOP 实现

AspectJ 提供了以注解方式对于 AOP 的实现。

实现步骤

(1) 定义业务接口与实现类

//主业务接口
public interface ISomeService {
	//主业务方法
	void doFirst();
	//主业务方法
	String doSecond();
	//主业务方法
	void doThird();

}

//目标类
public class SomeServiceImpl implements ISomeService {

	//目标方法
	@Override
	public void doFirst() {
		System.out.println("执行doFirst()方法");
	}

	//目标方法
	@Override
	public String doSecond() {
		System.out.println("执行doSecond()方法");
		return "abcde";
	}

	//目标方法
	@Override
	public void doThird() {
		System.out.println("执行doThird()方法");
		System.out.println("执行doThird()方法");
	}

}

(2) 定义切面 POJO 类,并添加@Aspect 注解和通知注解

该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法;在定义的 POJO 类上添加@Aspect 注解,指定当前 POJO 类将作为切面。

@Aspect		// 表示当前类为切面
public class MyAspectJ {
	
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore() {
		System.out.println("执行前置通知方法");
	}
	
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore(JoinPoint jp) {
		System.out.println("执行前置通知方法 jp = " + jp);
	}

}

(3) 定义 Spring 配置文件

	<!-- 注册切面 -->
	<bean id="myAspectJ" class="com.huang.annotation.MyAspectJ"/>
	
	<!-- 注册目标对象 -->
	<bean id="someService" class="com.huang.annotation.SomeServiceImpl"/>
	
	<!-- 注册AspectJ的自动代理 -->
	<aop:aspectj-autoproxy/>

(4) 定义测试类

	@Test
	public void test01() {
		String resource = "com/huang/annotation/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		//serviceProxy为代理对象,而非目标对象
		ISomeService service = (ISomeService) ac.getBean("someService");  
		service.doFirst();
		System.out.println("----------------------");
		service.doSecond();
		System.out.println("----------------------");
		service.doThird();
	}

}

@Before 前置通知-增强方法有 JoinPoint 参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

	//前置通知
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore() {
		System.out.println("执行前置通知方法");
	}
	
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore(JoinPoint jp) {
		System.out.println("执行前置通知方法 jp = " + jp);
	}

运行结果

Spring与AOP之AspectJ对DAO的实现

@AfterReturning 后置通知-注解有 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。 该注解的 returning 属性就是用于指定接收方法返回值的变量名的。

	//后置通知
	@AfterReturning("execution(* *..ISomeService.doSecond(..))")
	public void myAfterReturning() {
		System.out.println("执行后置通知方法");
	}
	
	@AfterReturning(value="execution(* *..ISomeService.doSecond(..))",returning="result")
	public void myAfterReturning(Object result) {
		System.out.println("执行后置通知方法 result = " + result);
	}

运行结果

Spring与AOP之AspectJ对DAO的实现

注意:
被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值, Object 类型。 并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

	//环绕通知
	@Around("execution(* *..ISomeService.doSecond(..))")
	public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("执行环绕通知方法,目标方法执行之前");
		
		//执行目标方法
		Object result = pjp.proceed();
		System.out.println("执行环绕通知方法,目标方法执行之后");
		if (result != null) {
			result = ((String)result).toUpperCase();
		}
		return result;
	}

运行结果

Spring与AOP之AspectJ对DAO的实现

@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。 该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

在目标类定义一个异常

	//目标方法
	@Override
	public void doThird() {
		System.out.println("执行doThird()方法" + 3 / 0);
		System.out.println("执行doThird()方法");
	}
	//异常通知
	@AfterThrowing("execution(* *..ISomeService.doThird(..))")
	public void myAfetrThrowing() {
		System.out.println("执行异常通知方法");
	}
	
	//使用切入点,叫 doThirdPointcut()
	@AfterThrowing(value="doThirdPointcut()",throwing="ex")
	public void myAfterThrowing(Exception ex) {
		System.out.println("执行异常通知方法 ex = " + ex.getMessage());
	}
	
	
	// 定义了一个切入点,叫 doThirdPointcut()
	@Pointcut("execution(* *..ISomeService.doThird(..))")
	public void doThirdPointcut() {}

运行结果

Spring与AOP之AspectJ对DAO的实现

@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行。

	//最终通知
	@After("doThirdPointcut()")
	public void myAfter() {
		System.out.println("执行最终通知方法");
	}

运行结果

Spring与AOP之AspectJ对DAO的实现

@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 executeion 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcute 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

	// 定义了一个切入点,叫 doThirdPointcut()
	@Pointcut("execution(* *..ISomeService.doThird(..))")
	public void doThirdPointcut() {}