← 返回首页
Spring基础教程(十三)
发表时间:2020-05-14 23:07:47
讲解JDK动态代理

Java中代理的实现一般分为三种:JDK静态代理、JDK动态代理以及CGLIB动态代理。在Spring的AOP实现中,主要应用了JDK动态代理以及CGLIB动态代理。本节着重介绍JDK动态代理机制。

Java动态代理就是通过使用反射,动态地获取抽象接口的类型,从而获取相关特性进行代理。需要注意的是:由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

JDK动态代理有以下三个角色: - 抽象角色:声明真实对象和代理对象的共同接口 - 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供了真实对象相同接口以便任何时候都能替代真实对象。同时,代理对象可以对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。 - 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

实例

功夫巨星成龙,在刚出道时接一部功夫电影,由于刚出道时不是大碗,所有大小琐事都必须自己搞定。比如:谈档期、谈片酬、广告合作、出演、首映仪式、已经如果获奖后如何出席颁奖典礼等等。

不使用动态代理,我们如何用java描述这个故事呢?

项目目录结构图如下:

1.设计演戏接口。 注意:JDK动态代理只支持接口类型,这里不能使用抽象类。

package com.dao;

//演戏的接口
public interface Act {

    //抽象方法
    public void act(); //演戏的方法
}

2.设计演员类

package com.entity;

import com.dao.Act;

public class Actor implements Act {

    private String name; //演员的名字
    private String type; //出演影片的类型

    public Actor() {
    }

    public Actor(String name, String type) {
        this.name = name;
        this.type = type;
    }

    public void act() {
        System.out.println("洽谈档期..");
        System.out.println("洽谈片酬..");
        System.out.println("洽谈广告合作..");
        System.out.println(this.name + "正在出演" + this.type + "影片...");
        System.out.println("洽谈首映仪式..");
        System.out.println("洽谈颁奖典礼..");
    }
}

3.设计测试类

package com.test;

import com.dao.Act;
import com.entity.Actor;

public class ActorDemo {

    public static void main(String[] args) {

        Act actor = new Actor("成龙","功夫片");
        actor.act();
    }
}

运行结果:
洽谈档期..
洽谈片酬..
洽谈广告合作..
成龙正在出演功夫片影片...
洽谈首映仪式..
洽谈颁奖典礼..

后来,成龙成为国际功夫巨星后,只专注认真演戏,演戏前后所有的琐事都会交给经纪人去处理,那么这个经纪人就是一个代理类(Proxy)。

上例,改写如下:

4.新增proxy包,里面创建ActorProxy类。

ActorProxy类代码如下:

package proxy;

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

//成龙经纪人类(代理类)
public class ActorProxy implements InvocationHandler {

    // 目标对象
    private Object targetObject; //成龙经纪人目标对象肯定是成龙

    //传入一个代理之前的对象,返回一个被代理对象。
    public Object newProxyInstance(Object targetObject){
        this.targetObject=targetObject;

        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),this);
    }

    //演戏之前要做的事情
    private void beforeAct(){
        System.out.println("洽谈档期..");
        System.out.println("洽谈片酬..");
        System.out.println("洽谈广告合作..");
    }

    //演戏之后要做的事情
    private void afterAct(){
        System.out.println("洽谈首映仪式..");
        System.out.println("洽谈颁奖典礼..");
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;
        this.beforeAct();
        ret=method.invoke(targetObject, args); //调用真正成龙演戏的方法
        this.afterAct();

        return ret;
    }
}

5.修改演员类.

演员类的act方法只专注认真演戏。

package com.entity;

import com.dao.Act;

public class Actor implements Act {

    private String name; //演员的名字
    private String type; //出演影片的类型
    public Actor() {
    }

    public Actor(String name, String type) {
        this.name = name;
        this.type = type;
    }
    public void act() {
        System.out.println(this.name + "正在出演" + this.type + "影片...");
    }
}

6.测试类测试

测试类里通过代理类生成代理对象,调用代理对象的演戏方法。

package com.test;

import com.dao.Act;
import com.entity.Actor;
import proxy.ActorProxy;

public class ActorDemo {

    public static void main(String[] args) {

        Act actor = new Actor("成龙","功夫片");
        actor = (Act) new ActorProxy().newProxyInstance(actor);
        actor.act();
    }
}

运行结果:
洽谈档期..
洽谈片酬..
洽谈广告合作..
成龙正在出演功夫片影片...
洽谈首映仪式..
洽谈颁奖典礼..

可以看出调用代理对象的演戏方法,演戏前后的琐事都是经纪人完成的。

使用动态代理有三个要点,

注意到 Proxy.newProxyInstance 这个方法,它需要传入 3 个参数。解析如下:

// 第一个参数,是类的加载器
// 第二个参数是委托类的接口类型,证代理类返回的是同一个实现接口下的类型,保持代理类与抽象角色行为的一致
// 第三个参数就是代理类本身,即告诉代理类,代理类遇到某个委托类的方法时该调用哪个类下的invoke方法
Proxy.newProxyInstance(Class loader, Class<?>[] interfaces, InvocationHandler h)

再来看看 invoke 方法,用户调用代理对象的什么方法,实质上都是在调用处理器的 invoke 方法,通过该方法调用目标方法,它也有三个参数:

// 第一个参数为 Proxy 类类型实例,如匿名的 $proxy 实例
// 第二个参数为委托类的方法对象
// 第三个参数为委托类的方法参数
// 返回类型为委托类某个方法的执行结果
public Object invoke(Object proxy, Method method, Object[] args)