马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
[步伐计划]—署理模式👳
本文章记载学习于——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 减 的方法,新需求: 盘算前后举行日记记载输出;
声明盘算器接口: 用于规范盘算器类的界说;
- //计算器类接口:
- public interface Calculator {
- //计算器加函数
- int add(int i,int j);
- //计算器减函数
- int sub(int i,int j);
- }
复制代码 盘算器接口实现类: 声明并实现盘算器接口;
- //计算器接口实现类:
- 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; }
- }
复制代码 新需求:在加减之进步行日记记载——盘算器日记接口实现类:
- 正常环境下,大部分开辟者会直接在上面实现类上改动,但为了不影响其他就创建新的实现类;
- //计算器日志
接口实现类: 不就是日志 嘛一行代码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 署理模式
署理模式是一种布局型计划模式:
它为其他对象提供一种署理,以控制对这个对象的访问,署理对象可以在客户端和目的对象之间起到中介的作用,
在不改变目的对象接口的条件下,对目的对象的访问举行控制或增强,上述代码举例:👆👆👆
署理模式的特点:
- 功能增强: 署理对象可以在访问现实对象之前或之后添加额外的功能
- 间接访问: 客户端通过署理访问现实对象,署理对象负责对现实对象的控制
- 代码解耦: 客户端不直接与现实对象交互,通过署理对象可以透明地扩显现实对象的功能
署理模式的分类:
- 静态署理: 在编译时创建署理类,署理类与目的类实现类似的接口,
- 动态署理: 在运行时动态天生署理类,实用于无需事先界说署理类的场景
静态署理:
静态署理:不保举) 相识即可,现实开辟中不常常使用,
署理类和被署理类在 编译时期 就确定了它们之间的署理关系,
署理类必要 实现 与被署理类 类似的接口 ,而且在署理类中持有被署理类的实例 目的对象,
通过调用被署理类的方法,来完成现实的利用,在方法调用前后添加额外的逻辑; 这一点也 不高级;
- //静态代理类
- 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;
- }
- //省略...其他实现;
- }
复制代码- //静态代理使用:
- 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方法,方法中,实行了增强逻辑;
- 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;
- }
- }
复制代码- 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方法;
- 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 是第三方库,以是必要引入对应依赖;
- <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)可以调用目的类的原始方法,并获取返回效果;
- 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;
- }
- }
复制代码- //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轻轻松松的,求点赞👍、收藏⭐ 🥰🥰🥰
|