一、AOP前奏
AOP:(Aspect Oriented Programming)面向切面编程
OOP:(Object Oriented Programming)面向对象编程
面向切面编程:基于OOP基础之上的新编程思想;指在程序运行期间,将某段代码动态的切入到指定的方法的指定位置进行运行的这种编程方式,成为面向切面编程
场景:计算器运行计算方法时进行日志记录
加入日志记录的方式:
1 2 3 4 5 | 1 、直接编写在方法内部【不推荐】,修改维护麻烦,代码耦合 日志记录:系统辅助功能 业务逻辑:系统核心功能 |
1 2 3 4 5 | 2 、我们希望的是: 业务逻辑:(核心功能);日志模块;在核心功能运行期间,自己动态的加入 运行时,日志功能可以加上; |
二、提出问题
情景:数学计算器
要求:
① 执行加减乘除运算;
② 日志:在程序执行期间追踪正在发生的活动
③ 验证:希望计算器只能处理正数的运算
常规实验:
Calculator 接口:
public interface Calculator {
public int add(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
MyMathCalculator 类:
public class MyMathCalculator implements Calculator{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
如果我们想要在方法运行的时候,添加日志记录该方法的参数和计算结果:
实现一:在业务方法中记录日志
@Override
public int add(int i, int j) {
System.out.println("【add】方法开始了,参数是:【"+ i + "," + j +"】");
int result = i + j;
System.out.println("【add】方法结束了,计算结果是:【"+ result +"】");
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("【sub】方法开始了,参数是:【"+ i + "," + j +"】");
int result = i - j;
System.out.println("【sub】方法结束了,计算结果是:【"+ result +"】");
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("【mul】方法开始了,参数是:【"+ i + "," + j +"】");
int result = i * j;
System.out.println("【mul】方法结束了,计算结果是:【"+ result +"】");
return result;
}
@Override
public int div(int i, int j) {
System.out.println("【div】方法开始了,参数是:【"+ i + "," + j +"】");
int result = i / j;
System.out.println("【div】方法结束了,计算结果是:【"+ result +"】");
return result;
}
实现二:使用工具类记录
编写 LogUtils 工具类:
public class LogUtils {
public static void logStart(Object... args) {
System.out.println("方法开始了,它使用的参数:" + Arrays.toString(args));
}
}
使用工具类:
@Override
public int add(int i, int j) {
LogUtils.logStart(i, j);
int result = i + j;
return result;
}
通过上面的两种实现方式,但是都存在下面的问题:
问题:
① 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
② 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
三、通过动态代理的方式
1、动态代理的原理
代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
图解:
2、动态代理的方式
3、数学计算器的改进
可以使用动态代理将日志代码动态的在目标方法执行前后先进行执行
MyMathCalculator:被代理类
public class MyMathCalculator implements Calculator{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
CalculatorProxy:代理类
/**
* CalculatorProxyNoLog:生成代理对象的类
*/
public class CalculatorProxyNoLog {
/**
* 为传入的参数创建一个动态代理对象
* Calculator calculator:被代理对象
* 返回代理对象
* @param calculator
*/
public static Calculator getProxy(final Calculator calculator) {
//被代理类的类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
//被代理类的接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,帮我们目标对象执行目标方法
InvocationHandler handler = new InvocationHandler() {
/**
*
* (Object proxy:代理对象,给 JDK 使用,任何时候都不要动这个对象
* Method method:当前将要执行的目标对象的方法
* Object[] args:整个方法调用时外界传入的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法,result 就是目标方法执行后的返回值
System.out.println("这是动态代理将要帮你执行方法");
Object result = method.invoke(calculator, args);
//返回值必须返回出去外界才能拿到真正执行后的返回值
return result;
}
};
//Proxy 为目标对象创建代理对象
Object proxy = Proxy.newProxyInstance(loader, interfaces, handler);
return (Calculator) proxy;
}
}
LogUtils:日志工具类
public class LogUtils {
public static void logStart(Object... args) {
System.out.println("方法开始了,它使用的参数:" + Arrays.toString(args));
}
public static void logStart(Method method, Object args){
System.out.println("【"+ method.getName() +"】方法执行了,参数为【"+ Arrays.asList(args) +"】");
}
public static void logReturn(Method method,Object result){
System.out.println("【"+ method.getName() +"】方法执行完成,他的结果为是:"+result);
}
public static void logException(Method method,Exception e){
System.out.println("【"+ method.getName() +"】方法出现了异常,异常为: "+ e.getCause());
}
public static void logEnd(Method method){
System.out.println("【"+ method.getName() +"】方法执行最终完成");
}
}
有了动态代理和完善的日志工具类,我们就可以低侵入式的实现给计算器功能实现日志记录功能:
完善动态代理
/**
*
* CalculatorProxy:生成代理对象的类
*/
public class CalculatorProxy {
/**
* 为传入的参数创建一个动态代理对象
* Calculator calculator:被代理对象
* 返回代理对象
* @param calculator
*/
public static Calculator getProxy(final Calculator calculator) {
//被代理类的类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
//被代理类的接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,帮我们目标对象执行目标方法
InvocationHandler handler = new InvocationHandler() {
/**
* 有了动态代理,日志记录可以做的非常强大,而且与业务逻辑解耦
*
* (Object proxy:代理对象,给 JDK 使用,任何时候都不要动这个对象
* Method method:当前将要执行的目标对象的方法
* Object[] args:整个方法调用时外界传入的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法,result 就是目标方法执行后的返回值
System.out.println("这是动态代理将要帮你执行方法");
Object result = null;
try {
System.out.println("【"+ method.getName() +"】方法开始执行,用的参数列表【"+ Arrays.toString(args)+"】");
result = method.invoke(calculator, args);
System.out.println("【"+ method.getName() +"】方法正常执行完毕,计算结果是:【"+ result+"】");
} catch (Exception e) {
//e.printStackTrace();
System.out.println("【"+ method.getName() +"】方法执行出现异常了,异常信息是:"+ e.getCause() +"");
} finally {
System.out.println("【"+ method.getName() +"】方法最终结束了");
}
//返回值必须返回出去外界才能拿到真正执行后的返回值
return result;
}
};
//Proxy 为目标对象创建代理对象
Object proxy = Proxy.newProxyInstance(loader, interfaces, handler);
return (Calculator) proxy;
}
}
测试:
/**
* 有了动态代理,日志记录可以做的非常强大,
*/
@Test
public void test1() {
Calculator calculator = new MyMathCalculator();
calculator.add(1,2);
System.out.println("=================");
//如果拿到这个对象的代理对象;使用代理对象执行加减乘除
Calculator proxy = CalculatorProxy.getProxy(calculator);
//代理对象和被代理对象唯一能产生的关联就是实现了同一个接口
System.out.println(proxy.getClass());//class com.sun.proxy.$Proxy4
proxy.div(2, 1);
}
四、总结
动态代理的问题:
1 2 3 | 1 、代码复杂 2 、jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为其创建代理对象的 |
Spring动态代理—>Spring实现AOP功能;底层就是动态代理
1 2 3 | 1 、可以利用Spring一句代码都不写的去创建动态代理; 2 、实现简单,没有强制要求必须实现接口 |
小结:
将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定的方法(加减乘除)的指定位置(方法的开始、结束、异常…)进行运行的这种编程方式(Spring简化了面向切面编程)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?