Spring与AOP系列五:AspectJ对AOP的实现

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

1.AspectJ简介

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

2.AspectJ的通知类型

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

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

3.AspectJ的切入点表达式

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

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

举例:


execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set *(..))
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在service包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* *.service.*.doSome())
指定只有一级包下的serivce子包下所有类中的doSome()方法为切入点
execution(* *..service.*.doSome())
指定所有包下的serivce子包下所有类中的doSome()方法为切入点
execution(* com.xyz.service.IAccountService.*(..))
指定切入点为: IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..))
指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
指定切入点为:所有的joke(String,int)方法,且joke()方法的第一个参数是String,第二个参数是int。如果方法中的参数类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke( java.util.List, int)。
execution(* joke(String,*)))
指定切入点为:所有的joke()方法,该方法第一个参数为String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。
execution(* joke(String,..)))
指定切入点为:所有的joke()方法,该方法第 一个参数为String,后面可以有任意个参数且参数类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都是。
execution(* joke(Object))
指定切入点为:所有的joke()方法,方法拥有一个参数,且参数是Object类型。 joke(Object ob)是,但,joke(String s)与joke(User u)均不是。
execution(* joke(Object+)))
指定切入点为:所有的joke()方法,方法拥有一个参数,且参数是Object类型或该类的子类。不仅joke(Object ob)是,joke(String s)和joke(User u)也是。

4.AspectJ基于注解的AOP实现

(1)实现步骤

Step1:定义业务接口与实现类

package com.lmm.annotation;

// 主业务接口
public interface ISomeService {
	// 目标方法    
	void doFirst();
	// 目标方法
	String doSecond();
	// 目标方法
	void doThird();
}
package com.lmm.annotation;

// 目标类
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()方法" + 3 / 0);
		System.out.println("执行doThird()方法");
	}
}

Step2:定义切面POJO类

package com.lmm.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect    // 表示当前类为切面
public class MyAspect {
	
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore() {
		System.out.println("执行前置通知方法");
	}
	
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore(JoinPoint jp) {
		System.out.println("执行前置通知方法 jp = " + jp);
	}
	
	@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);
	}
	
	@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;
	}
	
	@AfterThrowing("execution(* *..ISomeService.doThird(..))")
	public void myAfterThrowing() {
		System.out.println("执行异常通知方法");
	}
	
	@AfterThrowing(value="doThirdPointcut()", throwing="ex")
	public void myAfterThrowing(Exception ex) {
		System.out.println("执行异常通知方法 ex = " + ex.getMessage());
	}
	
	@After("doThirdPointcut()")
	public void myAfter() {
		System.out.println("执行最终通知方法");
	}
	
	// 定义了一个切入点,叫 doThirdPointcut()
	@Pointcut("execution(* *..ISomeService.doThird(..))")
	public void doThirdPointcut(){}
}

Step3:在切面类上添加@Aspect注解(如上代码)

Step4:在POJO类的普通方法上添加通知注解(如上代码@Before...等)

Step5:注册目标对象与POJO切面类

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

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

</beans>

Step6:注册AspectJ的自动代理

Step7:测试类中使用目标对象的id(如上代码)

package com.lmm.annotation;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

	@Test
	public void test01() {
		// 创建容器对象,加载Spring配置文件
		String resource = "com/lmm/annotation/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);

		ISomeService service = (ISomeService) ac.getBean("someService");
		service.doFirst();
		System.out.println("----------------------------");
		service.doSecond();
		System.out.println("----------------------------");
		service.doThird();
	}

}

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

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

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

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object类型,因为目标方法的返回值可能是任何类型。

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

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

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

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

(6)@After最终通知

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

(7)@Pointcut定义切入点

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

5.AspectJ基于XML的AOP实现

基本同上:

将MyAspect注解全部换为配置文件

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

	<!-- 注册切面 -->
	<bean id="myAspect" class="com.lmm.xml.MyAspect"/>
	
	<!-- 注册目标对象 -->
	<bean id="someService" class="com.lmm.xml.SomeServiceImpl"/>
	
	<!-- AOP配置 -->
	<aop:config>
		<aop:pointcut expression="execution(* *..ISomeService.doFirst(..))" id="doFirstPointcut"/>
		<aop:pointcut expression="execution(* *..ISomeService.doSecond(..))" id="doSecondPointcut"/>
		<aop:pointcut expression="execution(* *..ISomeService.doThird(..))" id="doThirdPointcut"/>
		
		<aop:aspect ref="myAspect">
			<aop:before method="myBefore" pointcut-ref="doFirstPointcut"/>
			<aop:before method="myBefore(org.aspectj.lang.JoinPoint)" pointcut-ref="doFirstPointcut"/>
			
			<aop:after-returning method="myAfterReturning" pointcut-ref="doSecondPointcut"/>
			<aop:after-returning method="myAfterReturning(java.lang.Object)" pointcut-ref="doSecondPointcut" returning="result"/>
			
			<aop:around method="myAround" pointcut-ref="doSecondPointcut"/>
			
			<aop:after-throwing method="myAfterThrowing" pointcut-ref="doThirdPointcut"/>
			<aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doThirdPointcut" throwing="ex"/>
			
			<aop:after method="myAfter" pointcut-ref="doThirdPointcut"/>
		</aop:aspect>
	</aop:config>

</beans>

参考:北京动力节点视频课

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值