Java中的常用的代理模式

news2025/1/12 21:38:03

本文介绍在Java种常用的3种动态代理。
代理模式是23种模式中的一种,属于结构型设计模式。这种模式的作用就是要创建一个中间对象(相当于中介或者代理对象),通过操作中间对象来间接调用目的对象的方法,字段等,真正做到0接触,而且还可以提供额外的服务(比如增加日志,修改请求中的参数等等)Spring框架中的AOP就使用了代理模式,这是符合OCP开闭原则的:在尽量不改动原有代码的基础上,增加新的功能,使得代码的扩展性更强。
代理模式在代码实现上有2种方式:

  1. 静态代理
  2. 动态代理

1. 静态代理

假如现在有一个业务需求:在不改变原有代码的情况下,给业务方法加上日志记录。此时该怎么办呢?
这里我们以代码演示静态代理:
一个接口类 :BusinessService

package com.guitarzheng.proxy;

/**
 * @title: 业务Service
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public interface BusinessService {

    /**
     * 正常的业务方法
     */
    void doBusiness();

}

一个目标类:BusinessServiceImpl

package com.guitarzheng.proxy.impl;

import com.guitarzheng.proxy.BusinessService;

/**
 *  业务Service实现类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class BusinessServiceImpl implements BusinessService {

    @Override
    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }

}

使用静态代理的方式解决:
此时,我们再添加一个测试类,用于测试:****

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.impl.BusinessServiceImpl;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {
        BusinessService businessService = new BusinessServiceImpl();
        businessService.doBusiness();
    }

}

此时执行上面的main方法,控制台结果如下:
在这里插入图片描述
但是如何解决增加日志的需求呢???
答:增加一个代理类同时和目标类实现同一个接口:BusinessServiceImplStaticProxy

package com.guitarzheng.proxy.impl;

import com.guitarzheng.proxy.BusinessService;

/**
 * @title: 静态代理
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class BusinessServiceImplStaticProxy implements BusinessService {

    @Override
    public void doBusiness() {
        System.out.println("---记录日志---");
        System.out.println("---业务逻辑执行片段---");
    }

}

对静态代理类进行测试:
在这里插入图片描述
结果如下:
在这里插入图片描述
满足业务需求!
但是,缺点也很明显:如果系统中的业务类和方法很多,那么我们要写很多代理类和方法,会出现类爆炸,这显然是我们不能接受的。怎么解决这个问题呢?此时要使用我们下来介绍的动态代理的技术。

2. 动态代理

动态代理为什么叫动态代理呢?它和静态代理有什么区别?
动态代理最重要的特点就是代理类是在内存中动态生成的,不需要我们手动new。

2.1 jdk 动态代理

jdk动态代理是Java官方自带的一种代理机制,jdk动态代理只能代理接口
下面我们演示如何使用jdk动态代理(这里接口和目标类还是上面给出的):
调用处理器类:JdkDynamicProxyLogHandler

package com.guitarzheng.proxy.handler;

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

/**
 * @title: 专门用于记录日志的jdk动态代理调用处理器
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class JdkDynamicProxyLogHandler implements InvocationHandler {

    // 目标对象
    private Object target;

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

    /**
     * 该方法由jdk负责调用,调用invoke方法执行目标对象的方法并对方法进行增强(本例中增加了记录日志的功能)
     * @param proxy 代理对象,此参数使用较少
     * @param method 目标对象上的目标方法,比如代理类本次调用目标对象的doBusiness方法,那么doBusiness方法就是目标方法
     * @param args 目标方法上的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---记录日志---【jdk动态代理方式】");
        method.invoke(target, args);
        // 可以返回null,也可以返回其它值
        return null;
    }

}

问:为什么要在JdkDynamicProxyLogHandler类中定义一个目标类的成员变量?
答: 首先我们定义的日志调用处理器实现了InvocationHandler接口,这是jdk动态代理要求的:如果要使用jdk的动态代理机制,必须要自定义调用处理器必须要实现InvocationHandler接口,重写invoke方法。在invoke方法中,代码:

method.invoke(targer, args);

该行代码表示的意思是执行目标对象的目标方法,此时我们需要目标对象,因此需要从外界传入,这里我们使用成员属性定义,在new代理类时将目标对象传入即可达到目的。
测试代码:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.handler.JdkDynamicProxyLogHandler;
import com.guitarzheng.proxy.impl.BusinessServiceImpl;

import java.lang.reflect.Proxy;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {

        // jdk动态代理
        // 1. 创建目标对象
        BusinessService target = new BusinessServiceImpl();
        // 2. 创建代理对象
        BusinessService targetProxy = (BusinessService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkDynamicProxyLogHandler(target));
        targetProxy.doBusiness();

    }

}

控制台输出结果如下:
在这里插入图片描述
针对步骤2创建代理对象做一些说明:
Proxy类本身提供了创建代理类的方法:newProxyInstance(ClassLoader loader, @NotNull Class<?> interfaces, @NotNull InvocationHandler h),实际上,该方法执行后,会在内存中生成一个代理类的字节码数据,然后通过类加载器(这里要求代理类的类加载器和目标类的类加载器必须是同一个)将对象加载到java虚拟机,完成代理类对象的创建工作。
参数说明:

  1. ClassLoader loader:加载目标类的类加载器,jdk动态代理要求代理类的类加载器和目标类的类加载器必须是同一个类加载器;
  2. @NotNull Class<?> interfaces:目标类实现的接口;
  3. @NotNull InvocationHandler h:调用处理器,是一个接口类,增强功能的代码就写在这里!

2.2 cglib 动态代理

cglib作为一种动态代理的实现方式,功能就比较强大。它既可以代理接口,也可以代理类,底层采用继承的方式实现,所以被代理的目标类不能使用关键字final修饰。
由于cglib是第三方提供的技术,使用之前要引入依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

注意:若项目不是maven项目,导入jar包即可,现在大多数项目都是用maven管理jar包,因此导入步骤这里省略。
本例中演示代理类的方式实现动态代理。
准备一个类:BusinessServiceImpl2

package com.guitarzheng.proxy.impl;

/**
 * @title: 普通的业务类
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class BusinessServiceImpl2 {
    
    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }
    
}

创建回调接口:CglibLogMethodInterceptor

package com.guitarzheng.proxy.interceptor;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @title: cglib动态代理日志回调接口拦截器
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class CglibLogMethodInterceptor implements MethodInterceptor {

    /**
     *
     * @param target 目标对象(代理对象)
     * @param method 目标方法(代理对象中的某个方法)
     * @param objects 目标方法参数
     * @param methodProxy 目标方法的代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("---记录日志---【cglib动态代理方式】");
        methodProxy.invokeSuper(target, objects);
        return null;
    }

}

使用clib动态代理方式在内存中动态的生成BusinessServiceImpl2的代理类,并创建代理类的对象:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.impl.BusinessServiceImpl2;
import com.guitarzheng.proxy.interceptor.CglibLogMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main(String[] args) {

        // cglib动态代理
        // 1. 创建字节码增强器(这里的enhancer就是代理类,但是它不能直接调用目标对象的方法,可以通过它创建目标对象的的子类或者目标类接口的实现类)
        Enhancer enhancer = new Enhancer();
        // 2. 设置代理类的父类(告诉代理类要代理哪个目标类,就把哪个目标类设置为父类)
        enhancer.setSuperclass(BusinessServiceImpl2.class);
        // 3. 设置回调接口(非常重要:对目标类的增强功能就写在回调接口中)
        enhancer.setCallback(new CglibLogMethodInterceptor());
        // 4. 创建class字节码文件并加载到jvm,生成代理对象(这个代理对象是目标对象的子类)
        BusinessServiceImpl2 businessServiceImpl2Proxy = (BusinessServiceImpl2) enhancer.create();
        // 5. 使用代理类
        businessServiceImpl2Proxy.doBusiness();

    }

}

在这里插入图片描述

注意:若项目不是maven项目,只引入cglib依赖,会出现以下错误:
在这里插入图片描述
cglib依赖asm的jar包,因此我们还要导入asm的jar包。
若项目是maven,则不会出现上述问题,因为在引入cglib依赖时,会自动帮我们引入和cglib关联的jar包。

2.3 Javassist 动态代理

我们已经知道,Java源代码经过编译之后生成class文件,而class文件中存储的正是二进制的Java字节码,jvm启动时依靠类加载器将Java字节码加载到jvm中,完成对象的初始化工作。Javassist就是一个用来处理Java字节码的类库。 它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,同时也可以通过完全手动的方式生成一个新的类对象。
声明一个业务类:BusinessServiceImpl3

package com.guitarzheng.proxy.impl;

/**
 * @title: 普通的业务类
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class BusinessServiceImpl3 {

    public void doBusiness() {
        System.out.println("---业务逻辑执行片段---");
    }

}

和jdk动态代理差不多,Javassist也需要一个回调接口处理器:JavassistProxyLogHandler

package com.guitarzheng.proxy.handler;

import javassist.util.proxy.MethodHandler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @title: Javaassist回调接口处理器
 * @author: devinChen
 * @date: 2023/1/11
 * @version: v1.0.0
 */
