AOP篇
1.基本概念
AOP:Aspect Oriented Programming(面向切面编程)
OOP:Object Oriented Programming(面向对象编程)
2.静态代理
基本概念:
定义:代理对象,是目标对象的接口的子类型,代理对象本身并不是目标对象,而是将目标对象作为自己的属性
优点:同一种类型的所有对象都能代理
缺点:范围太小,只能负责部分接口的代理
实现:
首先定义一个包含方法的接口
public interface MathCalculator {
public int add(int a, int b);
}
然后实现这个接口
public class MathCalculatorImpl implements MathCalculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
现在定义一个静态代理类,同样实现接口
静态代理类接收目标对象,在重写接口方法时直接调用该对象的方法
public class CalculatorStaticProxy implements MathCalculator {
private MathCalculator target;
public CalculatorStaticProxy(MathCalculator target) {
this.target = target;
}
@Override
public int add(int a, int b) {
int add = this.target.add(a, b);
System.out.println("[日志]add结果: " + add);
return add;
}
}
这样就可以在测试的同时打印日志
3.动态代理
基本概念:
定义:目标对象在执行期间会被动态拦截,插入指定逻辑
优点:可以代理世间万物
缺点:不好写(雾)
动态代理:JDK动态代理强制要求:目标必须有接口
实现:
void test() {
// 创建真实对象
MathCalculator mathCalculator = new MathCalculatorImpl();
// 创建动态代理处理器
/**
* 当代理对象的方法被调用时,会自动调用此方法。
*
* @param proxy 代理对象
* @param method 被调用的方法
* @param args 方法的参数
* @return 方法的返回值
* @throws Throwable 如果调用方法时发生异常
*/
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(mathCalculator, args);
// 打印日志
System.out.println("Result: " + result);
System.out.println("After method: " + method.getName());
return result;
}
};
// 创建代理对象
MathCalculator proxyCalculator = (MathCalculator) Proxy.newProxyInstance(
mathCalculator.getClass().getClassLoader(),
mathCalculator.getClass().getInterfaces(),
handler
);
// 调用代理对象的方法
int add = proxyCalculator.add(1, 2);
}
可以使用lamda表达式简化方法,下面是一个工具类,返回proxy
public class DynamicProxy {
public static Object getProxyInstance(Object target){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(Proxy, method, args) -> {
String name = method.getName();
System.out.println("【日志】,【"+name+"】开始 :参数:"+ Arrays.toString(args)+"动态代理执行了");
Object result = method.invoke(target, args);
return result;
}
);
}
}
4.日志工具类
可以写一个日志工具类,统一日志功能,简化开发
public class LogUtils {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 输出信息级别的日志
* @param message 日志信息
*/
public static void info(String message) {
log("INFO", message);
}
/**
* 输出警告级别的日志
* @param message 日志信息
*/
public static void warn(String message) {
log("WARN", message);
}
/**
* 输出错误级别的日志
* @param message 日志信息
*/
public static void error(String message) {
log("ERROR", message);
}
private static void log(String level, String message) {
String timestamp = LocalDateTime.now().format(FORMATTER);
System.out.printf("[%s] [%s] %s%n", timestamp, level, message);
}
}
5.AOP初步实现
5.1.专业术语:
- 切面(Aspect):封装横切逻辑的模块,包含通知和切入点。
- 连接点(Join Point):程序执行中可插入切面的点,如方法调用、异常抛出。
- 切入点(Pointcut):一组连接点的表达式,指定哪些连接点会被增强。
- 通知(Advice):切面在连接点执行的代码,有前置、后置、环绕、异常、最终通知。
- 目标对象(Target Object):被切面增强的对象。
- 代理(Proxy):AOP框架为目标对象生成的包含增强逻辑的对象。
- 织入(Weaving):将切面应用到目标对象创建代理的过程,分编译、类加载、运行时织入。
5.2.导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.3.切入方法的实现
- 创建单独的切面类,@Component,@Aspect
- 为四个切面(开始,结束,返回,抛错)定义方法,并加上注释
- 匹配切入方法:”execution(返回值类型 方法全签名(参数类型))”
- execution中支持使用通配符(*代表省略一个,..代表省略多个)
- 方法被切后目标对象就会被包装为代理对象
- 流程:前置>>>目标方法>>>返回/异常>>>后置
@Component
@Aspect
public class LogAspect {
@Before("execution(int com.example.demo.calculator.Impl.MathCalculatorImpl.*(int ,int))")
public void logStart(){
System.out.println("start");
}
@After("execution(int com.example.demo.calculator.Impl.MathCalculatorImpl.*(int,int))")
public void logEnd(){
System.out.println("end");
}
@AfterReturning("execution(int com.example.demo.calculator.Impl.MathCalculatorImpl.*(int,int))")
public void logReturn(){
System.out.println("return");
}
@AfterThrowing("execution(int com.example.demo.calculator.Impl.MathCalculatorImpl.*(int,int))")
public void logThrow(){
System.out.println("throw");
}
}
全写法:@注解("execution [public] int [com.lin.spring.aop.calclator.....].add(int,int) [throws ArithmeticException]")
省略写法:@注解("execution int add(int i,int j)")
5.4.匹配方法总结
- execution:基于方法签名匹配 语法:execution(修饰符? 返回类型 类路径?方法名(参数) throws 异常?) 示例: execution(public * com.example.service..(..)):匹配com.example.service包下所有类的所有公共方法。 execution(* com.example.dao..(int, int)):匹配com.example.dao包下所有类接收两个int类型参数的方法。
- @annotation:基于注解匹配 语法:@annotation(注解全限定名) 示例:@annotation(com.example.annotation.MyAnnotation):匹配带有MyAnnotation注解的方法。
- within:基于类或包匹配 语法:within(类或包路径) 示例:within(com.example.service.*):匹配com.example.service包下所有类的所有方法。
- this 和 target:基于代理对象和目标对象类型匹配 语法:this(类型全限定名) 或 target(类型全限定名) 示例: this(com.example.service.MyService):匹配代理对象是MyService类型的方法。 target(com.example.service.MyService):匹配目标对象是MyService类型的方法。
- args:基于运行时参数类型匹配 语法:args(参数类型列表) 示例: args(int, int):匹配运行时传入两个int类型参数的方法。 execution(* com.example.service..(..)) && args(String, int):匹配com.example.service包下所有类的方法,且运行时传入参数为一个String类型和一个int类型。
- 组合表达式 使用&&(与)、||(或)、!(非)组合多个切入点表达式。 示例:execution(* com.example.service..(..)) && @annotation(com.example.annotation.MyAnnotation) && args(int):匹配com.example.service包下所有带有MyAnnotation注解且运行时传入一个int类型参数的方法。
AOP的底层原理
1、Spring会为每个被切面切入的组件创建代理对象(Spring CGLIB 创建的代理对象,无视接口)。
2、代理对象中保存了切面类里面所有通知方法构成的增强器链。
3、目标方法执行时,会先去执行增强器链中拿到需要提前执行的通知方法去执行。
5.5.切入方法参数:
JoinPoint获取连接点
JoinPoint:包装了当前目标方法的所有信息
@Before("execution(..)")
public void logStart(JoinPoint jp){
System.out.println("start");
}
通过JoinPoint可以获得方法的各种信息
获得目标方法传来的返回值
@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
//1、拿到方法全签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//方法名
String name = signature.getName();
//目标方法传来的参数值
Object[] args = joinPoint.getArgs();
System.out.println("【切面 - 日志】【"+name+"】开始:参数列表:【"+ Arrays.toString(args) +"】");
}
Returning指定返回值接收
@AfterReturning(value = "pointCut()", returning = "result") //returning="result" 获取目标方法返回值
public void logReturn(JoinPoint joinPoint,Object result){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName();
System.out.println("【切面 - 日志】【"+name+"】返回:值:"+result);
}
可以指定目标对象接收方法的返回值
throwing指定异常接收
@AfterThrowing(
value = "pointCut()",
throwing = "e" //throwing="e" 获取目标方法抛出的异常
)
public void logException(JoinPoint joinPoint,Throwable e){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName();
System.out.println("【切面 - 日志】【"+name+"】异常:错误信息:【"+e.getMessage()+"】");
}
5.6.@PointCut封装匹配方法
@Pointcut("execution(..)")
public void pppcut() {}
封装好后切入注解可以直接引用
@Before("pppcut()")
5.7.多切面执行顺序

