六、aspectJ框架实现AOP(注解)
1、切面执行时机(Advice)
- @Before:前置增强
- @AfterReturning:后置增强
- @Around:环绕增强
- @AfterThrowing:异常增强
- @After:最终增强
追加一个额外注解:@Pointcut:定义切入点
2、切面执行位置(Pointcut,使用切入点表达式)
aspectJ定义了专门的表达式用于指定切入点,表达式的原型:
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:抛出异常类型
- ?:表示可选的部分
以上表达式共4个部分:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以, execution表达式中明显就是方法的签名。
**注意:**表达式中各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 任意个数 |
.. | 用在方法参数中,表示任意多个参数 用在包名后,表示当前包及其子包路径 |
+ | 用在类名后,表示当前类及其子类 用在接口后,表示当前接口及其实现类 |
栗子:
// 指定切入点为:任意公共方法
execution(public * *(..))
// 指定切入点为:任意一个以“set”开始的方法
execution(* set*(..))
// 指定切入点为:home.wang.service包下任意类的任意方法
execution(* home.wang.service.*.*(..))
// 指定切入点为:home.wang.service包及其子包下任意类的任意方法
execution(* home.wang.service..*.*(..))
// 指定切入点为:所有包下的service包下任意类的任意方法
execution(* *..service.*.*(..))
3、步骤
- 新建maven项目
- 加入依赖
- spring依赖
- aspectJ依赖
- junit单元测试
- 创建目标类:接口+实现类(给实现类方法的功能增强)
- 创建切面类:普通类
- 类上加注解:@Aspect
- 在类中定义方法(切面要执行的增强代码)
- 在方法上要加aspectJ中的增强注解:@Before…
- 还有要指定切入点表达式:execution(…)
- 创建Spring配置文件:声明对象(xml/注解),把对象交给容器统一管理
- 声明目标对象
- 声明切面类对象
- 声明aspectJ框架中的自动代理生成器标签
- 自动代理生成器:用来完成代理对象的自动创建功能
- 创建测试类,从Spring容器中获取目标对象(代理对象)
- 通过代理对象执行方法,实现AOP的功能增强
4、项目结构
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL5YzYwEDMklDNwITO4kTMhNGNiRDZwkDMwEmNlF2NykzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
pom.xml
<!--追加aspectJ依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.20</version>
</dependency>
service.impl.SomeServiceImpl.java
package hom.wang.service.impl;
import hom.wang.service.SomeService;
/*目标类*/
public class SomeServiceImpl implements SomeService {
@Override
public String doSomeSimple(String str, Integer num) {
System.out.println("===目标方法doSomeSimple(str=" + str + ", num=" + num + ")===");
return str + num;
}
@Override
public String doSomeAround(String str, Integer num) {
System.out.println("===目标方法doSomeAround(str=" + str + ", num=" + num + ")===");
return str + num;
}
@Override
public Integer doSomeAfterThrowing(Integer num0, Integer num1) {
System.out.println("===目标方法doSomeAfterThrowing(num0="
+ num0 + ", num1=" + num1 + ")===");
return num0 / num1;
}
}
service.impl.CglibServiceImpl.java
package hom.wang.service.impl;
public class CglibServiceImpl {
public void doSome(){
System.out.println("------CglibServiceImpl.doSome()------");
}
}
MyAspect.java
package hom.wang;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect:aspectJ框架中的注解
* 表示:当前类是切面类(用来给业务代码增强功能的类,这里写有切面的功能代码)
* 位置:类上
*/
@Aspect
public class MyAspect {
/**
* @Before:前置增强
* 属性:
* value:切入点表达式
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
*/
@Before(value = "execution(public String hom.wang.service.impl.SomeServiceImpl.doSomeSimple(String,Integer))")
public void myBefore(){
// 书写切面要执行的功能增强代码
System.out.println("切面前置增强:输出执行开始时间:" + new Date());
}
/**
* 指定参数中的给定参数:JoinPoint(业务方法)
* 作用:可在增强里获得业务方法的信息
* 使用须知:JoinPoint是由框架赋予的,必须放在参数列表第一位
*/
@Before(value = "execution(String do*Simple(..))")
public void myBefore_02(JoinPoint point){
// 书写切面要执行的功能增强代码
// 获取业务方法完整定义
System.out.println("业务方法定义:" + point.getSignature());
System.out.println("业务方法名称:" + point.getSignature().getName());
System.out.println("业务方法参数列表:");
Arrays.stream(point.getArgs()).forEach(System.out::println);
System.out.println("切面前置增强02:输出执行开始时间:" + new Date());
}
/**
* @AfterReturning:后置增强
* 属性:
* 1、value:切入点表达式
* 2、returning:自定义变量表示方法返回值(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、有参数(推荐Object)
*/
@AfterReturning(value = "execution(* do*Simple(..))", returning = "res555")
public void myAfterReturning(Object res555){
// 书写切面要执行的功能增强代码
System.out.println("切面后置增强:业务方法执行后返回值为:" + res555);
}
/**
* @Around:环绕增强
* 属性:
* 1、value:切入点表达式
* 要求:
* 1、公共方法 public
* 2、方法必须有返回值(推荐Object)
* 3、方法名称自定义
* 4、有固定参数(ProceedingJoinPoint extends JoinPoint)
*
* 环绕增强等同于JDK动态代理的InvocationHandler接口
* 参数ProceedingJoinPoint等同于Method
*/
@Around(value = "execution(* do*Around(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 定义返回值
Object result = null;
// 1、环绕增强之前置增强
System.out.println("环绕增强之前置增强:业务方法执行前记录的时间:" + new Date());
System.out.println("环绕增强之前置增强:业务方法入参:[");
Arrays.stream(pjp.getArgs()).forEach(System.out::println);
System.out.println("]");
// 2、目标方法调用
result = pjp.proceed(); // 相当于method。invoke()
// 3、环绕增强之后置增强
System.out.println("环绕增强之后置增强:业务方法执行结束提交事务");
// 返回执行结果
return result;
}
/**
* @AfterThrowing:异常增强
* 属性:
* 1、value:切入点表达式
* 2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、有一个参数是Exception,如果还有就是JoinPoint
* 特点:
* 1、在目标方法抛出异常时执行
* 2、可作异常监控
*/
@AfterThrowing(value = "execution(* do*AfterThrowing(..))", throwing = "exception666")
public void myAfterThrowing(Exception exception666){
// 书写切面要执行的功能增强代码
System.out.println("切面异常增强:业务方法运行时抛出异常:" + exception666.getMessage());
}
/**
* @After:最终增强
* 属性:
* 1、value:切入点表达式
* 2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
* 特点:
* 1、总是执行
* 2、可作异常监控
*/
@After(value = "execution(* do*After*(..))")
public void myAfter(){
// 书写切面要执行的功能增强代码
System.out.println("切面最终增强:总是会被执行");
// 一般做资源清除工作的
}
/**
* @Pointcut:定义和管理切入点(复用:多处使用同一个切入点时定义一个公用的切入点)
* 属性:
* 1、value:切入点表达式
* 特点:
* 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
* 其它的通知中, value属性就可以使用这个方法名称,代替切入点表达式
*/
@Pointcut(value = "execution(* do*(..))")
private void myPointcutComm(){
//无需代码
}
// 使用@Pointcut定义的切入点
@After(value = "myPointcutComm()")
public void myAfterCommon(){
// 书写切面要执行的功能增强代码
System.out.println("***通用的切面最终增强:总是会被执行***");
}
}
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: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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--对象交给Spring容器,由Spring容器统一创建、管理-->
<!--目标对象-->
<bean id="someService" class="hom.wang.service.impl.SomeServiceImpl" />
<bean id="cglibService" class="hom.wang.service.impl.CglibServiceImpl" />
<!--切面对象-->
<bean id="myAspect" class="hom.wang.MyAspect" />
<!--声明自动代理生成器:使用aspectJ框架内部功能创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象在内存中的结构来创建代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy:会把Spring容器中所有目标对象,一次性都生成代理对象
默认没有接口使用cglib,有接口使用jdk
-->
<aop:aspectj-autoproxy />
<!--
如果期望目标类有接口还依然使用cglib动态代理
proxy-target-class="true":告诉框架死活给我用cglib代理
-->
<!--<aop:aspectj-autoproxy proxy-target-class="true" />-->
</beans>
MyTest.java
package hom.wang;
import hom.wang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("==============没有接口时是CGLIB动态代理=====================");
CglibServiceImpl cglib =
(CglibServiceImpl) applicationContext.getBean("cglibService");
System.out.println("没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + cglib.getClass().getName());
SomeService proxy = (SomeService) applicationContext.getBean("someService");
System.out.println("存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + proxy.getClass().getName());
System.out.println("\n====================前置+后置===========================");
System.out.println("proxy.doSomeSimple()最终执行结果==" + proxy.doSomeSimple("哦吼吼", 20));
System.out.println("\n=====================环---绕===========================");
System.out.println("proxy.doSomeAround()最终执行结果==" + proxy.doSomeAround("咚恰恰", 666));
System.out.println("\n=====================异常+最终===========================");
System.out.println("proxy.doSomeAfterThrowing()最终执行结果==" + proxy.doSomeAfterThrowing(12, 0));
}
}
cout_control(控制台输出)
D:\Development\Environment\Java\jdk11\bin\java.exe...
==============没有接口时是CGLIB动态代理=====================
没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==hom.wang.service.impl.CglibServiceImpl$$EnhancerBySpringCGLIB$$6ae78cf8
存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==com.sun.proxy.$Proxy13
====================前置+后置===========================
切面前置增强:输出执行开始时间:Sun May 29 18:26:01 CST 2022
业务方法定义:String hom.wang.service.SomeService.doSomeSimple(String,Integer)
业务方法名称:doSomeSimple
业务方法参数列表:
哦吼吼
20
切面前置增强02:输出执行开始时间:Sun May 29 18:26:01 CST 2022
===目标方法doSomeSimple(str=哦吼吼, num=20)===
切面后置增强:业务方法执行后返回值为:哦吼吼20
***通用的切面最终增强:总是会被执行***
proxy.doSomeSimple()最终执行结果==哦吼吼20
=====================环---绕===========================
环绕增强之前置增强:业务方法执行前记录的时间:Sun May 29 18:26:01 CST 2022
环绕增强之前置增强:业务方法入参:[
咚恰恰
666
]
===目标方法doSomeAround(str=咚恰恰, num=666)===
***通用的切面最终增强:总是会被执行***
环绕增强之后置增强:业务方法执行结束提交事务
proxy.doSomeAround()最终执行结果==咚恰恰666
=====================异常+最终===========================
===目标方法doSomeAfterThrowing(num0=12, num1=0)===
切面异常增强:业务方法运行时抛出异常:/ by zero
***通用的切面最终增强:总是会被执行***
切面最终增强:总是会被执行
java.lang.ArithmeticException: / by zero...