【AspectJ】Spring中基于AOP的AspectJ框架总结
注:本文仅供学习参考。
前言:对于AOP编程思想,很多框架都进行了实现。Spring也不例外,可以完成面向切面编程。AspectJ框架也实现了AOP的功能,且实现方式更为简洁,使用更加方便,而且支持注解开发,故Spring将AspectJ对于AOP的实现也入到了自己的框架中。在Spring中使用AOP开发时,通常使用AspectJ实现。
1、AspectJ简介
-
Aspect是一个优秀的面向切面的框架,它扩展了Java语言,提供了强大的切面实现。
-
官网对它的介绍:
2、AspectJ通知类型
-
以下为AspectJ中常用的通知:
1)@Before 前置通知
2)@AfterReturning 后置通知
3)@Around 环绕通知
4)@After 最终通知
5)@Pointcut 定义切入点
3、AspectJ切入点表达式
(1)表达式格式如下:
-
execution(
modifiers-pattern
ret-type-pattern
declaring-type-pattern
name-pattern(param-pattern) throws-pattern)
-
解释:
- modifiers-pattern:访问权限类型 (可省略)
- ret-type-pattern:返回值类型
- declaring-type-pattern:包名类型 (可省略)
- name-pattern(param-pattern):方法名(参数类型和参数个数)
- throws-pattern:抛出异常类型 (可省略)
-
简化格式如下:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
// 访问权限和异常类型可省略
(2)切入点表达式中的特殊字符
- 表达式中可使用的特殊符号及含义:
- “ * ”:0个或多个任意字符
- “ … ”:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径
- “ + ”:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类
(3)案例(以下内容引自动力节点Spring课程讲义)
-
execution(public * *(…)) => 指定切入点为:任意公共方法。
-
execution(* set*(…)) => 指定切入点为:任何一个以“set”开始的方法。
-
execution(* com.xyz.service.impl.*.*(…)) => 指定切入点为:定义在 service 包里的任意类的任意方法。
-
execution(* com.xyz.service….(…)) * om.xyz.service.power2.aa..(…) => 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
-
execution(* * …service. * . * (…)) a.b.service..(…) a.b.c.d.service..(…) => 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。
-
execution(* * .service. * . *(…)) => 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点。
-
execution(* * . ISomeService.*(…)) => 指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
-
execution(* * …ISomeService.*(…)) => 指定所有包下的 ISomeSerivce 接口中所有方法为切入点
-
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,Strings3)不是。
-
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)目录结构
(2)添加Maven依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
(3)引入AOP约束
- AspectJ对于AOP的实现有注解和配置文件两种方式,常用是注解方式。AspectJ的aop约束在applicationContext.xml文件(spring配置文件)中引入,详见applicationContext.xml文件。
(4)案例代码
SomeService.java
package com.Etui.s04;
public interface SomeService {
String doSome(String userName, Integer userId);
void doSome2();
}
SomeServiceImpl.java
package com.Etui.s04;
import com.Etui.s02.Student;
import org.springframework.stereotype.Service;
@Service
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String userName, Integer userId) {
System.out.println("事务方法被调用…………………………");
// int a = 1/0; // 出错时查看After通知是否执行
return "hello";
}
@Override
public void doSome2() {
System.out.println("事务方法被调用…………………………");
}
}
MyAspect.java
package com.Etui.s04;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 最终通知
@Component
@Aspect
public class MyAspect {
/** @Before
* 前置通知中的切面方法的规范
* 1)访问权限是public
* 2)没有返回值void
* 3)切面方法的名称自定义
* 4)切面方法可以没有参数,如果有也是固定的类型JoinPoint
* 5)使用@Before注解表明是前切功能
* 6)@Before的参数:
* value:指定切入点表达式
* public String doSome(String name, int age)
*/
// 前置通知
@Before(value="muCut()")
public void serviceBefore() {
System.out.println("前置通知处理……………………………………");
}
/** @AfterReturning
* 后置通知切面方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)方法自定义
* 4)切面方法可以没有参数,如果有参数则是目标方法的返回值,也可以包含参数JoinPoint,它必须是第一个参数
* 5)使用@AfterReturning注解
* 6)参数value:指定切入点表达式
* returning:指定目标方法返回值的形参名称,此名称必须与切面方法的参数名称一致.
*/
// 后置通知
@AfterReturning(value = "muCut()", returning = "obj")
public void serviceAfterReturning(Object obj){
System.out.println("后置通知处理…………………………………………");
}
/** @Around
* 环绕通知方法的规范
* 1)访问权限是public
* 2)切面方法有返回值,此返回值就是目标方法的返回值.
* 3)切面方法的名称自定义
* 4)切面方法有参数,参数就是目标方法.它是ProceedingJoinPoint的类型
* 5)必须要回避异常Throwable
* 6)使用@Around注解
* 7)参数:value:指定切入点表达式
*
*/
// 环绕通知
@Around(value = "muCut()")
public void serviceAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知中的前置事务处理…………………………………………");
pjp.proceed(pjp.getArgs());
System.out.println("环绕通知中的后置事务处理……………………………………………………");
}
/** @After
* 最终方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)方法名称自定义
* 4)方法可以没有参数,也可以有,则JoinPoint.
* 5)使用@After注解
* 6)参数:value:指定切入点表达式
*/
// 最终通知
@After(value = "muCut()")
public void serviceAfter() {
System.out.println("最终通知事务处理…………………………");
}
/** @Pointcut
* 最终方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)方法名称自定义
* 4)方法可以没有参数,也可以有,则JoinPoint.
* 5)使用@After注解
* 6)参数:value:指定切入点表达式
*/
@Pointcut(value = "execution(* com.Etui.s04.*.*(..))")
public void muCut(){
}
}
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.Etui.s04"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
MyTest.java
package com.Etui.s04;
import com.Etui.s04.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void testAround(){
ApplicationContext ac = new ClassPathXmlApplicationContext("s04/applicationContext.xml");
SomeService someService = (SomeService) ac.getBean("someServiceImpl");
someService.doSome("刻晴", 001);
}
}
5、相关注意事项
(1)@Before前置通知的JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
(2)@AfterReturning后置通知的returning属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
(3)@Around环绕通知的ProceedingJoinPoint参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。