【Java高级程序设计学习笔记】动态代理

目录

1.1 代理模式

1.2 Java动态代理

1.3 动态代理机制的特点与不足


1.1 代理模式

  存在这样一个问题:现有类Person类,继承了接口Speakable,该接口有一个方法speak(String message),想知道Person类实现的speak方法何时执行。能否把获取时间的代码加到speak内部呢?显然不合适:1.这段代码本不该属于speak的一部分,不该让他执行。2.如果此类库已经编译且无法修改,那就无法修改原有的代码,在其方法内部加一行代码。一般情况下,应该对方法进行一次封装,重写一个方法,在新方法中调用speak方法并增加额外的代码,诸如此类的问题很多,例如限制外部非法访问、统计方法调用频率和开销等,因此将此类方法的解决方法称为代理模式。

  代理模式指的是为目标提供一个代理对象,外部对目标对象的访问,通过代理委托进行,以达到控制访问的目的。为保持行为的一致性,代理类通常与委托类实现同一接口,所以在访问者看来没有区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好的隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

public interface Speakable {
    public void speak(String message);
}

public class Person implements Speakable{
    @Override
    public void speak(String message){
        System.out.println("Speak:"+message);
    }
}

public class PersonProxy implements Speakable{
    private Person person;
    public PersonProxy(Person person) {
        this.person=person;
    }

    @Override
    public void speak(String message) {
        this.person.speak(message);
        System.out.println("运行时间: "+System.currentTimeMillis());
    }
}

public class Bootstrap {
    public static void main(String[] args) {
        Person person=new Person();
        PersonProxy personProxy=new PersonProxy(person);
        personProxy.speak("Lesson one!");
    }
}
Speak:Lesson one!
运行时间: 1648211468506

  本例定义了两个类,其中PersonProxy做Person访问对象的代理,这两个类都继承了同一个接口,外部访问的时候,调用的是PersonProxy类的speak方法,该方法对Person的speak进行了封装,在调用Person的speak方法后,打印了当前毫秒数。当然如果需要可以在方法访问前做一些预处理,如控制访问权限,在访问后进行消息转发等操作。

1.2 Java动态代理

    代理模式解决了很多问题的同时也增加了很多负担,因为必须为委托类维护一个代理,不易管理而且增加了代码量。Java的动态代理机制的思想就更加先进一步,因为他可以动态地创建代理并动态地处理代理方法的调用。Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单的指定一组接口及委托类对象。代理类负责将所有的方法调用分派到委托对象上反射执行,分派任务过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。

  下图中Proxy为动态代理的核心类,它负责创建所有的代理类,并且它所创建的代理类都是它的子类,而且这些子类继承所代理的一组接口,因此他就可以安全的转换成需要的类型,进行方法调用。InvocationHandler是调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问,图中ProxyHandler为该接口的一个实现,负责委托类代理访问。Proxied即为委托类,该委托类与动态生成的代理类共同实现了一组代理接口,并且调用处理器保存了该类的一个引用。

  下面写一个简单的动态代理实例,对动态代理机制进行详细的说明:

public class MyProxy implements InvocationHandler {
    private Object proxied;
    public MyProxy(Object proxied){
        this.proxied=proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(this.proxied,args);
        System.out.println("运行时间:"+System.currentTimeMillis());
        return null;
    }
}

public class Bootstrap {
    public static void main(String[] args) {
        Person person=new Person();
        Speakable speakable=(Speakable) Proxy.newProxyInstance(
                Speakable.class.getClassLoader(),
                new Class[]{Speakable.class},new MyProxy(person));
        speakable.speak("Lesson one!");
    }
}
Speak:Lesson one!
运行时间:1648215231373

  此例实现了一个代理接口Speakable,其内定义了speak方法,类Person实现了接口Speakable。MyProxy为调用处理器,负责处理对委托对象的访问,它继承了InvocationHandler接口,并实现了invoke(Object proxy,Method method,Object[] args)方法,第一个参数表示代理对象,既由Java动态生成的代理对象;第二个参数表示被执行的委托方法;第三个表示执行委托方法需要的参数,启动类Bootstrap先实例化了一个委托对象,接着用Proxy的静态方法newProxyInstance(ClassLoader loader,Class[] class, InvocationHandler handler);创建了一个动态代理实例,并将该实例转型为Speakable,最后就可以正确调用代理类的speak方法。 

1.3 动态代理机制的特点与不足

  动态生成的代理类特点如下。

1)包:如果所代理的接口都是public的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中由非public的接口(因为接口不能被定义为protect或private,所以出public之外就是默认的package访问级别),那么它将被定义在该接口所在包(假设代理了org.ddd.reflect包中的非public接口A,那么新生成的代理类所在的包是org.ddd.reflect),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。

2)类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承

3)类名:格式是“$ProxyN”,其中N是一个逐级递增的数字,代表Proxy第n次生成的动态代理类,但不是每次调用Proxy的静态方法创建动态代理类都会使得N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,会先返回先前已经创建好了的代理类的类对象。

4)类继承关系:动态生成的代理类继承了Proxy类,并实现了所代理的所有接口。

  实际上,每个动态代理实例都会关联一个调用器对象,可以通过Proxy提供的静态方法getInvocationHandler去获得代理类实例的调用处理器对象,在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke方法执行。当代理的一组接口有重复生声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

  至于被代理的接口,不能有重复接口,以避免动态代理代码生成时的编译错误,其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,会导致类定义失败。再次,需被代理的所有非public接口必须在同一个包里,否则代理类生成也会失败。最后,接口的数目不能超过65535,这是JVM的限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天的命名词

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值