首页 > 编程语言 >Java代理模式初探

Java代理模式初探

时间:2023-08-23 16:32:12浏览次数:45  
标签:Java 代理 初探 fun import IUserProvider com public

代理模式是一种常见的软件设计模式。所谓的代理者是指一个类别可以作为其它对象的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它重要或无法复制的资源。

有时候我们需要需要一个功能(通常是一个API)。该功能已经被某个类A实现了,代理类B实现相同的接口,并将操作交给A去处理,在这个过程中可以添加自己的功能。

之前只是听过一些说法,并未对Java代理进行学习,前些日子抽空学习了一点点,下面分享我在Java使用代理模式的一些Demo,主要分三类:静态代理JDK动态代理cglib动态代理

创建一个简单的代理

我们从一个接口IUserProvider开始:

package com.fun.ztest.proxytest;

public interface IUserProvider {

    User getUser(int i);
    
}

此接口由UserProviderImpl实现:

package com.fun.ztest.proxytest;

import com.fun.frame.SourceCode;
import com.fun.utils.RString;

public class UserProviderImpl extends SourceCode implements IUserProvider {

    @Override
    public User getUser(int i) {
        output("我是原配实现类!");
        return new User(RString.getString(i), getRandomInt(i), null);
    }


}

现在让我们为IUserProvider添加一个代理对象,该对象执行一些简单的日志记录:

package com.fun.ztest.proxytest;

import com.fun.frame.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogProxy extends SourceCode implements IUserProvider {

    private static Logger logger = LoggerFactory.getLogger(LogProxy.class);

    private IUserProvider userProvider;

    public LogProxy(IUserProvider userProvider) {
        this.userProvider = userProvider;
    }

    @Override
    public User getUser(int i) {
        logger.info("获取用户{}", i + EMPTY);
        return userProvider.getUser(i);
    }


}

我们要为IUserProvider创建代理,因此我们的代理需要实现IUserProvider。在构造函数中,AutoLogProxy类需要传入一个实际的IUserProvider实现类UserProviderImpl对象。然后在getUser方法中添加了一行日志,最终的操作还是交给IUserProvider实现类UserProviderImpl对象去完成。

要使用我们的代理,我们必须更新初始化代码:

public static void main(String[] args) {
        IUserProvider userProvider = new UserProviderImpl();
        LogProxy logProxy = new LogProxy(userProvider);
        logProxy.getUser(12);
    }

控制台输出

INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 获取用户12
INFO-> 我是原配实现类!

Process finished with exit code 0

手动代理创建的问题

这个解决方案有一个主要缺点:代理实现绑定到IUserProvider接口,因此很难重用。

代理逻辑通常很通用。代理的典型用例包括缓存、远程对象或延迟加载。

但是,代理需要实现特定的接口(及其方法),这与可重用性矛盾。

JDK动态代理

JDK提供了针对此问题的标准解决方案,称为Dynamic Proxies。使用动态代理,我们可以在运行时为特定接口创建实现。在此生成的代理上的方法调用被委派给InvocationHandler

下面是代理类的代码:

package com.fun.ztest.proxytest;

import com.fun.frame.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class AutoLogProxy extends SourceCode implements InvocationHandler {

    private static Logger logger = LoggerFactory.getLogger(AutoLogProxy.class);

    private final Object drive;

    public AutoLogProxy(Object invocationTarget) {
        this.drive = invocationTarget;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.warn("调用方法:{},参数:{}",method.getName(), Arrays.toString(args));
        return method.invoke(drive, args);
    }


}

下面是使用方法:

    public static void main(String[] args) {
        IUserProvider userProvider = new UserProviderImpl();
        IUserProvider provider = (IUserProvider) Proxy.newProxyInstance(IUserProvider.class.getClassLoader(), new Class[]{IUserProvider.class}, new AutoLogProxy(userProvider));
        provider.getUser(10);
    }

控制台输出

INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
WARN-> 调用方法:getUser,参数:[10]
INFO-> 我是原配实现类!

Process finished with exit code 0


使用Proxy.newProxyInstance,我们创建一个新的代理对象。此方法采用三个参数:

  • 应该使用的类加载器
  • 代理应实现的接口列表
  • 一个InvocationHandler接口实现

invoke方法有三个参数:

  • 调用了方法的代理对象
  • 被调用的方法
  • 已传递给调用方法的参数列表

代理创建(和接口实现)和代理逻辑(通过InvocationHandler)的分离支持可重用性。注意,我们在InvocationHandler实现中对IUserProvider接口没有任何依赖。在构造函数中,我们接受通用对象。这为我们提供了将InvocationHandler实现重用于不同接口的功能。

动态代理的局限性

动态代理始终需要一个接口。我们不能基于抽象类创建代理。

如果这确实是个大问题,那么可以查看字节码操作库cglibcglib能够通过子类化创建代理,因此能够在不需要接口的情况下为类创建代理。

cglib实现动态代理

这里我创建了一个新的User对象,写了两个方法:

package com.fun.ztest.proxytest;

