代理 模式

news2024/11/27 14:28:12

代理模式

Proxy Pattern

为其他对象提供一个代理以控制对这个对象的访问

可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。

静态代理

直接写死的代码的代理逻辑

在这里插入图片描述

动态代理

动态的生成代理类。有JDK动态代理,CGLIB代理

在这里插入图片描述

JDK自带的动态代理 :

  • java.lang.reflect.Proxy , 作用:动态生成代理类和对象
  • java.lang.reflect.InvocationHandler(处理器接口) , 可以通过invoke方法实现对真实角色的代理访问。每次通过Proxy生成代理类对象时都要指定对应的处理器对象

动态代理相比于静态代理的优点
抽象角色中(接口)声明的所以方法都被转移到调用处理器一个集中的方
法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类处理远程方法调用(RMI)。
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。( 比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。)

开发框架中应用场景:

实际上,随便选择一个技术框架都会用到代理模式!

struts2中拦截器的实现;
数据库连接池关闭处理;
Hibernate中延时加载的实现;
AspectJ的实现 , spring中AOP的实现;
mybatis中实现拦截器插件;
日志拦截;
声明式事务处理;
web service;
RMI远程方法调用

核心角色:

  1. 抽象角色:定义代理角色和真实角色的公共对外方法;
  2. 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用,– 关注真正的业务逻辑!;
  3. 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 将统一的流程控制放到代理角色中处理!

静态代理

需要定义接口或父类,被代理对象和代理对象一起实现相同的接口或继承相同的父类

定义接口:IUserDao.java

public interface IUserDao {
    void save();
}

定义目标对象

package com.ung.designMode.proxy;

/**
 * @author: wenyi
 * @create: 2022/9/19
 * @Description:
 */
public class UserDao implements IUserDao {
    @Override
    public void save() {
        System.out.println("。。。。保存好了数据。。。。");
    }
}

定义代理对象,都实现相同的接口

可以在调用目标方法前后来进行代理操作,什么开启事务,提交事务等

package com.ung.designMode.proxy;

/**
 * @author: wenyi
 * @create: 2022/9/19
 * @Description:
 */
public class UserDaoProxy implements IUserDao {

    private IUserDao iUserDao;

    public UserDaoProxy(IUserDao iUserDao) {
        this.iUserDao = iUserDao;
    }

    @Override
    public void save() {
        //在这里可以定义调用前的执行操作
        System.out.println("调用前的操作代理");
        iUserDao.save();
        //在这里可以定义调用后的执行操作
        System.out.println("调用后的操作代理");
    }
}

测试

package com.ung.designMode.proxy;

import org.junit.Test;

import static org.junit.Assert.*;

public class UserDaoTest {
    @Test
    public void save() {
        //目标对象
        UserDao userDao = new UserDao();
        //代理对象,将目标对象传给代理对象,建立代理关系
        UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
        //执行的也是代理的方法
        userDaoProxy.save();
    }
}

在这里插入图片描述

这样的代理,可以实现不修改目标对象的功能,来进行功能的扩展增强;

但是这样有缺陷,都需要实现相同的接口,导致代理类很多,

如果接口的方法修改,呐需要变动的就很多,不易维护

可以使用动态代理,也叫 JDK代理,接口代理

JDK动态代理
  1. 代理对象可以不用实现接口

  2. 利用jdk的api来生成代理对象,动态的在内存中构建代理对象

    需要我们指定一个创建代理对象和目标对象实现的接口

在这里插入图片描述

总结:代理对象可以不用实现接口,但是目标对象要实现接口,否则不能使用动态代理

对之前的案例,相同的基础上,新建一个ProxyFactory的代理工厂类,在测试类中建立目标对象和代理对象的联系,最后调用代理对象的方法就可以

新建 ProxyFactory 代理类

package com.ung.designMode.proxy;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author: wenyi
 * @create: 2022/9/19
 * @Description: 代理类工厂
 */
public class ProxyFactory {
    //    维护一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//类加载器
                target.getClass().getInterfaces(),//目标类实现的接口
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("调用前的开启事务");
                        Object returnValue = method.invoke(target, args);
                        System.out.println("调用后的提交事务");
                        return returnValue;
                    }
                }
        );
    }
}

测试

package com.ung.designMode.proxy;

import org.junit.Test;

import static org.junit.Assert.*;

public class UserDaoTest {
    @Test
    public void save() {
        //目标对象
        UserDao userDao = new UserDao();
//        //代理对象,将目标对象传给代理对象,建立代理关系
//        UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
//        //执行的也是代理的方法
//        userDaoProxy.save();
        System.out.println("原始的目标对象"+userDao.getClass());
        //代理对象
        IUserDao proxy = (IUserDao) new ProxyFactory(userDao).getProxyInstance();
        System.out.println("代理对象"+proxy.getClass());
        proxy.save();
    }
}

在这里插入图片描述

idea中通过配置VM参数可以将动态生成的代理类的字节码文件保存到本地,方便观察分析代理类的结构。

JDK8及以前版本:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
JDK8以后版本:-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

