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. Jenkins - 构建时运行Selenium打不开浏览器解决方法

    Jenkins-构建时运行Selenium打不开浏览器解决方法 前言 为了让Jenkins执行的Web自动化测试任务,能顺利调出浏览器页面,我们需要以命令行的方式启动Jenkins并执行脚本. 注:通 ...

  2. 【熊子q的代码乐园】用python写个健康报备记录小系统

    目录 一.前言 二.技术实现 1.概述 2. 环境 3. 技术核心 4. 数据库 5. 源代码 三.最后 一.前言 coding的一路上,遇到过许多问题,也写过一些代码去解决,回头看看还是有点意思的, ...

  3. Vulnhub Broken

    Vulnhub Broken 一.操作文档 [Vulnhub - Broken-Gallery writeup (mzfr.me)](https://blog.mzfr.me/vulnhub-writ ...

  4. 【实践篇】手把手教你落地DDD

    1. 前言 常见的DDD实现架构有很多种,如经典四层架构.六边形(适配器端口)架构.整洁架构(Clean Architecture).CQRS架构等.架构无优劣高下之分,只要熟练掌握就都是合适的架构. ...

  5. Java(数组使用、Arrays、稀疏数组)

    1.数组的使用 For-Each循环 int[] arrays = {1,2,3,4,5}; //打印全部的数组元素 JDK1.5 没有下标 for (int array : arrays) { Sy ...

  6. wait,notify,notifyAll,sleep,join等线程方法的全方位演练

    一.概念解释 1. 进入阻塞: 有时我们想让一个线程或多个线程暂时去休息一下,可以使用 wait(),使线程进入到阻塞状态,等到后面用到它时,再使用notify().notifyAll() 唤醒它,线 ...

  7. 杭电多校第二场 DOS Card

    杭电多校第二场 DOS Card 评价一下这道题:我写过最爽的线段树题. 这道题真的非常令人身心愉悦,非常厉害的一道线段树入门题.我写这个一次调试都没有,过了样例就交了就过了,一切都是行云流水. 这道 ...

  8. Unity 制作KinematicCharacterController

    本篇博客为游戏开发记录,博主只是想自己做个移动组件给自己做游戏用,此间产生的一些经验也做一个分享. 简介 为了在3D世界中自由的控制我们的角色,引擎一般会提供一些基础的移动组件,上层用户做提供一些每帧 ...

  9. MySQL存储之为什么要使用B+树做为储存结构?

    导言: 在使用MySQL数据库的时候,我们知道了它有两种物理存储结构,hash存储和B+树存储,由于hash存储使用的少,而B+树存储使用的范围就多些,如 InnoDB和MYISAM引擎都是使用的B+ ...

  10. GGTalk 开源即时通讯系统源码剖析之:虚拟数据库

    继上篇<GGTalk 开源即时通讯系统源码剖析之:服务端全局缓存>详细介绍了 GGTalk 对需要频繁查询数据库的数据做了服务端全局缓存处理,以降低数据库的读取压力以及加快客户端请求的响应 ...