9.1、环境搭建

9.1.1、创建module

9.1.2、选择maven

9.1.3、设置module名称和路径

9.1.4、module初始状态

9.1.5、配置打包方式和依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.rain</groupId>
<artifactId>spring_proxy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <dependencies>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies> </project>

9.2、场景模拟

9.2.1、创建Calculator接口及实现类

package org.rain.spring.proxy;

/**
* @author liaojy
* @date 2023/8/6 - 23:53
*/
public interface Calculator { int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j); }

package org.rain.spring.proxy;

/**
* @author liaojy
* @date 2023/8/6 - 23:55
*/
public class CalculatorImpl implements Calculator {
public int add(int i, int j) { int result = i + j;
System.out.println("方法内部 result = " + result);
return result; } public int sub(int i, int j) { int result = i - j;
System.out.println("方法内部 result = " + result);
return result; } public int mul(int i, int j) { int result = i * j;
System.out.println("方法内部 result = " + result);
return result; } public int div(int i, int j) { int result = i / j;
System.out.println("方法内部 result = " + result);
return result; }
}

9.2.2、为Calculator实现类增加日志功能

package org.rain.spring.proxy;

/**
* @author liaojy
* @date 2023/8/6 - 23:55
*/
public class CalculatorImpl implements Calculator {
public int add(int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
int result = i + j;
System.out.println("方法内部 result = " + result);
System.out.println("[日志] add 方法结束了,结果是:" + result);
return result; } public int sub(int i, int j) { System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
int result = i - j;
System.out.println("方法内部 result = " + result);
System.out.println("[日志] sub 方法结束了,结果是:" + result);
return result; } public int mul(int i, int j) { System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
int result = i * j;
System.out.println("方法内部 result = " + result);
System.out.println("[日志] mul 方法结束了,结果是:" + result);
return result; } public int div(int i, int j) { System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
int result = i / j;
System.out.println("方法内部 result = " + result);
System.out.println("[日志] div 方法结束了,结果是:" + result);
return result; }
}

9.3、场景分析

9.3.1、代码缺陷

关于带日志功能的实现类,有如下缺陷:

  • 附加功能对核心业务功能有干扰,降低了开发效率

  • 附加功能分散在各个业务功能方法中,不利于统一维护

9.3.2、解决思路

解决这两个问题,核心方式就是:解耦;把附加功能从业务功能代码中抽取出来

9.3.3、技术难点

要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,因此需要引入新的技术(代理模式)。

9.4、代理模式的概述

9.4.1、概念

  • 代理模式是二十三种设计模式中的一种,属于结构型模式

  • 它的思想就是在不改动目标方法代码的基础上,增强目标方法的功能

  • 它的实现就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用目标方法

  • 它的作用就是把不属于目标方法核心逻辑的代码从目标方法中剥离出来,从而实现解耦和统一维护

9.4.2、术语

  • 目标:封装了核心功能代码,的类、对象、方法

  • 代理:封装了增强功能代码、且能调用目标,的类、对象、方法

9.4.3、生活中的目标和代理

  • 广告商找大明星(目标)拍广告,需要经过经纪人(代理)

  • 买房者找卖房者(目标)购房,需要经过房产中介(代理)

9.5、静态代理

先将实现类CalculatorImpl还原为没有增加日志功能的状态,即9.2.1小节的状态

9.5.1、创建静态代理类CalculatorStaticProxy

注意:代理类和目标类要实现相同的接口,这样能保证它们有相同的方法列表

package org.rain.spring.proxy;

/**
* @author liaojy
* @date 2023/8/7 - 12:56
*/
public class CalculatorStaticProxy implements Calculator { // 将被代理的目标对象声明为成员变量
private Calculator target; public CalculatorStaticProxy(Calculator target) {
this.target = target;
} 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 int sub(int i, int j) {
System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
int subResult = target.sub(i, j);
System.out.println("[日志] sub 方法结束了,结果是:" + subResult);
return subResult;
} public int mul(int i, int j) {
System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
int mulResult = target.mul(i, j);
System.out.println("[日志] mul 方法结束了,结果是:" + mulResult);
return mulResult;
} public int div(int i, int j) {
System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
int divResult = target.div(i, j);
System.out.println("[日志] div 方法结束了,结果是:" + divResult);
return divResult;
}
}

9.5.2、测试

package org.rain.spring.test;

import org.junit.Test;
import org.rain.spring.proxy.CalculatorImpl;
import org.rain.spring.proxy.CalculatorStaticProxy; /**
* @author liaojy
* @date 2023/8/7 - 14:12
*/
public class ProxyTest { @Test
public void testStaticProxy(){
CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
int addResult = calculatorStaticProxy.add(1, 2);
System.out.println(addResult);
} }

9.5.3、静态代理的缺点

  • 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性

  • 当其他目标类也需要附加日志,就得创建更多静态代理类,还是产生了大量重复的代码;而且日志功能还是分散的,没有统一管理

9.6、动态代理

动态代理的意思是,在代码运行的过程中动态地生成目标类的代理类