在这里插入图片描述

可以看到由JDK代理实现的代理类 是通过实现接口,继承Proxy的单继承,默认继承了Proxy就不可以再继承其他类了

实现被代理类实现的接口方法,如果被代理的目标类有私有方法,那这样生成的代理类就不能对私有方法进行代理

CGLIB 动态代理

对类进行代理,code generator library 代码生成库,动态生成字节码对象;

创建代理拦截器

package com.ung.designMode.proxy;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author: wenyi
 * @create: 2022/9/19
 * @Description:
 */
public class LycCglibProxyInterceptor implements MethodInterceptor {
    // proxy:代理对象 method:目标对象中的方法 args:目标对象中的方法 methodProxy:代理对象中的代理方法对象
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行前");
        Object rtnObj = methodProxy.invokeSuper(o, objects);
        System.out.println("执行后");
        return rtnObj;
    }
}

测试

package com.ung.designMode.proxy;

import org.junit.Test;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;


public class LycCglibProxyInterceptorTest {

    @Test
    public void intercept() {
        //创建空的字节码对象
        Enhancer enhancer = new Enhancer();
        //设置字节码对象的父类就是目标类
        enhancer.setSuperclass(UserDao.class);
        //回调对象
        Callback callback =new LycCglibProxyInterceptor();
        //设置字节码对象的回调
        enhancer.setCallback(callback);
        //代理对象
        UserDao userDaoProxy = (UserDao) enhancer.create();
        //调用方法
        userDaoProxy.save();
    }
}

可以看到也成功代理

//保存生成的字节码文件
byte[] generate = enhancer.getStrategy().generate(enhancer);
FileOutputStream fileOutputStream = new FileOutputStream(
new File("C:\\个人文档\\learing\\lyccglibtest.class"));
fileOutputStream.write(generate);
fileOutputStream.flush();
fileOutputStream.close();

在这里插入图片描述

(queryCost 是被代理的方法)

可以看到 通过CGLIB动态代理生成的 代理类,是创建一个新的类继承被代理类,在重写了父类的方法,

在重写的方法里面调用的接口的intercept方法,就是queryCost 被代理的方法,在这里可以进行目标方法的增强;

这样通过子类重写父类的方式的代理,要求被代理的方法不能是final方法修改,否则子类无法重写进行代理;

总结:

  1. 如果目标对象实现了接口,默认会使用JDK的动态代理实现AOP

    底层依赖 java 反射实现增强,生成一个实现代理接口的代理类

    只能代理实现接口的目标类,代理类和目标类都要实现相同的接口

  2. 如果目标对象没有实现接口,那就必须用CGLIB动态代理

    通过生成一个目标类的子类,来重写目标方法实现代理增强,有拦截器拦截父类的方法调用,目标类不能用final修改方法和类,否则不能继承和重写方法;

Spring会再两者之间转换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

12.2、后渗透测试--令牌窃取

攻击机kali:192.168.11.106靶机windows server 2008 R2:192.168.11.134(包含ms17_010漏洞)一、令牌简介与原理 令牌(Token) 就是系统的临时密钥,相当于账户名和密码,用来决定是否允许这次请求和判断这次请求…

二进制搭建k8s——部署node节点

上篇:二进制搭建k8s——部署etcd集群和单master 二进制搭建k8s——部署node节点二进制搭建k8s——部署node节点环境部署node节点部署网络组件方法一:部署Flannel方法二:部署 CalicoCNI网络插件介绍Kubernetes的三种网络K8S 中 Pod 网络通信&a…

浅浅讲解下Linux内存管理之CMA

说明: Kernel版本:4.14ARM64处理器,Contex-A53,双核使用工具:Source Insight 3.5, Visio 1. 概述 Contiguous Memory Allocator, CMA,连续内存分配器,用于分配连续的大块内存。CMA…

c语言内存和文件处理有关知识

内存 分配内存的函数calloc&#xff0c;malloc 定义于头文件 <stdlib.h>功能malloc分配内存(函数)calloc分配并清零内存(函数)realloc扩充之前分配的内存块(函数)free归还还之前分配的内存(函数)aligned_alloc(C11)分配对齐的内存(函数) 函数原型 void *malloc(unsigne…

Java基础之Collection的ArrayList

Java基础之Collection的ArrayList一、add()与addAll()二、remove()三、trimToSize()1、案例一、add()与addAll() 跟C 的vector不同&#xff0c;ArrayList没有push_back()方法&#xff0c;对应的方法是add(E e)&#xff0c;ArrayList也没有insert()方法&#xff0c;对应的方法是…

Oracle---初学篇

Oracle初学篇 Oracle的启动&#xff0c;监听&#xff0c;用户 文章目录Oracle初学篇Oracle的启动Oracle的监听监听服务的主要文件1.listener.ora2.tnsnames.ora3.sqlnet.oraOracle用户Oracle安装成功后默认的三个用户创建用户Oracle的启动 之前写了关于如何在CentOS7上搭建Ora…

