Java代理的基本用法

目录

 

什么是代理

静态代理

jdk动态代理

入门

提升

为什么被代理类必须实现接口?

问题

 


什么是代理

代理在生活中指的是什么?

“有什么事请找我的律师谈”,这里的律师就充当着代理的作用。为了更好达表的我的观点,帮助我处理纠纷,我需要聘请更专业的律师来替我打官司。

在程序原有功能的基础上,现在需要添加新的功能。在不改变原有类的情况下,我们可以创建一个代理类,来代替原有类出面,完成重要的任务。

Spring框架中的AOP切面编程,正是用了代理技术。

很多好用方便的框架,通过动态代理、反射等技术,在不影响业务代码的情况下(保证最小的入侵),替我们完成许多复杂的工作。

代理应用广泛,但别把它想的那么复杂,基本的原理和思想也是来源于生活。

常见的代理方式有:静态代理、jdk动态代理、cglib动态代理。今天暂且整理前两种的实现方式。

静态代理

小明来到了一家管理严格的公司,这个公司有很多的摄像头,当员工操作公司文件的时候,都会很智能的拍摄下来,防止泄密。我们来实现这一功能:

对文件的操作通常有增删改查,先定义一个接口,来描述“操作”。


public interface Operation {
    void select();
    void add();
    void update();
    void delete();
}

公司的文件需要实现这一接口,因为它可以被增删改查

public class OfficeFile implements Operation{
    @Override
    public void select() {
        System.out.println("查询公司文件");
    }

    @Override
    public void add() {
        System.out.println("添加公司文件");
    }

    @Override
    public void update() {
        System.out.println("更换公司文件");
    }

    @Override
    public void delete() {
        System.out.println("删除公司文件");
    }
}

现在的需求是,在查看和拿走(删除)文件的时候,记录下操作人的行为。

public class OfficeFileProxy implements Operation{
    private Operation operation;

    public OfficeFileProxy(Operation operation) {
        this.operation = operation;
    }

    @Override
    public void select() {
        System.out.println("监控发现一个人正在查看公司文件");
        operation.select();
        System.out.println("此人已离开,拍摄完毕");
    }

    @Override
    public void add() {
        operation.add();
    }

    @Override
    public void update() {
        operation.update();
    }

    @Override
    public void delete() {
        System.out.println("监控发现一个人正在删除公司文件");
        operation.delete();
        System.out.println("此人已离开,拍摄完毕");
    }
}

现在有人来操作文件了,摄像头记录了这一切

public class StaticProxyTest {
    public static void main(String[] args) {
        OfficeFileProxy officeFileProxy = new OfficeFileProxy(new OfficeFile());
        officeFileProxy.delete();
        System.out.println("--------------分割线---------------");
        officeFileProxy.add();
        System.out.println("--------------分割线---------------");
        officeFileProxy.select();
    }
}

运行结果

此时的OfficeFileProxy就是我们请过来的代理,它可以实现OfficeFile的所有功能,并提供了监控服务。

这就是静态代理的实现方式。

jdk动态代理

动态代理相对静态代理来说,更为高级。它可以动态(运行时)的创建代理类,实现上述功能。

在使用静态代理的过程中,可能会出现多个处理器(例如上述的select、delete),使用动态代理可以将所有的调用重定向到单一处理器上。

入门

先来简化一下难度,假如现在只有一个操作,就是查询。

接口

public interface Operation {
    void select();
}

实现类

public class OfficeFile implements Operation{
    @Override
    public void select() {
        System.out.println("查询公司文件");
    }
}

创建代理类并测试

(好长的一串,先这么写吧,后面再分解)

public class DynamicProxy {
    public static void main(String[] args) {
        Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                OfficeFile officeFile = new OfficeFile();
                System.out.println("有人要开始查询文件了-----------------");
                Object result = method.invoke(officeFile, args);
                System.out.println("这个人已经查看完了-----------------");
                return result;
            }
        });
        operationProxy.select();
    }
}

结果

功能基本达到了,但是有些地方还有问题。

第一点,匿名类这玩意自己用起来感觉很爽,但是看别人用就感觉提高了代码的复杂度,不好理解,那我们先把匿名类分解出来。

主程序简化了:

public class DynamicProxy {
    public static void main(String[] args) {
        Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
                OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
        operationProxy.select();
    }
}

下面我们来分析一下这几行代码。

1、 Proxy类
jdk提供的代理工具类
2、newProxyInstance()
产生一个代理类的实例,也就是OfficeFile的实例
3、Operation
用Operation来接收这个实例,是因为OfficeFile继承了Operation接口,多态。
4、参数
第一个参数:被代理类的类加载器
第二个参数:被代理类的所有接口的字节码的对象
第三个参数:处理器,在这个对象里加工被代理的类的方法

处理器:

