欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JDK动态代理浅析

程序员文章站 2022-06-28 11:31:28
Java中的动态代理设计模式是非常经典且非常重要的设计模式之一,在感叹设计者的天才设计至于,我们想去探究一下这个设计模式是如何来实现的; 著名的spring框架的AOP的原理就是Java的动态代理机制; 在Spring中动态代理是实现有两种:JDK动态代理和Cglib动态代理,本篇分析的是JDK动态 ......

java中的动态代理设计模式是非常经典且非常重要的设计模式之一,在感叹设计者的天才设计至于,我们想去探究一下这个设计模式是如何来实现的;

著名的spring框架的aop的原理就是java的动态代理机制;

在spring中动态代理是实现有两种:jdk动态代理和cglib动态代理,本篇分析的是jdk动态代理的实现;

查看下篇 [ cglib动态代理浅析 ]

在探究动态代理之前,先看一下静态代理,这可以帮助我们更好的理解动态代理;

以公司老板和员工为例,现在boss有一些项目要完成(定义接口)

public interface boss {
    public void dosomethinig();
    public void finishtasks(string name);
}

这些工作肯定是需要雇员工去做的(去实现老板定义的接口)

public class employee implements boss {
    @override
    public void dosomethinig() {
        system.out.println("员工工作中 ...");
    }
    @override
    public void finishtasks(string name) {
        system.out.println("员工正在完成项目 " + name + " ...");
    }
}

我们先使用静态代理,这时候我们雇一个项目经理负责代理员工完成工作,可能有点不恰当哈,先这么理解吧;(同样需要实现老板的接口,代理员工执行任务)

public class manager implements boss {
    private employee employee = new employee();
    @override
    public void dosomethinig() {
        // 员工完成工作之前
        system.out.println(">>>>>员工完成工作之前");
        // 员工正在完成工作
        employee.dosomethinig();
        // 员工完成工作之后
        system.out.println("员工完成工作之后>>>>>");
    }
    @override
    public void finishtasks(string name) {
        // 员工完成工作之前
        system.out.println(">>>>>员工完成工作之前");
        // 员工正在完成工作
        employee.finishtasks(name);
        // 员工完成工作之后
        system.out.println("员工完成工作之后>>>>>");
    }
}

 

写个测试类

public class testproxy {
    public static void main(string[] args) {
        manager manager = new manager();
        manager.dosomethinig();
        manager.finishtasks("java静态代理");
    }
}

 

刚开始,我也一直很疑惑,员工都已经实现boss的所有方法了,而且在测试类中也直接new出来员工对象了,直接员工.方法执行不就完了,为什么还要多此一举使用一个代理类来完成执行呢?先看测试执行结果:

>>>>>员工完成工作之前
员工工作中 ...
员工完成工作之后>>>>>

>>>>>员工完成工作之前
员工正在完成项目 java静态代理 ...
员工完成工作之后>>>>>

 

可以看出在项目经理代理类中,我们其实是可以在员工方法执行之前和执行之后做一些操作的,具体可以做什么和能做什么,你们发挥自己想象力;

代理模式应用在实际项目中,是能够帮我们完成很多事情的,比如个大框架中最常见的日志记录,数据库事务控制,权限验证等等,就不一一列举了(当然它们使用的肯定不是静态代理,而是接下来要说的动态代理了);

但是静态代理又一个非常大的弊端,就是每个接口都需要一个代理类去实现和执行,随着业务的数量越来越复杂的时候,代理类的代码量也是十分惊人的,这对于项目来说是很难去管理维护的;而动态代理的出现,正是用来解决这个问题的;

在java的动态代理机制中,有两个至关重要的对象:

invocationhandler 接口
proxy 类

 

我们看看jdk api是怎么描述invocationhandler 的(摘自jdk api 1.6.0中文版)

public interface invocationhandler
invocationhandler 是代理实例的调用处理程序实现的接口。 
每个代理实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
invoke方法描述(摘自jdk api 1.6.0中文版)

invoke object invoke(object proxy,method method,object[] args)throws throwable
    在代理实例上处理方法调用并返回结果。
    在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

参数:
    proxy 
        - 在其上调用方法的代理实例,指代我们所代理的那个真实对象
    method 
        - 对应于在代理实例上调用的接口方法的 method 实例。
        - method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
        - 指代的是我们所要调用真实对象的某个方法的method对象
    args 
        - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。
        - 基本类型的参数被包装在适当基本包装器类(如 java.lang.integer 或 java.lang.boolean)的实例中。 
        - 指代的是调用真实对象某个方法时接受的参数
返回:
    从代理实例的方法调用返回的值。
    如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;
    否则,它一定是可分配到声明返回类型的类型。
    如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 nullpointerexception。
    否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 classcastexception。

 

我们再来看看proxy类(摘自jdk api 1.6.0中文版)

public class proxy extends object implements serializable 
proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
proxy.newproxyinstance方法(摘自jdk api 1.6.0中文版)

public static object newproxyinstance(
    classloader loader,
    class<?>[] interfaces,
    invocationhandler h)
    throws illegalargumentexception
    返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
        - 此方法相当于: 
            proxy.getproxyclass(loader, interfaces).
            getconstructor(new class[] { invocationhandler.class }).
            newinstance(new object[] { handler });
        - proxy.newproxyinstance 抛出 illegalargumentexception,原因与 proxy.getproxyclass 相同。