public class JavassistProxyLogHandler implements MethodHandler {

    /**
     *
     * @param target 目标类
     * @param thisMethod 目标方法
     * @param proceed 代理方法(用来代理目标方法)
     * @param args 目标方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object target, Method thisMethod, Method proceed, Object[] args) throws Throwable {
    	// 增强代码
        System.out.println("---记录日志---【Javassist动态代理方式】");
        // 原业务方法放行
        proceed.invoke(target, args);
        // 若需要返回值,可以返回
        return null;
    }

}

生成代理类并调用方法:

package com.guitarzheng.proxy;

import com.guitarzheng.proxy.handler.JavassistProxyLogHandler;
import com.guitarzheng.proxy.impl.BusinessServiceImpl3;
import com.guitarzheng.proxy.interceptor.CglibLogMethodInterceptor;
import javassist.ClassPool;
import javassist.util.proxy.ProxyFactory;

/**
 * @title: 代理测试类
 * @author: devinChen
 * @date: 2023/1/10
 * @version: v1.0.0
 */
public class ProxyTest {

    public static void main (String[] args) throws InstantiationException, IllegalAccessException {
          
        // Javassist动态代理
        //1.创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //2.设置父类
        proxyFactory.setSuperclass(BusinessServiceImpl3.class);
        //3.设置回调处理器(具体的增强功能写在这里)
        proxyFactory.setHandler(new JavassistProxyLogHandler());
        //4.创建代理类的Class实例
        Class proxyClass = proxyFactory.createClass();
        //5.创建代理对象
        BusinessServiceImpl3 businessServiceImpl3Proxy = (BusinessServiceImpl3) proxyClass.newInstance();
        //6.使用代理对象调用业务方法
        businessServiceImpl3Proxy.doBusiness();
    }

}

运行结果如下:
在这里插入图片描述
实际上,Javassist也可以之间创建一个新的类,并给类设置属性和方法等。具体可参考这里:https://www.jb51.net/article/205638.htm

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

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

相关文章

Everything搜索知识总结

1.只知道那个文件以 .txt结尾 .*\.txt$ ($表示以什么结尾) 2.搜索某个路径下的文件 D:\ configure.bat (搜索D盘下的该文件,注意要用这种类型的"\",和被搜索的文件之间有空格;要先打出路径,再打出搜索文件.) 3.搜索指定路径下的多个文件 路径\ 文件1 | …

Halcon亚像素边缘缺陷检测案例

一、下面的案例是总结的Halcon边缘缺陷检测的一种情况。本案例是利用阈值分割获取金属区域&#xff0c;并利用boundary和edges_sub_pix获取到亚像素边缘。然后综合利用fit_rectangle2_contour_xld拟合出金属对应的放射矩形&#xff0c;最后利用dist_rectangle2_contour_points_…

【小白课程】openKylin用户手册原理解析,一招教你学会自定义!

openKylin用户手册是详细描述openKylin操作系统的功能和用户界面&#xff0c;让用户了解如何使用该软件的说明书。通过阅读openKylin用户手册&#xff0c;能够更快更好的上手和使用openKylin操作系统。今天就带大家简单了解下openKylin用户手册的实现原理以及如何自定义用户手册…

用EditPlus编译Fortran

一、EditPlus配置 语法点亮 安装好EditPlus后&#xff0c;点击Tool->Prefenrences&#xff0c;在File->Setting&syntex下&#xff0c;点击Add按钮&#xff0c;填Frotran。 到EditPlus官网上 EditPlus - User Files (other files) 下载Fortran语法文件 ​ 二、配置…

设计模式学习(四):Strategy策略模式

一、什么是Strategy模式 Strategy的意思是“策略”&#xff0c;指的是与敌军对垒时行军作战的方法。在编程中&#xff0c;我们可以将它理解为“算法”。无论什么程序&#xff0c;其目的都是解决问题。而为了解决问题&#xff0c;我们又需要编写特定的算法。使用Strategy模式可以…

Redis- 主从复制原理

1、概述 Master节点在平时提供服务&#xff0c;另外一个或多个Slave节点在平时不提供服务&#xff08;或只提供数据读取服务&#xff09;。当Master节点由于某些原因停止服务后&#xff0c;再人工/自动完成Slave节点到Master节点的切换工作&#xff0c;以便整个Redis集群继续向…

Spring依赖注入源码分析

1. 前言 Spring的核心之一就是依赖注入&#xff0c;Spring提供了Autowired注解来给bean注入依赖。除了注入最基本的bean之外&#xff0c;Spring还做了一些扩展&#xff0c;例如你可以注入Optional&#xff0c;以此来判断依赖的bean是否存在&#xff1b;你还可以注入Map来获得所…

Leetcode:617. 合并二叉树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&am…

leetcode 399. 除法求值-java题解

题目所属分类 flod最短路算法 原题链接 给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件&#xff0c;其中 equations[i] [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。 另有一些以数组 queri…

编译metabase

Linux Centos7 配置Metabase编译打包环境 安装Oracle JDK1.8&#xff08;如果已经安装&#xff0c;则可以省略此步骤&#xff0c;必须是Oracle JDK&#xff09; 在线下载Oracle JDK 1.8 将下载好的tar包放入linux目录下 2、解压tar进行安装 tar -zxvf jdk-8u212-linux-x64.t…

SSL/TLS协议信息泄露漏洞(CVE-2016-2183)

最近服务器扫描出SSL/TLS协议信息泄露漏洞(CVE-2016-2183) TLS是安全传输层协议&#xff0c;用于在两个通信应用程序之间提供保密性和数据完整性。 TLS, SSH, IPSec协商及其他产品中使用的DES及Triple DES密码存在大约四十亿块的生日界&#xff0c;这可使远程攻击者通过Sweet…

总结几个常用的Git命令的使用方法

目录 1、Git的使用越来越广泛 2、设置Git的用户名和密码并查看 3、建立自己的 Git 仓库 4、将自己的代码提交到远程 (origin) 仓库 5、同步远程仓库的更新到本地仓库 6、分支管理 7、获取远程仓库的内容 1、Git的使用越来越广泛 现在很多的公司或者机构都在使用Git进行项目和代…

Elasticsearch基础1——搜索引擎发展史和工作流程、es\es-head\kibana的基础安装

文章目录一、搜索引擎1.1 搜索引擎的发展背景1.2 Lucene和Elasticsearch1.3 Solr和Elasticsearch对比1.4 数据搜索方式1.5 搜索引擎1.5.1 搜索引擎工作流程1.5.2 网络爬虫原理流程1.5.3 网页分析1.5.4 正排索引和倒排索引二、Elasticsearch基础安装1.2 概述简介2.2 安装2.2.1 W…

tensorflow算子注册以及op详解

在自定义的算子时&#xff0c;经常遇到一些函数和宏&#xff0c;这里介绍一下常见的函数和宏 REGISTER_OP 首先我们来思考REGISTER_OP的作用是什么&#xff1f;当我们定义一个tensorflow的算子&#xff0c;首先我们需要tensorflow知道这个算子&#xff0c;也就是说我们要把这…

WeLink的使用

我这里是注册的企业端 流程>手机号验证码 注册成功后登陆 进入首页面 按操作逐步完成信息需求 因个体使用情况不同 在角色分类和组织架构中可根据自己部门或单位的分工分类 【拉人】&#xff1a; 三种方式 主要就是网址超链接和企业码 前提需要用户先注册 【加入审核】是根…

Nginx——反向代理解决跨域问题(Windows)

这个破玩意是真麻烦&#xff0c;必须写一篇文章避避坑了。一、先看看大佬的解释&#xff0c;了解反向代理和跨域问题吧&#xff1a;Nginx反向代理什么是跨域问题二、OK&#xff0c;直接开工&#xff0c;装Nginx下载地址: http://nginx.org/en/download.html如图所示, 选择相应的…

Flink多流转换(Flink Stream Unoin、Flink Stream Connect、Flink Stream Window Join)

文章目录多流转换1、分流操作1.1、在flink 1.13版本中已弃用.split()进行分流1.2、使用&#xff08;process function&#xff09;的侧输出流&#xff08;side output&#xff09;进行分流2、基本合流操作2.1、联合&#xff08;Flink Stream Union&#xff09;2.2、连接&#x…

【Go】实操使用go连接clickhouse

前言 近段时间业务在一个局点测试clickhouse&#xff0c;用java写的代码在环境上一直连接不上clickhouse服务&#xff0c;报错信息也比较奇怪&#xff0c;No client available&#xff0c;研发查了一段时间没查出来&#xff0c;让运维这边继续查&#xff1a; 运维同学查了各种…

OAuth 2.0授权框架详解

简介 在现代的网站中&#xff0c;我们经常会遇到使用OAuth授权的情况&#xff0c;比如有一个比较小众的网站&#xff0c;需要用户登录&#xff0c;但是直接让用户注册就显得非常麻烦&#xff0c;用户可能因为这个原因而流失&#xff0c;那么该网站可以使用OAuth授权&#xff0…

FactoryBean和BeanFactory的区别

1. 前言 “BeanFactory和FactoryBean的区别是什么&#xff1f;&#xff1f;&#xff1f;” 这是Spring非常高频的一道面试题&#xff0c;BeanFactory是Spring bean容器的顶级接口&#xff0c;负责创建和维护容器内所有的bean对象。而FactoryBean是用来创建一类bean的接口&…