目录
概念:AOP 面向切面编程(Aspect-Oriented Programming)
面向切面编程,能够将那些与业务无关,却为业务模块所调用的逻辑封装起来,以减少系统代码的重复度,减少模块之间的耦合度。
代理模式
对比维度 | 静态代理 | 动态代理 |
---|---|---|
代理关系确定时机 | 编码期间:代理类需手动编写,明确代理目标 | 运行期间:代理类由程序动态生成,无需预定义 |
实现方式 | 代理类与目标类实现相同接口(或继承目标类),并通过组合持有目标对象 | 通过反射(JDK动态代理)或字节码生成(CGLIB)动态拦截目标对象方法 |
代理范围 | - 仅能代理同一接口/父类的类 - 需为每个目标类单独编写代理类 |
- JDK动态代理:代理接口实现类 - CGLIB:代理无接口类(非final类) |
代码复杂度 | - 代码简单直观,适合新手 - 需手动维护代理类 |
- 需理解反射/字节码机制 - 框架(如Spring)可简化开发 |
性能 | - 直接调用目标方法,无反射开销 - 性能更高 |
- JDK动态代理:反射调用性能较低 - CGLIB:首次生成代理类较慢 |
灵活性 | - 扩展功能固定,仅针对特定类 - 接口变动需同步修改代理类 |
- 可批量代理多个类,统一添加功能(如日志、事务) - 接口变动自动适配 |
典型应用场景 | - 代理少量固定类 - 高频调用场景(如核心服务) |
- 框架级功能扩展(Spring AOP) - 需统一管理多个类的增强逻辑 |
静态代理:编码期间就决定好了代理关系
定义:代理对象是目标对象的子类型,代理对象本省并不是目标对象,而是将目标作为自己的属性
优点:同一种类型的所有对象都能代理
缺点:范围太小
动态代理:运行期间才决定好了代理关系
定义:目标对象会在执行期间被动态拦截,插入指定逻辑
优点:可以代理世间万物
缺点:复杂不好写
一 静态代理
静态代理是通过手动编写一个“替身类”(代理类),在不修改原始类代码的前提下,为其添加额外功能(如日志、权限校验),使用时通过代理类间接调用原始类。
一、核心作用
控制访问:限制或增强对原始对象的访问(如权限校验)。
功能扩展:在不修改原始对象代码的前提下,添加新功能(如日志记录、性能统计)。
二、使用场景
日志记录:在方法调用前后自动记录日志。
权限校验:调用方法前检查用户权限。
延迟加载:在需要时才初始化复杂对象(如大文件加载)。
简化复杂操作:隐藏底层复杂逻辑,对外提供简洁接口。
避坑提示:如果系统中有大量需要代理的类,建议改用动态代理(如JDK动态代理或CGLIB)。
代码实现:
定义接口:
package org.example.spring02.MathMethod;
public interface MathCalculator {
//定义四则运算
public int div(int a, int b);
public int mul(int a, int b);
public int sub(int a, int b);
public int add(int a, int b);
}
实现类:
package org.example.spring02.MathMethod.MIm;
import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;
@Component
public class MathCalculatorImpl implements MathCalculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int sub(int a, int b) {
return a - b;
}
@Override
public int mul(int a, int b) {
return a * b;
}
@Override
public int div(int a, int b) {
return a / b;
}
}
静态代理类:
package org.example.spring02.MathMethod.proxy.statics;
import lombok.Data;
import org.example.spring02.MathMethod.MathCalculator;
/**
* 静态代理类,实现了MathCalculator接口
* 本类用于在不修改原始类的情况下,增加额外的功能处理,如日志、权限校验等
*/
@Data
public class CalculatorStaticProxy implements MathCalculator {
// 被代理对象
private MathCalculator target;
public CalculatorStaticProxy(MathCalculator mc) {
this.target = mc;
}
@Override
public int div(int a, int b) {
//书写日志
System.out.println("div被调用了,参数是:" + a + "," + b);
System.out.println("div被调用了,结果是:" + target.div(a, b));
return target.div(a, b);
}
@Override
public int mul(int a, int b) {
System.out.println("mul被调用了,参数是:" + a + "," + b);
System.out.println("mul被调用了,结果是:" + target.mul(a, b));
return target.mul(a, b);
}
@Override
public int sub(int a, int b) {
System.out.println("sub被调用了,参数是:" + a + "," + b);
System.out.println("sub被调用了,结果是:" + target.sub(a, b));
return target.sub(a, b);
}
@Override
public int add(int a, int b) {
System.out.println("add被调用了,参数是:" + a + "," + b);
System.out.println("add被调用了,结果是:" + target.add(a, b));
return target.add(a, b);
}
}
测试类:
// JUnit测试方法,用于测试MathCalculator的功能
@Test
void test01() {
// 创建MathCalculator的实例对象
MathCalculator target = new MathCalculatorImpl();
// 调用add方法进行加法运算,并打印结果
int add1 = target.add(1, 3);
System.out.println(add1);
// 分隔符,用于区分不同的测试部分
System.out.println("======");
// 创建静态代理对象,并传入目标对象
CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(target);
// 通过静态代理对象调用add方法进行加法运算,并打印结果
int add = calculatorStaticProxy.add(1, 3);
System.out.println(add);
}
运行展示:
二 动态代理
动态代理:JDK动态代理目标对象必须有接口,代理的也只是接口规定的方法
动态代理:是在运行时通过反射或字节码技术自动生成代理类,无需手动编写“替身类”,即可为多个目标对象统一添加功能(如日志、事务管理)。
拦截处理:动态代理对象在调用原生对象的方法时,通过统一的入口(如 InvocationHandler.invoke()
)拦截方法调用,从而实现对参数、方法执行过程和返回结果的增强或修改。
一、核心作用
统一扩展功能:批量对多个类添加相同的增强逻辑。
解耦代码:将核心业务逻辑与辅助功能(如权限校验)分离。
动态适配:运行时根据需求灵活生成代理对象。
二、使用场景
框架级功能扩展:如Spring AOP(日志、事务、权限)。
RPC调用:隐藏网络通信细节,让远程调用像本地调用。
接口监控:统计接口调用耗时、成功率等。
Mock测试:动态生成测试用的代理对象。
1. ClassLoader loader
作用:
动态代理生成的字节码需要被类加载器加载到 JVM 中。使用目标对象的类加载器可确保代理类能访问目标类及其接口。示例:
// 目标对象
UserService target = new UserServiceImpl();
// 使用目标对象的类加载器
ClassLoader loader = target.getClass().getClassLoader();
2.Class<?>[] interfaces
作用:
指定代理类需要实现的接口。代理对象会实现这些接口的所有方法,并将方法调用委托给InvocationHandler
。示例:
// 目标对象实现的接口
Class<?>[] interfaces = target.getClass().getInterfaces();
3.InvocationHandler h
作用:
定义代理逻辑的核心处理器。所有通过代理对象调用的方法,都会触发invoke()
方法,开发者可在此插入增强逻辑。示例:
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑(如记录日志)
System.out.println("调用方法: " + method.getName());
// 调用目标对象的方法
return method.invoke(target, args);
}
};
具体实现:(初始)
@Test
void test02() {
// 原生对象
MathCalculator target = new MathCalculatorImpl();
int add1 = target.add(1, 3);
System.out.println("原生对象");
System.out.println(add1);
//创建动态代理
/*
三个参数
proxy:代理对象->相当于明星的代理人
method:代理对象调用目标对象的方法
args:方法调用传递的参数
*/
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用目标对象的方法并返回
System.out.println("动态代理InvocationHandler的invoke中定义的");
System.out.println("[日志] 调用了方法: " + method.getName());
System.out.println("[日志] 获得参数: " + Arrays.toString(args));
return method.invoke(target, args);
}
};
/*
三个参数
ClassLoader:确保代理类的正确加载。
Interfaces:定义代理类的行为范围(基于接口)。
InvocationHandler:实现动态代理的核心逻辑(AOP 的基础)。
*/
MathCalculator proxy = (MathCalculator) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler
);
int add = proxy.add(1, 3);
System.out.println(add);
}
具体实现:(改进)
一、核心业务逻辑
1. 接口 MathCalculator
作用:定义业务契约,明确四则运算的规范。
关键点:
声明了加减乘除四个方法。
作为动态代理的接口约束(JDK动态代理必须基于接口)。
2. 实现类 MathCalculatorImpl
作用:具体实现四则运算的业务逻辑。
关键点:
使用
@Component
标注为 Spring 组件(若配合 Spring 使用)。实现了
MathCalculator
接口中的方法。是动态代理的目标对象(原生对象)。
接口
package org.example.spring02.MathMethod;
public interface MathCalculator {
//定义四则运算
public int div(int a, int b);
public int mul(int a, int b);
public int sub(int a, int b);
public int add(int a, int b);
}
实现类
package org.example.spring02.MathMethod.MIm;
import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;
@Component
public class MathCalculatorImpl implements MathCalculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int sub(int a, int b) {
return a - b;
}
@Override
public int mul(int a, int b) {
return a * b;
}
@Override
public int div(int a, int b) {
return a / b;
}
}
二、日志工具模块
日志工具类 logUtils
作用:提供静态方法统一管理日志格式。
核心方法:
logStart()
:记录方法开始执行的日志(含方法名和参数)。logEnd()
:记录方法正常结束的日志(含结果)。logException()
:记录方法执行异常的日志。
设计亮点:
使用
Object... args
支持可变参数,适配不同方法的参数。通过
Arrays.toString(args)
格式化参数输出。
package org.example.spring02.log;
import java.util.Arrays;
public class logUtils {
public static void logStart(String name , Object... args){
System.out.println("开始执行方法:"+name+",参数:"+ Arrays.toString(args));
}
public static void logEnd(String name , Object result){
System.out.println("方法:"+name+",执行结果:"+result);
}
public static void logException(String name , Exception e){
System.out.println("方法:"+name+",执行异常:"+e.getMessage());
}
public static void logEnd(){
System.out.println("方法执行结束");
}
}
三、动态代理模块
动态代理类 DynamicProxy
作用:生成代理对象,为原生对象的方法调用添加日志增强逻辑。
package org.example.spring02.MathMethod.proxy.dynamic;
import org.example.spring02.log.logUtils;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class DynamicProxy {
public static Object newProxyInstance(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("动态代理开始");
System.out.println("[日志] 调用了方法: " + method.getName());
logUtils.logStart(method.getName(), args);
System.out.println("[日志] 获得参数: " + Arrays.toString(args));
return method.invoke(target, args);
}
);
}
}
四、测试验证模块
测试方法 test03()
作用:验证动态代理的日志增强功能是否生效。
@Test
void test03(){
MathCalculator o = (MathCalculator) DynamicProxy.newProxyInstance(new MathCalculatorImpl());
int add = o.add(1, 3);
System.out.println(add);
}