import com.fun.frame.SourceCode;

public class CUser extends SourceCode {

    public void fun() {
        output(DEFAULT_STRING);
    }

    public void tester() {
        output(DEFAULT_CHARSET.name());
    }

}

下面是代理的实现类:

package com.fun.ztest.proxytest;

import com.fun.frame.SourceCode;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Arrays;

public class CUserProxy extends SourceCode implements MethodInterceptor {

    private static Logger logger = LoggerFactory.getLogger(CUserProxy.class);

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("调用方法:{},参数:{}", method.getName(), Arrays.toString(args));
        proxy.invokeSuper(obj, args);
        return obj;
    }


}

下面是使用Demo

    public static void main(String[] args) {
        CUserProxy proxy = new CUserProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CUser.class);
        enhancer.setCallback(proxy);
        CUser user = (CUser) enhancer.create();
        user.fun();
    }

控制台输出


INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 调用方法:fun,参数:[]
INFO-> FunTester

Process finished with exit code 0

结论

代理模式可能非常强大,我们可以添加功能而无需修改实际的代码。代理通常用于向现有类中添加一些通用功能。使用动态代理,我们可以将代理创建与代理实现分开,具备重用性和灵活性多种优点。

标签:Java,代理,初探,fun,import,IUserProvider,com,public
From: https://blog.51cto.com/FunTester/7204380

相关文章

  • Java简单实现大数相加
    Java简单实现大数相加publicclassBigNumberAdd{/***假设两个都是正数*@paramnum1*@paramnum2*@returnnum1+num2*/publicstaticStringadd(Stringnum1,Stringnum2){intm=num1.length();intn=......
  • java 读取各种类型的文件 (三)
    后端java,springboot、前端vue:对txt文件的读写,以及前端预览一、后端读publicvoidreadTxt(){InputStreamReaderinput=null;BufferedReaderbuffer_reader=null;ArrayBlockingQueue<String[]>fileData=new......
  • java 读取各种类型的文件 (二)
    1、Filefile=newFile();参数可以是文件夹路径,也可以是文件路径;当newFile()之后只是先在内存中创建了File对象,还没有在磁盘上创建具体文件文件夹;如果参数中的文件或者文件夹已经存在的话,那么File的属性就按已存在的文件或者文件夹赋值;如果不存在则除了文件名......
  • 如何在Linux中使用SSH动态端口转发代理
    本文将介绍如何在Linux中使用SSH动态端口转发代理,让你轻松畅享网络自由,保障个人隐私和数据安全。SSH(SecureShell)是一种加密的远程登录协议,广泛用于提供安全的远程访问和数据传输。而动态端口转发代理,则是利用SSH的强大功能,将本地机器的网络流量通过SSH连接转发到远程服务器,实现访......
  • 爬虫之争:Python还是Java?
    在爬虫的世界里,选择合适的编程语言是关键之一。但是面对众多选择,你是选择Python还是Java呢?本文将为你分析Python和Java在爬虫领域的优势与劣势,帮助你做出选择。PythonPython作为一种高级编程语言,简洁明了的语法让人爱不释手。以下是Python在爬虫领域的优势:1.简洁易学:Python语法简单......
  • 【python】python开源代理ip池
    一、前言随着互联网的不断发展,越来越多的应用需要使用高匿代理IP才能访问目标网站,而代理IP作为一种能够隐藏本机真实IP地址的工具,在网络抓取、搜索引擎排名、广告投放、反爬虫等方面有着广泛的应用场景。但是,由于代理IP的稳定性难以保证,而且容易被反爬虫机制识别和封杀,为解决这些......
  • Java 获取Html标签中各属性值
    该方式适用于取任何标签的任何属性只要对应替换标签名和属性。publicstaticSet<String>getImgStr(StringhtmlStr){Set<String>pics=newHashSet<String>();Stringimg="";Patternp_image;Matcherm_image;StringregEx_img="<......
  • java-将列表格式化成json字符串
    List<String>list=newArrayList<>();list.add("{'county':'china','age':18}");list.add("{'county':'japan','age':28}");......
  • 代理IP与Socks5代理:跨界电商智能爬虫的引擎与安全壁垒
    一、代理IP:智能爬虫的引擎多地区数据采集: 代理IP允许企业模拟不同地区的IP地址,轻松实现多地区数据采集。这为企业洞察全球市场需求提供了重要数据支持。规避反爬虫机制: 代理IP通过随机切换IP地址,规避了网站的反爬虫机制,确保数据采集的稳定性和连续性。突破地理封锁: 代理IP使企业......
  • 代理IP与Socks5代理:跨界电商智能爬虫的引擎与安全壁垒
    一、代理IP:智能爬虫的引擎多地区数据采集: 代理IP允许企业模拟不同地区的IP地址,轻松实现多地区数据采集。这为企业洞察全球市场需求提供了重要数据支持。规避反爬虫机制: 代理IP通过随机切换IP地址,规避了网站的反爬虫机制,确保数据采集的稳定性和连续性。突破地理封锁: 代理IP使企业......