public class OfficeFileInvocationHandler implements InvocationHandler {

    private Object target;
    public OfficeFileInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("有人要开始查询文件了-----------------");
        Object result = method.invoke(target, args);
        System.out.println("这个人已经查看完了-----------------");
        return result;
    }
}

通过构造注入的方式,更加灵活一点。

InvocationHandler也是jdk提供的代理工具类,听名字就知道是个处理器,当代理的的方法被调用时,会自动调用invoke方法。

再看一下Proxy类里面有哪些方法:

分别是:

1、创建一个代理类实例(常用)

2、获取代理类的字节码对象

3、获取处理器

4、判断是否是一个代理类的字节码对象

我们只用了第一个方法。现在我们来走点弯路,用第二个方法创建代理类实例,分解一下过程。

public class DynamicProxy {
    public static void main(String[] args) throws Exception {
//        Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
//                OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
//        operationProxy.select();
        //获取代理类的字节码对象
        Class<?> proxyClass = Proxy.getProxyClass(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces());
        //获得代理类的字节码文件
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        //创建代理对象(实例化)
        Operation operationProxy = (Operation) constructor.newInstance(new OfficeFileInvocationHandler(new OfficeFile()));
        operationProxy.select();
    }
}

其实就是通过反射api创建实例的不同方法而已,重点还是反射。

提升

现在来加大难度,完善我们的程序。

首先,将操作拓展为增删改查,并添加参数。

接口

public interface Operation {
    void select(String fileName);
    void update();
    void delete();
    void add();
}

处理类

通过方法名判断是否需要进一步加工

public class OfficeFileInvocationHandler implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        if ("select".equals(method.getName())) {
            System.out.println("有人要开始查询文件了:  " + args[0] + "-----------------");
            result = method.invoke(target, args);
            System.out.println("这个人已经查看完了-----------------");
        } else if ("delete".equals(method.getName())) {
            System.out.println("删除了一个文件-------------------");
            result = method.invoke(target, args);
            System.out.println("这个人已经删除完了-----------------");
        } else {
            //add、update忽略
            result = method.invoke(target, args);
        }

        return result;
    }
}

主程序

public class DynamicProxy {
    public static void main(String[] args) throws Exception {
//        Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
//                OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
//        operationProxy.select();
        //获取代理类的字节码对象
        Class<?> proxyClass = Proxy.getProxyClass(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces());
        //获得代理类的字节码文件
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        //创建代理对象(实例化)
        Operation operationProxy = (Operation) constructor.newInstance(new OfficeFileInvocationHandler(new OfficeFile()));
        operationProxy.select("企业文化");
        operationProxy.add();
        operationProxy.update();
        operationProxy.delete();
    }
}

运行结果:

有人要开始查询文件了:  企业文化-----------------
查询公司文件___企业文化
这个人已经查看完了-----------------
添加公司文件
更新公司文件
删除了一个文件-------------------
删除公司文件
这个人已经删除完了-----------------

为什么被代理类必须实现接口?

为什么这个Operation一直存在,我们不能直接用OfficeFile这个类吗?

从程序本身来看,确实将操作抽象出来比较好,符合面向对象程序设计的规范,也方便运用各种设计模式。

但如果我们就不想用这个接口,直接用实现类试试

public class DynamicProxy {
    public static void main(String[] args) throws Exception {
        OfficeFile officeFileProxy = (OfficeFile) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
                OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
        officeFileProxy.select("企业文化");

    }
}

程序会报错

代理对象不能被转化成OfficeFile对象。

我们点进proxy的源码看看,

获取代理类的字节码对象,再点进去

找到了工厂类ProxyClassFactory,点进去

终于找到了生成代理类字节码文件的地方,可惜返回值是byte数组,我们看不到。

但是如果把字节流写进.class文件,不就可以得到代理类的字节码文件了吗。

仿造上面的方法,填好参数,设置好文件存放位置:

public class GenerateProxyClass {
    public static void main(String[] args) {
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                "OfficeFile", com.dayrain.demo.proxy.verson3.OfficeFile.class.getInterfaces(), Modifier.PUBLIC);
        File file = new File("E:\\personcode\\base-demo\\class\\OfficeFileProxy.class");
        try {
            OutputStream outputStream = new FileOutputStream(file);
            outputStream.write(proxyClassFile);
            outputStream.flush();
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

随便下个反编译工具,打开看看。

代理类已经继承了Proxy,Java中是不允许多继承的,所以只能通过实现接口的方式,来与被代理类产生联系。

问题

jdk的动态代理虽然可以在运行中创建代理对象,但是很依赖接口,没有继承接口,就无法实现代理。

市面上的框架基本都是用的cglib,不需要实现接口,这个日后再做总结。

 

如果文章有任何问题,欢迎批评指正!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值