对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
1.AspectJ简介
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。(百度百科)
2.AspectJ的通知类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于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>
参考:北京动力节点视频课