9.6.1、创建生成代理对象的工厂类ProxyFactory

比起实现固定接口方法的静态代理,动态代理的关键是能动态获取并实现目标的接口方法;

因此动态代理能对任意目标对象的核心业务方法(接口方法)进行增强

package org.rain.spring.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays; /**
* @author liaojy
* @date 2023/8/7 - 23:07
*/
//这个类不是一个代理类而是一个工具(工厂)类,用于动态生成目标对象的代理对象
public class ProxyFactory { //因为被代理的目标对象是任意的,所以目标对象变量的类型设为Object
private Object target; //通过工厂类的有参构造方法,对目标对象变量进行赋值
public ProxyFactory(Object target) {
this.target = target;
} //生成任意目标对象所对应的代理对象;因为不确定动态生成的代理对象的类型,所以返回值设为Object
public Object getPoxy(){ //通过目标对象获取应用类加载器
ClassLoader classLoader = target.getClass().getClassLoader(); //获取目标对象实现的所有接口的class对象所组成的数组
Class<?>[] interfaces = target.getClass().getInterfaces(); //通过InvocationHandler的匿名内部类,来设置代理类中如何重写接口中的抽象方法
InvocationHandler invocationHandler = new InvocationHandler() { //通过invoke方法来统一管理代理类中的方法该如何执行,该方法有三个参数
/**
* @param proxy:表示代理对象
* @param method:表示要执行的方法
* @param args:表示要执行的方法的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用目标对象执行功能之前,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + Arrays.toString(args)); //固定写法:调用目标对象实现的核心逻辑(最重要的步骤)
Object result = method.invoke(target, args); //在调用目标对象执行功能之后,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + result); //固定写法:保证代理对象和目标对象的返回值一致
return result;
} }; //返回(java.lang.reflect包下的)Proxy类的newProxyInstance方法所生产的代理对象
/**
* newProxyInstance方法有三个参数:
*
* 1、ClassLoader classLoader:指定加载(动态生成的)代理类的类加载器
* 类只有被加载后才能使用,(动态生成的)代理类需要用应用类加载器来加载
* 类加载器有四种:
* 跟类加载器(用于加载核心类库)
* 扩展类加载器(用于加载扩展类库)
* 应用类加载器(用于加载自己写的类或第三方jar包中的类)
* 自定义类加载器
*
* 2、Class<?>[] interfaces:指定代理对象要实现的接口
* 这个参数用于保证代理对象和目标对象有相同的方法列表
*
* 3、InvocationHandler invocationHandle:指定调用处理器
* 该处理器设置了代理对象实现的接口的方法被调用时,该如何执行
*/
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); }
}

9.6.2、测试

    @Test
public void testDynamicProxy(){ //根据目标对象来创建(动态)代理对象的工厂
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl()); //通过(动态)代理对象的工厂,生成目标对象所对应的(动态)代理对象
//因为代理类是动态生成的,所以不确定代理类的类型,因此用其所实现的接口类型
Calculator poxy = (Calculator) proxyFactory.getPoxy(); //调用动态代理对象的方法,该方法是目标对象核心业务方法的增强方法
int addResult = poxy.add(1, 2);
System.out.println(addResult); }

9.6.3、增强的位置

除了可以在调用目标对象执行功能之前或之后,加入额外的操作之外;

还可以在调用目标对象执行功能发生异常时(catch位置)或在调用目标对象执行功能完毕时(finally位置),加入额外的操作

也就是说,(静态或动态)代理能增强的位置一共有四个

            //通过invoke方法来统一管理代理类中的方法该如何执行,该方法有三个参数
