AOP思想概念
抽:把非核心代码封装到切面类中进行管理,
套:抽出来的代码套用到当前抽取出来的位置。
效果:不修改源代码基础上给程序动态的统一添加额外功能。
横切关注点:目标对象中所抽取出来的非核心业务。
一个方法有多个横切关注点。
根据附加功能的逻辑上的需要:有十个附加功能,就有十个横
切关注点。
通知:
把横切关注点收集到类中,或者把横切关注点封装到类中,然后这个类叫做切面。
而切面类名所封装的每一个横切关注点都是一个通知。
(非核心业务代码在目标对象中叫做横切关 注点,抽取出来之后,
放到切面类中进行封装,当前横切关注点在切面中表示为通知方法)
一个横切关注点表示一个通知方法。
通知分为五种:
前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行
异常通知:在被代理的目标方法异常结束后执行
后置通知:在被代理的目标方法最终结束后执行
环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,
包括上面四种通知对应的所有位置。
切面:
切面也就是封装通知方法的类。
目标:
指的是目标对象:进行功能增强的对象,也就是抽非核心业务代码的对象。
代理:
AOP里面代理对象不需要自己创建,封装了代理模式。
套
连接点:
纯逻辑概念,不是语法定义的。
抽取横切关注点的位置,横切关注点从哪抽出来的。
为什么要连接点:AOP面向切面编程,不但要抽取出来,还要从哪抽取的就要套到哪。
这就是不改变源代码的情况下进行功能增强加入一些额外操作。
切入点:
定位连接点的方式。
从代码的层面表示连接点的位置
本质就是表达式
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
基于注解的AOP
AOP是一种思想,AspectJ是AOP思想的具体实现方式。
@Aspect注解:将当前组件标识为切面
@Before():标识为前置通知 ....
把通知作用到连接点上,通过切入点表达式,
所以在设置在标识通知的注解面的value属性设置的就是切入点表达式
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码
文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
![](https://img-blog.csdnimg.cn/img_convert/6e57e287094cebee7d70ebf8ab6b80ce.png)
加入依赖
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
准备被代理的目标资源
接口
package com.atguigu.spring.aop.annotaion;
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
实现类/目标对象类
package com.atguigu.spring.aop.annotaion;
import org.springframework.stereotype.Component;
@Component
public class Calculatorlmpl implements Calculator{
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
切面类
package com.atguigu.spring.aop.annotaion;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
//将当前组件标识为切面
@Aspect
public class LoggerAspect {
/**
* 标识为前置通知
* 方法有参数,有两个参数都是int,不需要设置参数名,
* 因为方法存在重载,重载跟参数名没关系,根类型属顺序个数有关
*/
@Before("execution(public int com.atguigu.spring.aop.annotaion.Calculatorlmpl.add(int,int))")
public void beforeAdviceMethod(){
System.out.println("loggerAspect,前置通知");
}
}
ApplicationContext.xml文件
AOP的注意事项:
切面类和目标类都需要交给ioc容器管理
切面类必须通过@Aspect注解标识为一个切面
在Spring的配置文件设置 <aop:aspectj-autoproxy/> 开启基于注解的AOP
<context:component-scan base-package="com.atguigu.spring.aop.annotaion"></context:component-scan>
<!-- 开启基于注解的AOP功能 -->
<aop:aspectj-autoproxy/>
切入点表达式语法以及获取连接点信息的优化
切入点表达式语法
切入点表达式:在设置在标识通知的注解value属性中
第一个*表示任意的访问修饰符和返回值类型
第二个*表示当前类中任意的方法
. . 任意方法中的任意参数列表
类的地方也可以使用*,表示包下所有的类
@Before("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
表达式语法的重用
切入点表达式的重用
@Pointcut()声明一个公共的切入点表达式
@Pointcut("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
public void pointCut(){}
使用方式:@ABefore("pointCut()")
获取连接点的信息
JoinPoint:获取连接点信息
在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
获取连接点所对应方法的签名信息:joinPoint.getSignature() ;
获取连接点所对应方法的参数: joinPoint.getArgs() ;
// 声明一个公共的切入点表达式
@Pointcut("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
public void pointCut(){}
@Before("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint){
// 获取连接点所对应方法的签名信息 也就是方法的声明信息
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("loggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
@After("pointCut()")
public void afterAviceMethod(){
}
各种通知方法
前置通知:使用@Before注解标识,在被代理的目标方法前执行。
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行
(在目标对象返回值之后执行)。
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行
(在catch子句中执行)。
后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行
(finally子句中执行的)。
环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的
目标方法,包括上面四种通知对应的所有位置。
前置通知
@Before("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("loggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
返回通知
在返回通知中若要获取目标对象方法的返回值,
只需要通过@AfterReturning注解的returning属性,
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数。
@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}
异常通知
在异常通知中若要获取目标对象方法的异常,
只需要通过@AfterThrowing注解的throwing属性,
就可以将通知方法的某个参数指定为接收目标对象方法出现异常的参数。
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Throwable ex){
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+"异常通知:"+ex);
}
后置通知
@After("pointCut()")
public void afterAviceMethod(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+"执行完毕");
}
环绕通知
环绕通知的方法的返回值一定要和目标对象方法的返回值一致。
@Around("pointCut()")
public Object aroundAdviceMethod(ProceedingJoinPoint proceedingJoinPoint){
Object result=null;
try {
System.out.println("环绕通知---->前置通知");
// 表示目标对象方法的执行
result = proceedingJoinPoint.proceed();
System.out.println("环绕通知---->返回通知");
} catch (Throwable e) {
System.out.println("环绕通知---->返回通知");
}finally {
System.out.println("环绕通知---->后置通知");
}
return result;
}
切面优先级
通过@Order注解的value属性设置优先级,默认值Integer的最大值。
@Order注解的value属性值越小,优先级越高。
@Component
@Aspect
@Order(1)
public class ValibdateAspect {
@Before("execution(* com.atguigu.spring.aop.annotaion.Calculatorlmpl.*(..))")
public void boforeMethod(){
System.out.println("validateAspect-->前置通知");
}
}