Java日记(day25)--Java动态代理

132 阅读9分钟

Java动态代理

1、动态代理概述

代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问

代理设计模式的原理

  • 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
  • 之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能

应用场景

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象

分类

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类):JDK自带的动态代理(反射)

2、静态代理

特点:代理类和被代理类在编译期间,就确定下来了

//声明一个造衣服的工厂接口
interface ClothFactory{
    void produceCloth();//生产衣服的方法    
}

//代理类
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory){
        this.factory=factory;

    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        factory.produceCloth(); //本质上还是被代理类生产衣服
        System.out.println("代理工厂做一些后续的收尾工作");
    }
}

//被代理类
class ChinaComeOnFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("中国工厂生成一批优质衣服");
    }
}

//测试
public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类对象
        //ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
        ClothFactory chinaComeOnFactory = new ChinaComeOnFactory();
        
        //创建代理类对象
        //ProxyClothFactory proxyClothFactory = new ProxyClothFactory(chinaComeOnFactory);
        ClothFactory proxyClothFactory = new ProxyClothFactory(chinaComeOnFactory);
        
        //让代理类去做一些事情
        proxyClothFactory.produceCloth();

    }
}

由于静态代理中代理类和被代理都都实现了同一个接口,所以我们实现了当通过代理类的对象调用方法produceCloth时,并且调用被代理类中的同名方法produceCloth

总之,就是在生产衣服的时候,让我们的代理类去帮我们的nike工厂(被代理类)做一些事情。但内部本质上是我们的nike工厂生产衣服(调用方法),但是我们要通过代理类对象去帮我们做这件事(调用方法)生产衣服

3、动态代理

3.1、概述

动态代理是指客户通过代理类来调用其它对象的 方法,并且是在程序运行时根据需要动态创建目标类的代理对象

动态代理使用场景:

  • 调试
  • 远程方法调用

动态代理相比于静态代理的优点:

抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,可以更加灵活和统一的处理众多的方法。

3.2、Java动态代理相关 API

1、Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。

2、提供用于创建动态代理类和动态代理对象的静态方法

image.png

3.3、动态代理步骤

1、创建一个实现接口 InvocationHandler 的类,它必须实现 invoke 方法,以完成代理的具体操作。

image.png

2、创建被代理的类以及接口

image.png

3、通过 Proxy 的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个 Subject 接口代理类

4、通过 Subject 代理类调用 RealSubject 被代理类的方法

String info = sub.say("Peter",24)
System.out.println(info);

3.4、实例

要想实现动态代理,需要解决的问题:

  • 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
  • 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
package test;

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

/*
动态代理举例
 */
interface Human{
    String getBelief();
    void eat(String food);
}

//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe i can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("i like to eat "+food);
    }
}

/*
要想实现动态代理,需要解决的问题:
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */
class ProxyFactory{
    //调用此方法,返回一个代理类的对象,解决问题一,不能专门返回一个类(这样就写死了,因此需要返回Object)
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象,也是不能写死,需要Object这样具有动态性,传入都可以对应执行动态代理
        
        //这里是newProxyInstance()方法第三个参数需要传入的参数,通过它来实现代理
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        
        //对传入要被代理的对象进行绑定
        myInvocationHandler.bind(obj);
        
        //反射,调用方法创建一个代理类对象
        //获取到代理类对象实例,以后通过它来代理调用方法
        //第一个参数传入要被代理的对象的加载器,第二个是被代理对象实现的接口,因为代理要实现相同的接口
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
    }
}

//newProxyInstance第三个参数对应类型是接口,因此需要实现它,创建这个对象传入
class MyInvocationHandler implements InvocationHandler{
    
    private Object obj;//需要使用被代理类的对象进行赋值
    
    //绑定被代理对象
    public void bind(Object obj){
        this.obj=obj;
    }
    
    //当通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类对象
        //objects第三个参数是执行对应方法传入的参数
        Object returnValue= method.invoke(obj,objects);//执行后拿到返回值
        
        //上述方法的返回值就作为当前类中的invoke()的返回值
        //被代理对象执行的方法会有返回值的话,通过inboke返回值返回
        return returnValue;
    }
}


public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        
        //proxyInstance:代理类的对象,传入要被代理的对象生成创建的
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        
        //当通过代理类对象调用方法时,会自动的调用被代理类中的同名方法,可以看一下上面的代码实现
        //这样就通过动态代理进行执行对应对象的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief); //打印被代理类对象的方法
        proxyInstance.eat("炸酱面");//打印被代理类对象的方法
        System.out.println("================================");
        
        //再来一个例子
        ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(chinaComeOnFactory);

        proxyInstance1.produceCloth();
    }
}

总结:同静态代理一样,基本步骤

  • 创建一个接口
  • 被代理类要实现它并重写里面的方法
  • 代理类对象通过由被代理类对象反射动态获取
  • 代理类要替被代理类完成一些工作,即当通过代理类对象调用实现的方法时,我们要实现自动的调用被代理类中同名的方法,即通过invoke,方法体里面还有invoke就是自动的实现被代理类对象调用这个重写的同名的方法

4、AOP与动态代理

AOP (Aspect Orient Programming) 解决代码的高耦合问题,前面介绍的Proxy 和 InvocationHandler ,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制

image.png

上面这个相对来说比较冗余,下面是改进后的

image.png

说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、2、3又和一个特定的方法A耦合。最理想的效果是:代码块1、2、3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法。 如下就要使用到AOP

image.png

使用 Proxy 生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理,这种动态代理在 AOP 中被称为 AOP 代理, AOP 代理可代替目标对象, AOP 代理 包含了目标对象的全部方法。但 AOP 代理中的方法与目标对象的方法存在差异:AOP 代理里的方法可以在执行目标方法之前、之后插入一些通用处理,参考Spring

AOP代码实现

package test;

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

/*
动态代理举例
 */
interface Human{
    String getBelief();
    void eat(String food);
}

//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe i can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("i like to eat "+food);
    }
}


class HumanUtil{
    //写两个通用方法到动态代理里面可以调用
    public void method1(){
        System.out.println("=============通用方法一===============");
    }
    public void method2(){
        System.out.println("=============通用方法二===============");
    }
}


class ProxyFactory{
    public static Object getProxyInstance(Object obj){
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        myInvocationHandler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
    }
}

class MyInvocationHandler implements InvocationHandler{
    private Object obj
    public void bind(Object obj){
        this.obj=obj;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

        HumanUtil humanUtil = new HumanUtil();
        
        //通用方法一,方法一二之间的可以切换方法,是动态的,不确定的
        humanUtil.method1();
       
       Object returnValue= method.invoke(obj,objects);//执行后拿到返回值

        //通用方法二
        humanUtil.method2();
       return returnValue;
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        
        
        proxyInstance.eat("炸酱面");
        System.out.println("================================");
        
        //再来一个例子
        ChinaComeOnFactory chinaComeOnFactory = new ChinaComeOnFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(chinaComeOnFactory);

        proxyInstance1.produceCloth();
    }
}

这样就实现了上图表示的方式,method.invoke执行的方法对应图中灰色方法,就是动态的,不确定的,而前后都对应每个动态方法可能需要执行的通用方法。这个就达到了每个方法执行前后都有对应的通用处理,其实就是借助动态代理实现的,都是会在invoke方法中调用这两个方法,然后中间的方法具有动态性会更换其他方法执行,都是在invoke中调用方法,只不过前后的方法都是固定的,不变的,中间的动态变化。