/**
* @param proxy:表示代理对象
* @param method:表示要执行的方法
* @param args:表示要执行的方法的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//第1个增强位置:在调用目标对象执行功能之前,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + Arrays.toString(args)); //固定写法:调用目标对象实现的核心逻辑(最重要的步骤)
result = method.invoke(target, args); //第2个增强位置:在调用目标对象执行功能之后,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + result);
} catch (Exception e) {
//第3个增强位置:在调用目标对象执行功能发生异常时,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+",异常:"+e.getMessage());
} finally {
//第4个增强位置:在调用目标对象执行功能完毕时,加入额外的操作(这里是附加日志功能)
System.out.println("[日志] "+method.getName()+",方法执行完毕");
} //固定写法:保证代理对象和目标对象的返回值一致
return result;
}

9.6.4、扩展知识

  • 动态代理有两种方式:jdk动态代理(本示例)和cglib动态代理

  • jdk动态代理,要求目标必须实现接口,而且只能对目标所实现的接口方法进行增强

  • jdk动态代理,生成的代理类在com.sun.proxy包下,类名为:$proxy+数字

  • cglib动态代理,不要求目标必须实现接口,生成的代理类会继承目标类,并且和目标类在相同的包下

  • 虽然在实际中很少写动态代理的代码,但了解动态代理的思想,对学习Spring的AOP知识很有帮助

9、Spring之代理模式的更多相关文章

  1. Spring AOP /代理模式/事务管理/读写分离/多数据源管理

    参考文章: http://www.cnblogs.com/MOBIN/p/5597215.html http://www.cnblogs.com/fenglie/articles/4097759.ht ...

  2. Spring增强代理模式

    1. 依赖注入;(掌握) 2. XML自动注入;(掌握) 3. 全注解配置;(掌握) 4. 代理模式;(掌握,难点) 依赖注入 构造参数注入 constructor-arg:构造器注入: index: ...

  3. Java进阶知识20 Spring的代理模式

    本文知识点(目录): 1.概念  2.代理模式      2.1.静态代理      2.2.动态代理      2.3.Cglib子类代理 1.概念 1.工厂模式  2. 单例模式 代理(Proxy ...

  4. Spring设计模式——代理模式[手写实现JDK动态代理]

    代理模式 代理模式(Proxy Pattern):是指为其他对象提供一种代理,以控制对这个对象的访问. 代理对象在客户端和目标对象之间起到中介作用,代理模式属于结构型设计模式. 使用代理模式主要有两个 ...

  5. Spring AOP代理模式

    代理模式 代理模式是一种设计模式,提供了对目标对象的另外的访问方式.即通过代理访问目标对象. 好处:可以再目标对象实现的基础上,增加额外的功能的操作.扩展目标对象的功能,而不改变现有的功能逻辑. 1. ...

  6. spring的代理模式

    静态代理: 首先定义一个接口,随便写一个方法 定义2个实现接口的方法 (被代理的对象) (代理对象) 需要将接口 定义get set 方法 代理增强的方法 然后实现 输出结果如下: 动态代理(jdk动 ...

  7. Spring的代理模式(静态,JDK,CGLIB)

    一.静态代理   1.定义业务接口 public interface Subject { void doSomeThing(); }   2.真实业务类实现接口 public class RealSu ...

  8. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  9. &lt;四&gt;读&lt;&lt;大话设计模式&gt;&gt;之代理模式

    代理模式我想大家即便不熟悉也都听过吧,从字面意思上看就是替别人干活的,比方代理商.在项目的实际应用中也有非常多地方用到.比方spring通过代理模式生成对象等. 代理模式的书面定义:为其它对象提供一种 ...

  10. Spring代理模式及AOP基本术语

    一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...

随机推荐

  1. SQL Server:User, group, or role 'iemis' already exists in the current database.

    --最新的解决方法 --先创建用户帐户,不进行授权,然后通过下面的SQL语句将该用户帐户关联至对应的数据库用户.优点是避免了重新授权的操作. USE tempdbEXEC sp_change_user ...

  2. FPGA输出时钟jitter

    If customer performs simple clock forwarding from clock source -> FPGA clock input -> FPGA clo ...

  3. django之drf(部分讲解)

    序列化类常用字段和字段参数 drf在Django字段类型的基础上派生了自己的字段类型以及字段参数 序列化器的字段类型用于处理原始值和内部数据类型直接的转换 还可以用于验证输入.以及父对象检索和设置值 ...

  4. SignalR WebSocket通讯机制

    1.什么是SignalR ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化向应用程序添加实时 Web 功能的过程. 实时 Web 功能是让服务器代码在可用时立即将内容推 ...

  5. Vue cli3 整合SuperMap巧遇js异步加载的坑

    最近使用到superMap做三维地图,而项目又分为可视化大屏与后台管理系统两部分,所以项目配置了多入口,然引入cesium依赖就成了问题,在vue cli3 整合Cesium,处理build 时内存溢 ...

  6. 读文献先读图——主成分分析 PCA 图

    上周五彩斑斓的气泡图 有让你眼花缭乱吗? 本周,化繁为简的PCA图 你值得拥有!  数据分析| 科研制图﹒PCA 图 关键词:主成分分析.降维 1665 年的鼠疫 牛顿停课在家提出了万有引力 ;183 ...

  7. API NEWS | Money Lover爆出潜在API漏洞

    欢迎大家围观小阑精心整理的API安全最新资讯,在这里你能看到最专业.最前沿的API安全技术和产业资讯,我们提供关于全球API安全资讯与信息安全深度观察. 本周,我们带来的分享如下: Money Lov ...

  8. Helm实战案例二:在Kubernetes(k8s)上使用helm安装部署日志管理系统EFK

    目录 一.系统环境 二.前言 三.日志管理系统EFK简介 四.helm安装EFK 4.1 helm在线安装EFK 4.2 helm离线安装EFK(推荐) 五.访问kibana 5.1 数据分片 六.卸 ...

  9. The content of element type “web-app“ must match 解决方法

    报错原因 ‍ 问题描述 : 在创建 SpringMVC 时 , 选用 idea 的 webapp 模板来创建 , xml 配置文件中进行配置时发现提示警告 警告如下: ‍ ​ ​ 这错误大概的意思就是 ...

  10. 使用部分写时复制提升Lakehouse的 ACID Upserts性能

    使用部分写时复制提升Lakehouse的 ACID Upserts性能 译自:Fast Copy-On-Write within Apache Parquet for Data Lakehouse A ...