JAVA反射应用之动态代理

195 阅读8分钟

代理模式在Java中应用十分广泛,它说的是使用一个代理将对象包装起来然后用该代理对象取代原始对象,任何原始对象的调用都需要通过代理对象,代理对象决定是否以及何时将方法调用转到原始对象上。这种模式可以这样简单理解:你自己想要做某件事情(被代理类),但是觉得自己做非常麻烦或者不方便,所以就叫一个另一个人(代理类)来帮你做这个事情,而你自己只需要告诉要做啥事就好了。上面我们讲到了反射,在下面我们会说一说java中的代理

 1、静态代理

  静态代理其实就是程序运行之前,提前写好被代理类的代理类,编译之后直接运行即可起到代理的效果,下面会用简单的例子来说明。在例子中,首先我们有一个顶级接口(ProductFactory),这个接口需要代理类(ProxyTeaProduct)和被代理类(TeaProduct)都去实现它,在被代理类中我们重写需要实现的方法(action),该方法会交由代理类去选择是否执行和在何处执行;被代理类中主要是提供顶级接口的的一个引用但是引用实际指向的对象则是实现了该接口的代理类(使用多态的特点,在代理类中提供构造器传递实际的对象引用)。分析之后,我们通过下面这个图理解一下这个过程。

图片.png package proxy;

/**
 * 静态代理
 */
//产品接口
interface ProductFactory {
    void action();
}

//一个具体产品的实现类,作为一个被代理类
class TeaProduct implements ProductFactory{
    @Override
    public void action() {
        System.out.println("我是生产茶叶的......");
    }
}

//TeaProduct的代理类
class ProxyTeaProduct implements ProductFactory {
    //我们需要ProductFactory的一个实现类,去代理这个实现类中的方法(多态)
    ProductFactory productFactory;

    //通过构造器传入实际被代理类的对象,这时候代理类调用action的时候就可以在其中执行被代理代理类的方法了
    public ProxyTeaProduct(ProductFactory productFactory) {
        this.productFactory = productFactory;
    }

    @Override
    public void action() {
        System.out.println("我是代理类,我开始代理执行方法了......");
        productFactory.action();
    }
}
public class TestProduct {

    public static void main(String[] args) {
        //创建代理类的对象
        ProxyTeaProduct proxyTeaProduct = new ProxyTeaProduct(new TeaProduct());
        //执行代理类代理的方法
        proxyTeaProduct.action();
    }
}

那么程序测试的输出结果也很显然了,代理类执行自己实现的方法,而在其中有调用了被代理类的方法

  

  那么我们想一下,上面这种称为静态代理的方式有什么缺点呢?因为每一个代理类只能为一个借口服务(因为这个代理类需要实现这个接口,然后去代理接口实现类的方法),这样一来程序中就会产生过多的代理类。比如说我们现在又来一个接口,那么是不是也需要提供去被代理类去实现它然后交给代理类去代理执行呢,那这样程序就不灵活了。那么如果有一种方式,就可以处理新添加接口的以及实现那不就更加灵活了吗,在java中反射机制的存在为动态代理创造了机会

2、JDK中的动态代理

  动态代理是指通过代理类来调用它对象的方法,并且是在程序运行使其根据需要创建目标类型的代理对象。它只提供一个代理类,我们只需要在运行时候动态传递给需要他代理的对象就可以完成对不同接口的服务了。看下面的例子。(JDK提供的代理正能针对接口做代理,也就是下面的newProxyInstance返回的必须要是一个接口)

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

/**
 * JDK中的动态代理
 */
//第一个接口
interface TargetOne {
    void action();
}
//第一个接口的被代理类
class TargetOneImpl implements TargetOne{
    @Override
    public void action() {
        System.out.println("我会实现父接口的方法...action");
    }
}


