前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java代理模式初探

Java代理模式初探

作者头像
FunTester
发布2021-01-05 10:00:33
3140
发布2021-01-05 10:00:33
举报
文章被收录于专栏:FunTester

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

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

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

创建一个简单的代理

我们从一个接口IUserProvider开始:

代码语言:javascript
复制
package com.fun.ztest.proxytest;

public interface IUserProvider {

    User getUser(int i);
    
}

此接口由UserProviderImpl实现:

代码语言:javascript
复制
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添加一个代理对象,该对象执行一些简单的日志记录:

代码语言:javascript
复制
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对象去完成。

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

代码语言:javascript
复制
public static void main(String[] args) {
        IUserProvider userProvider = new UserProviderImpl();
        LogProxy logProxy = new LogProxy(userProvider);
        logProxy.getUser(12);
    }

控制台输出

代码语言:javascript
复制
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

下面是代理类的代码:

代码语言:javascript
复制
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);
    }


}

下面是使用方法:

代码语言:javascript
复制
    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);
    }

控制台输出

代码语言:javascript
复制
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对象,写了两个方法:

代码语言:javascript
复制
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());
    }

}

下面是代理的实现类:

代码语言:javascript
复制
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

代码语言:javascript
复制
    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();
    }

控制台输出

代码语言:javascript
复制

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

结论

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


「FunTester」,非著名测试开发,文章记录学习和感悟,欢迎关注,交流成长。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建一个简单的代理
    • 控制台输出
      • 手动代理创建的问题
        • JDK动态代理
          • 控制台输出
          • 控制台输出
      • 动态代理的局限性
      • cglib实现动态代理
      • 结论
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档