参数:
    loader
        - 定义代理类的类加载器
        - classloader对象,定义了由哪个classloader对象来对生成的代理对象进行加载
    interfaces
        - 代理类要实现的接口列表
        - interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口
        - 如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    h 
        - 指派方法调用的调用处理程序
        - invocationhandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个invocationhandler对象上
返回:
    一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

 

现在,我们使用动态代理来决绝刚才的问题,boss和员工还是原来的

public interface boss {
    public void dosomethinig();
    public void finishtasks(string name);
}

 

员工

public class employee implements boss {
    @override
    public void dosomethinig() {
        system.out.println("员工工作中 ...");
    }
    @override
    public void finishtasks(string name) {
        system.out.println("员工正在完成项目 " + name + " ...");
    }
}

 

这时候我们使用动态代理,雇一个领导,来代理所有的事情;

public class leader implements invocationhandler {
    private object target;
    // 返回一个代理对象,并绑定被代理对象
    public object getproxyinstance(object object) {
        this.target = object;
        // proxy的newproxyinstance方法需要三个参数
        return proxy.newproxyinstance(
            // 1.一个类加载器,通常可以从已经被加载的对象中获取类加载器
            object.getclass().getclassloader(),
            // 2.希望代理实现的接口列表
            object.getclass().getinterfaces(),
            // 3.一个invocationhandler接口的实现
            this);
    }
    @override
    public object invoke(object proxy, method method, object[] args) throws throwable {
        // 这里我们简单输出一下invoke的第二第三个参数
        system.out.println("method : " + method);
        system.out.println("args : " + args);
        if (args != null) {
        system.out.print("arg : ");
        for (object arg : args) {
            system.out.print(arg + " ");
            }
            system.out.println();
        }
        // 员工完成工作之前
        system.out.println(">>>>>员工完成工作之前");
        // 员工正在完成工作
        object invoke = method.invoke(target, args);
        // 员工完成工作之后
        system.out.println("员工完成工作之后>>>>>");
        return invoke;
    }
}

 

测试类:

public class leadertest {
    public static void main(string[] args) {
        employee employee = new employee();
        leader leader = new leader();
        boss boss = (boss) leader.getproxyinstance(employee);
        boss.dosomethinig();
        boss.finishtasks("java动态代理");
    }
}

 

控制台输出:

method : public abstract void com.guitu18.boss.dosomethinig()
args : null
>>>>>员工完成工作之前
员工工作中 ...
员工完成工作之后>>>>>

method : public abstract void com.guitu18.boss.finishtasks(java.lang.string)
args : [ljava.lang.object;@1c7c054
arg : java动态代理 
>>>>>员工完成工作之前
员工正在完成项目 java动态代理 ...
员工完成工作之后>>>>>

 

我们在方法中打印invoke方法的第二个参数method method,从控制台看出,我们在调用方法的时候,实际上使用的是代理对象来调用真实对象的方法的:

method : public abstract void com.guitu18.boss.dosomethinig()
method : public abstract void com.guitu18.boss.finishtasks(java.lang.string)

 

而打印invoke方法的第三个参数object[] args时我们发现,该数组其实就是真实方法接收的参数,如果没有则为null;

args : null

args : [ljava.lang.object;@1c7c054
arg : java动态代理

 

现在,我们可以在method.invoke方法的前后都加上自己的一些操作;

在各大框架中,使用动态代理的比比皆是,比如如著名的spring框架的aop原理,比如shiro的权限验证,拦截器等等;

从以上代码中,我们也可以看出,使用动态代理之后,代理类的代码量已经被固定下来了,不会随着业务的复杂和庞大而变得越来越多;

其实在这里,我们已经可以把代码写成代理工厂模式了,简化一些操作:

public class proxyfactory {
    public static object getproxyinstance(final object target) {
        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 invoke = method.invoke(target, args);
                        // 方法执行后
                        system.out.println("提交/回滚事务...");
                        return invoke;
                    }
                });
    }
}

 

在proxyfactory里,提供了一个静态方法,我么仅需要传递进来一个代理类的具体实现,就能够获得一个代理对象,在这个方法中,proxy.newproxyinstance需要的第三个参数,是以匿名内部类的方式去提供和定义的,更加简洁和灵活;

编写测试类:

public class proxyfactorytest {
    public static void main(string[] args) {
        boss boss = (boss) proxyfactory.getproxyinstance(new employee());
        boss.dosomethinig();
        boss.finishtasks("java动态代理");
    }
}

 

控制台输出:

开始事务...
员工工作中 ...
提交/回滚事务...

开始事务...
员工正在完成项目 java动态代理 ...
提交/回滚事务...

 

在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。

并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求;

例如:

数据库事务的操作,在方法执行前开启事务,执行完毕提交事务,如果方法执行出错,回滚事务; 拦截用户的访问请求,以检查用户是否有访问权限,有权限继续执行,没权限不执行做其他操作; 增强,动态为某个对象添加额外的功能;

尾巴:

  1. 动态代理是十分强大的设计模式,代理类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现aop编程,实际上静态代理也可以实现,总的来说,aop可以算作是代理模式的一个典型应用;
  3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变;