//动态代理类
class DynamicProxyHandler implements InvocationHandler {
    //接口的一个引用,多态的特性会使得在程序运行的时候,它实际指向的是实现它的子类对象
    private TargetOne targetOne;
    //我们使用Proxy类的静态方法newProxyInstance方法,将代理对象伪装成那个被代理的对象
    /**
     * ①这个方法会将targetOne指向实际实现接口的子类对象
     * ②根据被代理类的信息返回一个代理类对象
     */
    public Object setObj(TargetOne targetOne) {
        this.targetOne = targetOne;
        //    public static Object newProxyInstance(ClassLoader loader, //被代理类的类加载器
        //                                          Class<?>[] interfaces, //被代理类实现的接口
        //                                          InvocationHandler h) //实现InvocationHandler的代理类对象
        return Proxy.newProxyInstance(targetOne.getClass().getClassLoader(),targetOne.getClass().getInterfaces(),this);
    }
    //当通过代理类的对象发起对接口被重写的方法的调用的时候,都会转换为对invoke方法的调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("这是我代理之前要准备的事情......");
        /**
         *      这里回想一下在静态代理的时候,我们显示指定代理类需要执行的是被代理类的哪些方法;
         *      而在这里的动态代理实现中,我们并不知道代理类会实现什么方法,他是根据运行时通过反射来
         *  知道自己要去指定被代理类的什么方法的
         */
        Object returnVal = method.invoke(this.targetOne,args);//这里的返回值,就相当于真正调用的被代理类方法的返回值
        System.out.println("这是我代理之后要处理的事情......");
        return returnVal;
    }
}
public class TestProxy {
    public static void main(String[] args) {
        //创建被代理类的对象
        TargetOneImpl targetOneImpl = new TargetOneImpl();
        //创建实现了InvocationHandler的代理类对象,然后调用其中的setObj方法完成两项操作
        //①将被代理类对象传入,运行时候调用的是被代理类重写的方法
        //②返回一个类对象,通过代理类对象执行接口中的方法
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        TargetOne targetOne = (TargetOne) dynamicProxyHandler.setObj(targetOneImpl);
        targetOne.action(); //调用该方法运行时都会转为对DynamicProxyHandler中的invoke方法的调用
    }
}

运行结果如下。现在我们对比jdk提供的动态代理和我们刚刚实现的静态代理,刚刚说到静态代理对于新添加的接口需要定义对应的代理类去代理接口的实现类。而上面的测试程序所使用的动态代理规避了这个问题,即我们不需要显示的指定每个接口对应的代理类,有新的接口添加没有关系,只需要在使用的时候传入接口对应的实现类然后返回代理类对象(接口实现类型),然后调用被代理类的方法即可。

  

3、动态代理与AOP简单实现

1、AOP是什么

  AOP(Aspect Orient Programming)我们一般称之为面向切面编程,作为一种面向对象的补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志记录等。AOP实现的关键在于AOP的代理(实际实现上有静态代理和动态代理),我们下面使用JDK的动态代理的方式模拟实现下面的场景。

2、模拟实现AOP

  我们先考虑下面图中的情况和说明。然后我们使用动态代理的思想模拟简单实现一下这个场景

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

//基于jdk的针对接口实现动态代理,要求的接口
interface Target {
    void login();

    void logout();
}

//被代理类
class TargetImpl implements Target {
    @Override
    public void login() {
        System.out.println("log......");
    }

    @Override
    public void logout() {
        System.out.println("logout......");
    }
}

class Util {
    public void printLog() {
        System.out.println("我是记录打印日志功能的方法......");
    }

    public void getProperties() {
        System.out.println("我是获取配置文件信息功能的方法......");
    }
}

//实现了InvocationHandler的统一代理类
class DynamicProxyHandler implements InvocationHandler {
    private Object object;

    /**
     * 参数为obj,是应对对不同的被代理类,都能绑定与该代理类的代理关系
     * 这个方法会将targetOne指向实际实现接口的子类对象,即当前代理类实际要去代理的那个类
     */
    public void setObj(Object obj) {
        this.object = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Util util = new Util();
        util.getProperties();
        Object object = method.invoke(this.object, args); //这个方法是个动态的方法,可以是login,可以是logout,具体在测试调用中调用不同方法
        util.printLog();
        return object;
    }
}

//该类的主要作用就是动态的创建一个代理类的对象,同时需要执行被代理类
class MyDynamicProxyUtil {
    //参数obj表示动态的传递进来被代理类的对象
    public static Object getProxyInstance(Object object) {
        //获取代理类对象
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        dynamicProxyHandler.setObj(object);
        //设置好代理类与被代理类之间的关系后,返回一个代理类的对象
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), dynamicProxyHandler);
    }
}

public class TestAop {
    public static void main(String[] args) {
        //获得被代理类
        Target target = new TargetImpl();
        //通过代理类工具类,设置实际与代理类绑定的被代理类,并返回一个代理类对象执行实际的方法
        Target execute = (Target) MyDynamicProxyUtil.getProxyInstance(target);
        execute.login();
        execute.logout();
    }
}

  现在来分析一下上面的代码,首先我们看一下下面的这个图。在图中动态代理增加的通用日志方法配置文件方法就是增加的方法,他在执行用户实际自己开发的方法之前、之后调用。对应于上面的程序就是Target接口的实现类实现的login、logout方法被代理类动态的调用,在他们执行之前会调用日志模块和配置文件模块的功能。

图片.png