代理对象封装目标方法(前置>>>目标方法>>>返回/异常>>>后置)
代理对象又作为新的目标方法被封装(前置>>>原代理对象>>>返回/异常>>>后置)
@Order(数字)可以指定优先级(不指定为类名首字母,A最前Z最后)
数字越小越先执行,越处于外层
6.环绕通知
作用:上述方法只能感知对象,不能修改对象,环绕通知则可以控制目标方法是否执行,修改目标对象的参数,执行结果等
public class aroundAspect {
@Around("execution(..)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();//获取参数
System.out.println("环绕-前置通知");
Object result = null;
try {
result = pjp.proceed(args);//执行目标方法
System.out.println("环绕-返回通知");
}
catch (Throwable e) {
System.out.println("环绕-异常通知");
throw e;
}
finally {
System.out.println("环绕-后置通知");
}
return result;
}
}
7.AOP常见应用场景:
- 日志记录:在方法前后记录操作信息,便于调试与监控。
- 事务管理:统一管理事务的开启、提交和回滚。
- 权限验证:执行方法前检查用户权限,保障系统安全。
- 性能监控:统计方法执行时间,定位性能瓶颈。
- 缓存管理:先查缓存,无结果则执行方法并缓存结果。
PS:Spring也提供了许多工具类
比如:

Spring的三级缓存机制


——————-AOP篇结束——————–
Comments NOTHING