2021年全国研究生数学建模竞赛华为杯D题抗乳腺癌候选药物的优化建模求解全过程文档及程序

2021年全国研究生数学建模竞赛华为杯 D题 抗乳腺癌候选药物的优化建模 原题再现&#xff1a; 一、背景介绍   乳腺癌是目前世界上最常见&#xff0c;致死率较高的癌症之一。乳腺癌的发展与雌激素受体密切相关&#xff0c;有研究发现&#xff0c;雌激素受体α亚型&#xff0…

LeetCode 0547. 省份数量:图的连通分量

【LetMeFly】547.省份数量 力扣题目链接&#xff1a;https://leetcode.cn/problems/number-of-provinces/ 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a …

Windows文件夹开启大小写敏感

Windows 的文件系统的文件名&#xff0c;是大小写不敏感的&#xff0c;也就是你的文件名是 a.txt 或者 A.txt&#xff0c;在 Windows 中都是一视同仁&#xff0c;认为是同一个文件。 自从 Windows 10 引入 Linux 子系统&#xff08;WSL&#xff09;后&#xff0c;有越来越多开…

JAVA毕业设计——基于ssm的汽车租赁管理系统 (源代码+数据库)

代码地址 https://github.com/ynwynw/carRental-public 毕业设计所有选题地址 https://github.com/ynwynw/allProject 基于Springboot的汽车租赁管理系统 (源代码数据库)601 一、系统介绍 汽车租赁系统总共分为两个大的模块&#xff0c;分别是系统模块和业务模块。其中系统模…

基于混沌原子搜索优化的电力系统(HPS)负载频率自动控制(ALFC)(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

Spring Security 中的四种权限控制方式

Spring Security 中对于权限控制默认已经提供了很多了&#xff0c;但是&#xff0c;一个优秀的框架必须具备良好的扩展性&#xff0c;恰好&#xff0c;Spring Security 的扩展性就非常棒&#xff0c;我们既可以使用 Spring Security 提供的方式做授权&#xff0c;也可以自定义授…

如何实现外网访问API接口

Application Programming Interface 缩写为API&#xff0c;中文翻译为“应用程序接口”&#xff0c;是一些预先定义的函数&#xff0c;或指软件系统不同组成部分衔接的约定。目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&…

TVS管开关电源防护应用及电源防护元件的品类

瞬态抑制二极管简称TVS管。其作用原理是能够在极短的时间内承受反向电压的冲击&#xff0c;使得两极之间的电压钳位在特定电压水平上&#xff0c;有效避免了对后面电路的冲击&#xff0c;从而保护了被保护电子线路中的精密元件不受其损害。 瞬态抑制二极管TVS的钳位响应速度为为…

pytest + yaml 框架 -7.用例分层机制

前言 当我们测试流程类的接口&#xff0c;需反复去调用同一个接口&#xff0c;就会想到复用API&#xff0c;在代码里面可以写成函数去调用。 那么在yaml 文件中&#xff0c;我们可以把单个API写到一个yaml 文件&#xff0c;测试用例去调用导入API。 pip 安装插件 pip instal…

电子产品设计的流程有哪些

电子产品设计过程是指导工业外观设计的具体环节&#xff0c;主要包括产品市场需求分析、产品设计、产品原型设计、生产测试设计、大规模生产等方法和步骤。 一、电子产品设计流程是什么? 1.产品市场需求分析是电子产品设计成功的第一步&#xff0c;也是非常重要的一步。开发者…

JavaSE(数组)

1. 数组 数组创建及初始化 总结&#xff1a; 三种写法包括了动态初始化和静态初始化&#xff0c;其中省略格式不能再省略&#xff08;拆分&#xff09;&#xff1b;没有初始化时&#xff08;默认值为基类类型对应的默认值&#xff09;其中引用类型的默认值为null 三种写法 1…

RK3288-8.1-添加一个人体感应的开关-pir_pin_status

收到一个人体感应的需求,在设置添加一个开关 第一步,确定人体感应接口的GPIO 可知GPIO7_B5为对应GPIO 第二步,注册对应DTS 然后去驱动内注册对应的节点 很基础的东西,就不多说了,省略一万字......................(得到节点) cat sys/devices/platform/attr/pir_pin_status …

CAD尺寸标注不显示数值

在创建一个新的CAD图纸后&#xff0c;使用CAD尺寸标注会看见只有一条直线而没有数字显示&#xff0c;这是为什么呢&#xff1f;实际上是存在数值的&#xff0c;只是默认的数值很小&#xff0c;如果图形尺寸过大就看不清楚需要放大才能看见&#xff0c;就需要调整CAD尺寸标注的格…

RSS Can:借助 V8 让 Golang 应用具备动态化能力(二)

继续聊聊之前做过的一个小东西的踩坑历程&#xff0c;如果你也想高效获取信息&#xff0c;或许这个系列的内容会对你有用。 写在前面 在上一篇文章《RSS Can&#xff1a;使用 Golang 实现更好的 RSS Hub 服务&#xff08;一&#xff09;》里&#xff0c;我们简单介绍了使用 G…