[程序设计]---代理模式👳
本文章记录学习于------52.面向切面:AOP-场景模拟_哔哩哔哩_bilibili
最近闲来无事,在学习Spring的源码: 后面慢慢更新源码系列blog,希望多多关注🙏🙏
目前已经总结的blog 系列🕳: 慈様や 前端学习导航👩🏻🚀🚀 、小様---Java后端开发日记
很多框架在设计和实现过程中广泛运用了多种设计模式:Spring核心IOC、AOP
-
工厂模式:
BeanFactory
和ApplicationContext
实现了工厂模式,负责创建和管理Bean对象 -
单例模式: Spring 默认 将所有Bean定义为单例模式,即每个容器中只存在一个实例
-
代理模式: AOP 面向切面编程的核心,通过动态代理:
JDK代理
、CGLIB代理
实现了非侵入式编程,可以在不修改原有业务代码的情况下增加额外功能
在目标方法调用前后添加增强逻辑,如事务管理、日志记录等,
-
适配器默认、观察者默认、策略模式... 设计模式使,
Spring框架展现出了高度的模块化、可配置性和可扩展性,使得开发者能够高效地构建复杂的应用程序
👊 总而颜值->设计模式很重要,
设计模式其实是在软件开发过程中经过经验积累和验证总结,一套通用代码设计方案
如果熟悉了设计模式,当遇到类似的场景,我们可以快速地参考设计模式实现代码,
不仅可以加速我们的编码速度,也提升了代码的:扩展性、重用性、维护性!
个人看法:
虽然,设计模式听着高大上,其实实际开发过程中已经潜移默化的接触了很多了,
且有的设计模式已经随着时代、或还并没有完全适合的引用常见,新手建议会用为主,了解为辅
设计模式:属于程序的内功心法💖、学习是一个长期反复推敲的过程、不断优化升级🆙🆙❗ ❗ ❗
- 注意:设计模式不属于某个特定的编程语言,而是一种编程思想,代码模式;
设计模式分类:
目前设计模式主要有:23种,大致分为三大类:
创建型模式 Creational Patterns
用于创建对象的模式,同时隐藏对象创建的逻辑,
避免代码中出现大量 new
操作和复杂的创建逻辑,目的是解耦对象的创建和使用
- 常用的有: 单例模式、工厂模式、建造者模式
- 不常见的有: 原型模式
结构型模式 Structural Patterns
用于处理对象组合的结构,关注类与对象的组合,目的是通过组合对象或类的方式,形成更大的结构
- 常用的有: 适配器模式、桥接模式、组合模式、装饰器模式、代理模式
- 不常见的有: 外观模式(门面模式)、享元模式
行为型模式Behavioral Patterns
用于定义对象相互协作以完成单个对象无法单独实现的任务,目的:是定义类和对象间的通信方式;
- 常用的有: 观察者模式、责任链模式、策略模式、模板模式、状态模式、迭代器模式,
- 不常见的有: 备忘录模式、命令模式、中介者模式、访问者模式、解释器模式
需求介绍:
目前有一个计算器🧮类 ,它有add 加
、sub 减
的方法,新需求: 计算前后进行日志记录输出;
声明计算器接口: 用于规范计算器类的定义;
java
//计算器类接口:
public interface Calculator {
//计算器加函数
int add(int i,int j);
//计算器减函数
int sub(int i,int j);
}
计算器接口实现类: 声明并实现计算器接口;
java
//计算器接口实现类:
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) { return i+j; }
@Override
public int sub(int i, int j) { return i-j; }
}
新需求:在加减之前进行日志记录------计算器日志接口实现类:
- 正常情况下,大部分开发者会直接在上面实现类上改动,但为了不影响其他就创建新的实现类;
java
//计算器日志接口实现类: 不就是日志嘛一行代码CV的事~~
public class CalculatorLogImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("[日志]mul 方法开始了,参数是:"+i+","+ j);
System.out.println("[日志]mul 方法结束了,结果是:"+(i + j));
return i + j;
}
@Override
public int sub(int i, int j) {
System.out.println("[日志]mul 方法开始了,参数是:"+i+","+ j);
System.out.println("[日志]mul 方法结束了,结果是:"+(i - j));
return i-j;
}
}
轻轻松松~ 小活😀 这就结束了吗?到此为止了...
NO---NO---NO❗ ❗ ❗ 如果这个类在大一点呢?或者方法实现更复杂呢?
- 针对日志功能实现类,我们发现有如下缺陷:对核心业务功能有干扰,附加功能分散各个业务功能中;
🆗,主角登场------代理模式:👇👇👇
JDK 代理模式
代理模式是一种结构型设计模式:
它为其他对象提供一种代理,以控制对这个对象的访问,代理对象可以在客户端和目标对象之间起到中介的作用,
在不改变目标对象接口的前提下,对目标对象的访问进行控制或增强,上述代码举例:👆👆👆
代理模式的特点:
- 功能增强: 代理对象可以在访问实际对象之前或之后添加额外的功能
- 间接访问: 客户端通过代理访问实际对象,代理对象负责对实际对象的控制
- 代码解耦: 客户端不直接与实际对象交互,通过代理对象可以透明地扩展实际对象的功能
代理模式的分类:
- 静态代理: 在编译时创建代理类,代理类与目标类实现相同的接口,
- 动态代理: 在运行时动态生成代理类,适用于无需事先定义代理类的场景
静态代理:
静态代理:不推荐) 了解即可,实际开发中不经常使用,
代理类和被代理类在 编译时期 就确定了它们之间的代理关系,
代理类需要 实现 与被代理类 相同的接口 ,并且在代理类中
持有被代理类的实例 目标对象
,
通过调用被代理类的方法 ,来完成实际的操作,在方法调用前后添加额外的逻辑; 这一点也 不高级;
java
//静态代理类
public class CalculatorStaticProxy implements Calculator {
//被代理的目标对象
private Calculator target;
public CalculatorStaticProxy(Calculator target) { this.target = target; }
@Override
//附加功能由代理类中的代理方法来实现
public int add(int i, int j) {
System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
//通过目标对象来实现核心业务逻辑
int addResult = target.add(i, j);
System.out.println("[日志] add 方法结束了,结果是:" + addResult);
return addResult;
}
//省略...其他实现;
}
java
//静态代理使用:
public class CalculatorStaticProxyMain {
public static void main(String[] args) {
//创建被代理类对象
CalculatorImpl calculator = new CalculatorImpl();
//被代理类对象 ==>构造创建 对应的 ==> 静态代理类,静态代理类中对函数进行调用扩展;
CalculatorStaticProxy calStaticProxy = new CalculatorStaticProxy(calculator);
//因此: 直接使用静态代理类对象即可,获得存在日志的计算机函数方法;
calStaticProxy.add(1,1);
}
}
静态代理确实实现了解耦:,但是由于代码都写死了,完全不具备任何的灵活性
- 如果:将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码
如何: 将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现------动态代理技术
动态代理:
涉及知识: 【反射】、【类加载ClassLoad】... ,JDK 官方文档📑 Java.lang.reflect.Proxy
Java 动态代理是一种在运行时
动态地创建代理对象的机制:
-
它允许开发者在不修改原有类代码的基础上,
-
为目标对象创建代理,并且能够在代理对象的方法调用前后灵活地添加自定义的逻辑;
与静态代理不同: 动态代理不需要为每个目标类手动编写代理类,在JDK
中,常用的实现方式是 反射
- 反射机制: 是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力
实现原理
实现原理: 基于 Java 的java.lang.reflect
包:InvocationHandler
接口:
开发者需要实现这个接口,并且实现invoke
方法,在invoke
方法中定义代理对象的方法被调用时要执行逻辑;
invoke
方法有三个参数:proxy
代理对象本身、method
被调用的方法对象、args
方法的参数数组;
Proxy
类: 通过Proxy.newProxyInstance
方法可以 创建生成 代理对象;这个方法需要三个参数:
- 目标对象的类加载器:
目标对象.getClass().getClassLoader(),
- 目标对象的接口数组:
目标对象.getClass().getInterfaces(),
InvocationHandler
接口实例;
🆗,到此就获得了一个:(动态)代理对象: 使用动态代理对象,具有目标对象,一样的方法([参数]);
- 通过代理对象方法时,实际上会调用
InvocationHandler实例对象
的invoke
方法,方法中,执行了增强逻辑;
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//动态代理类
public class DynamicProxy implements InvocationHandler {
//目标代理对象
private Object target;
public DynamicProxy(Object target) { this.target = target; }
@Override
//proxy代理对象本身、method被调用的方法对象、args方法的参数数组
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + args);
// 通过目标对象来实现核心业务逻辑
Object addResult = method.invoke(target, args);
System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + addResult);
return addResult;
}
}
java
import org.proxyD.Calculator;
import java.lang.reflect.Proxy;
import org.proxyD.CalculatorImpl;
/** 动态代理使用: */
public class DynamicProxyMain {
public static void main(String[] args) {
//定义目标对象、InvocationHandler接口实例
CalculatorImpl calculator = new CalculatorImpl();
DynamicProxy calStaticProxy = new DynamicProxy(calculator);
//Proxy.newProxyInstance(目标类加载对象,目标接口数组,InvocationHandler接口实例) 动态创建生成代理对象
Calculator Calculatorimpl = (Calculator) Proxy.newProxyInstance(
//calculator 目标对象通过接口getClass 获得类加载对象、接口数组
calculator.getClass().getClassLoader(),
calculator.getClass().getInterfaces(),
//InvocationHandler 接口实例
calStaticProxy
);
//使用: 生成代理类
Calculatorimpl.add(1,1);
Calculatorimpl.sub(1,1);
}
}
动态代理优势: 可以在运行时根据需要动态地创建代理对象,而不需要像静态代理那样为每个目标类提前编写代理类
代码复用性强 :一个InvocationHandler
实现类可以用于代理多个实现相同接口的目标对象
易于维护和扩展 :由于代理逻辑集中在InvocationHandler
实现类的invoke
方法中,
当需要修改代理逻辑时,只需要修改这个方法即可,而不需要多个代理类进行修改;
优化---代理工厂:
考虑到代码复用与管理: 可以对上述代码进行优化一个 代理工厂,将Proxy.newProxyInstance
生成代理类操作进行统一管理;
匿名内部类实现: InvocationHandler
接口,重写了invoke
方法;
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
//动态代理工厂
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target) { this.target = target; }
//动态代理对象函数
//通过: 创建 InvocationHandler接口 匿名实现类对象,直接返回 动态代理对象;
public Object getProxy(){
/** newProxyInstance():创建一个代理实例 其中有三个参数: */
/** 1、classLoader:加载动态生成的代理类的类加载器 */
ClassLoader classLoader = target.getClass().getClassLoader();
/** 2、interfaces:目标对象实现的所有接口的class对象所组成的数组 */
Class<?>[] interfaces = target.getClass().getInterfaces();
/** 3、通过: 创建 InvocationHandler接口 匿名实现类对象,直接返回 动态代理对象
* invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法 */
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[动态代理][日志] "+method.getName()+" 异常");
} finally {
System.out.println("[动态代理][日志] 方法执行完毕");
}
return result;
}
};
//最后返回代理对象;
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
CGLIB 动态代理:
JDK动态代理是基于接口的,所以要求代理类一定是有定义接口的,
CGLIB 基于 ASM
字节码生成工具,它是通过 继承
的方式生成目标类的子类来实现代理类,
-
注意
final
方法,不可继承 -
它们之间的性能随着
JDK
版本的不同而不同: -
JDK6
在运行次数较少的情况下,动态代理与CGLIB
差距不明显,次数增加之后CGLIB
更快 -
JDK7
情况发生了逆转!在运行次数较少1.000,000情况下,JDK
动态代理比CGLIB
快了差不多30%;而当调用次数增加之后(50000000),
JDK
动态代理比CGLIB
快了接近1倍,Jdk8
表现和Jdk7
基本一致
CGLIB 的工作原理
继承目标类 :CGLIB 通过 继承目标类 来创建代理类。在生成的代理类中,它会重写目标类的方法
方法拦截 :CGLIB 利用方法拦截器(MethodInterceptor
)来控制对目标类方法的访问;
- 当代理类的方法被调用时,会先进入方法拦截器的
intercept
方法;进行操作;
添加 CGLIB 依赖:CGLIB 是第三方库,所以需要引入对应依赖;
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
创建方法拦截器 :实现 CGLIB 的MethodInterceptor
接口来定义方法拦截逻辑,重写:intercept
方法:
-
第一个参数
o
是代理对象本身 -
第二个参数
method
是被调用的目标方法 -
第三个参数
objects
是目标方法的参数数组 -
第四个参数
methodProxy
是一个方法代理对象,用于调用目标类的原始方法通过
methodProxy.invokeSuper(o, objects)
可以调用目标类的原始方法,并获取返回结果;
java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//实现 CGLIB 的MethodInterceptor接口来定义方法拦截逻辑
public class CglibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理类在目标方法执行前的操作"+method.getName());
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("代理类在目标方法执行后结果"+result);
return result;
}
}
java
//CGLIB 的Enhancer类来创建代理对象
import net.sf.cglib.proxy.Enhancer;
public class CglibInterceptorMain {
public static void main(String[] args) {
//首先创建一个Enhancer对象
Enhancer enhancer = new Enhancer();
//设置代理类要继承的目标类、设置方法拦截器
enhancer.setSuperclass(Calculator.class);
enhancer.setCallback(new CglibInterceptor());
//最后通过enhancer.create()创建代理对象将其转换为目标类类型
Calculator proxy = (Calculator) enhancer.create();
//代理类对象调用函数;
proxy.add(1,1);
proxy.sub(1,1);
}
}
🆗,代理模式大致如此,是不是so easy
轻轻松松的,求点赞👍、收藏⭐ 🥰🥰🥰