AOP切面编程
- 通知类型
- 表达式
- 重用表达式
- 切面优先级
- 使用注解开发,加上注解实现某些功能
简介
- 动态代理分为JDK动态代理和cglib动态代理
- 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
- JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
- cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
依赖
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
注解说明
@Aspect
表示这个类是一个切面类@Component
注解保证这个切面类能够放入IOC容器
简单使用
几种通知类型
- 前置通知
@Before()
- 环绕通知
@Around()
- 返回通知
@AfterReturning()
- 异常通知
@AfterThrowing()
- 后置通知
@After()
表达式介绍
切入点表达式
在方法中可以传入参数public void afterMethod(JoinPoint joinPoint)
,类型为JoinPoint
-
获取连接点的签名信息
-
String methodName = joinPoint.getSignature().getName()
-
-
获取目标方法的返回值
-
其中表达式中
returning
后面参数result
要与public void afterReturningMethod(JoinPoint joinPoint, Object result)
传入参数名称一样,类型可以不一样但是名称要一样。 -
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); }
-
-
获取目标方法的异常
-
在表达式中加入
throwing = "ex"
,和上面一样,传入参数名称要一直,也要是ex
,如:Throwable ex
-
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); }
-
切入点表达式使用
前置通知
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
public class LogAspect {
// 设置切入点和通知类型
@Before(value = "execution(public int com.example.aop.annoaop.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("Logger-->前置通知" + "参数:" + args[0] + "," + args[1]);
}
}
后置通知
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
public class LogAspect {
@After(value = "execution(public int com.example.aop.annoaop.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();// 方法名
System.out.println("Logger-->后置通知,方法名:" + name);
}
}
返回通知
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
public class LogAspect {
@AfterReturning(value = "execution(public int com.example.aop.annoaop.CalculatorImpl.*(..))", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:" + methodName + ",结果:" + result);
}
}
异常通知
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
public class LogAspect {
@AfterThrowing(value = "execution(public int com.example.aop.annoaop.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
}
}
环绕通知
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
public class LogAspect {
@Around(value = "execution(public int com.example.aop.annoaop.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
// 目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
重用切入点表达式
申明表达式
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
在方法中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
在不同切面中使用
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
切面的优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
将@Order注解放在切面类上而不是方法上!!!
- 优先级低的
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
@Order(4)
public class LogAspect {
@After(value = "pointcut()")
public void afterMethod2(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();// 方法名
System.out.println("Logger-->后置通知 111111,方法名:" + name);
}
}
- 优先级高的
@Aspect// 表示这个类是一个切面类
@Component// 注解保证这个切面类能够放入IOC容器
@Order(1)
public class NewLogAspect {
@After(value = "com.example.aop.annoaop.LogAspect.pointcut()")
public void afterMethod(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();// 方法名
System.out.println("Logger-->后置通知 444444,方法名:" + name);
}
}
效果
原先交换顺序后
没有交换顺序前
注解使用
使用AOP在方法或者接口上写上某些注解,完成特定方法。
实现思路
- 创建
@interface
- 和上面一样要写
Aspect
,并且要被spring
管理 - 在方法或者接口上加上
@interface
注解
实现示例
- 创建
@interface
并命名为BunnyLog
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
// 加上这个注解打印当前时间
public @interface BunnyLog {
}
-
使用AOP切面,需要注意的是:
-
如果只是单纯的加上注解,不考虑指定类或者类中方法,完成某些功能,要修改下面切入点
-
@Pointcut(value = "@annotation(com.example.aop.annoaop.BunnyLog)") public void bunnyPointcut() { }
-
-
如果想指定某些类或者某些方法下的
-
@Pointcut("execution(* com.example.aop.annoaop.*.*(..)) && @annotation(com.example.aop.annoaop.BunnyLog)") public void bunnyPointcut() { }
-
-
-
创建切面
@Aspect
@Component
public class BunnyAspect {
/**
* 切入点,并且加上了 @BunnyLog注解
*/
@Pointcut(value = "execution(* com.example.aop.annoaop.*.*(..)) || @annotation(com.example.aop.annoaop.BunnyLog)")
public void bunnyPointcut() {
}
@Before("bunnyPointcut()")
public void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println("------AOP前置通知生效,方法名称------>" + name);
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyy年MM月dd日 HH:mm:ss");
String format = localDateTime.format(timeFormatter);
System.out.println("BunnyAspect,现在时间====>" + format);
}
}
- 普通方法,需要在类上加上
@Component
被spring管理,之后再方法中加上注解@BunnyLog
@Component
public class BunnyTestImpl {
@BunnyLog
public void method() {
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyy年MM月dd日 HH:mm:ss");
String format = localDateTime.format(timeFormatter);
System.out.println("测试方法,现在时间====>" + format);
}
}
- 创建一个测试类
public class TestBefore {
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
BunnyTestImpl bean = context.getBean(BunnyTestImpl.class);
bean.method();
}
}
执行结果:
r);
System.out.println(“测试方法,现在时间====>” + format);
}
}
- 创建一个测试类
```java
public class TestBefore {
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
BunnyTestImpl bean = context.getBean(BunnyTestImpl.class);
bean.method();
}
}
执行结果: