设计模式——动态代理及实现

在啃Spring的AOP之前,在复习以下动态代理及其实现方式

代理模式

在不使用代理模式的场景下,对象A想使用对象B的功能,一个方法是通过持有对对象B的引用,然后直接使用对象B提供的服务了。

而使用了代理模式,则引入一个第三方的代理对象,这个代理对象持有着对对象B的引用,可以调用对象B的服务与资源,而如果有对象希望使用对象B提供的服务的话,则不再去找对象B,而是去找这个代理对象。 如下图所示。
在这里插入图片描述
很多思想都是通过引入一个第三者来去实现某个功能,使得程序耦合度更低,或者提高代码复用性等等,例如发布-订阅模式,控制反转,领域模型的思想等等,而这里的代理模式,也是引入了一个第三者(代理对象),这个对象对外提供的接口仅仅是对某一主要功能的服务接口,代理对象内部持有真正提供这一服务的对象,通过它们来调用服务,同时,在代理对象这一层面对方法进行增强。。。

例如:当我们调用dao接口访问数据库的时候,我们关注的是访问数据库这一过程,而记录日志,安全管理,事务管理这一类事情尽管也很重要,但并不是我们关注的对象,并且这类事情不仅仅只是在这一次访问数据库才会去做的,而是每次访问数据库都要去做的。。
在这里插入图片描述

代理模式的两种实现方式

在实现代理模式之前,脑中要有2个关键核心的东西, 代理对象 = 被代理对象 + 切面逻辑, 也许这么说并不是非常严谨准确,但方便对代理模式的学习。

一、静态代理

静态代理就是在编译时就已经确定了怎样去增强方法,也就是,把切面的逻辑都写死在代理对象里面。就是说,在代理类里,就已经确定了代理对象 和 切面逻辑。(毕竟都写死在里面了)

package myAOP.proxy;

/**
 * @author czf
 * @Date 2020/5/7 5:50 下午
 */
public interface Pay {
    void pay(); 
}

package myAOP.proxy;

/**
 * @author czf
 * @Date 2020/5/7 9:05 下午
 */
 // 因为主要功能都是提供某一个服务,实现某一个服务的话,往往也是要去实现某一个接口(单一责任原则),因此代理对象往往会和被代理对象实现同一个接口。 
 // 例如,这里被代理对象是(ToCPaymentImpl implemets Pay) 也是实现了Pay接口
public class StaticPayProxy implements Pay {
    private Pay payment;  // 被代理的对象

    public StaticPayProxy(Pay payment) {
        this.payment = payment;
    }

    @Override
    public void pay() {
        postProcessBeforePayment();  // 增强逻辑
        payment.pay(); // 真正的业务逻辑,我们所关心的
        postProcessAfterPayment(); // 增强逻辑
    }
    void postProcessBeforePayment(){
        System.out.println("支付前需要做的一些事情~~~ 我要支付啦!");
    }

    void postProcessAfterPayment(){
        System.out.println("支付后需要做的一些事情~~~支付完啦!");
    }
}

ge myAOP.proxy;

import myAOP.proxy.impl.ToCPaymentImpl;


/**
 * @author czf
 * @Date 2020/5/7 5:52 下午
 */
public class proxyMain {
    public static void main(String[] args) {
        Pay userPay = new StaticPayProxy(new ToCPaymentImpl());
        userPay.pay();
    }
}

在这里插入图片描述
静态代理带来的弊端就是,增强的代码被写死到某一个固定的代理类中,这样使得增强的代码无法被复用。 例如,上面的例子中,我们又来了一个ToC的Payment需要去实现的话,那么也许业务逻辑不一样,但是增强的方法实际上是一样的,但我们确不得不重新在新的代理对象里再写一遍这个增强方法。。 为了能够使得增强代码复用, 可以用动态代理来做。

二、动态代理

动态代理是在运行时去确定增强的方法,在运行时将一些切面逻辑给织入到我们的程序中。也就是说,在运行时才确定 被代理对象 + 切面逻辑 。 而下面的2种动态代理的实现,实际上也主要就是学习使用JDK和第三方库提供给我们的强大功能,即:

  1. 切面逻辑抽象化的功能 (JDK里用的InvovationHandler,Cglib里是实现了CallBack接口的Interceptor)
  2. 代理对象创建的功能(JDK里用的是Proxy类,Cglib里用的是Enhancer类)

2.1 使用JDK的提供的动态代理

jdk1.3之后就提供了动态代理的功能.

1. 创建将切面逻辑抽象化的Handler对象
package myAOP.proxy;

import com.sun.corba.se.spi.ior.ObjectKey;

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

/**
 * @author czf
 * @Date 2020/5/7 5:53 下午
 */
public class PaymentHandler implements InvocationHandler {
    /**
     * 调用需要调用的核心方法, 然后在这个方法周围进行织入~
     * @param proxy 代理的对象, 注意,是代理的对象!!!
     *              不是被代理的对象!!而往往内部调用的都是被代理对象的方法!!
     *              所以需要通过其他方式来得到被代理对象,这里通过有参构造方法实现
     * @param method  被代理对象的指定方法
     * @param args 方法参数
     * @return
     * @throws Throwable
     */
    private Object targetObject;  // 这个才是被代理的对象!!通过有参构造函数传入

    public PaymentHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    // 对外提供的是被代理对象所要提供的方法,但在其基础上又添加了一部分逻辑
    // 就是下面的postProcessBeforePayment和postProcessAfterPayment
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        postProcessBeforePayment();
        Object res = method.invoke(targetObject, args); // 这里使用的是被代理对象~
        postProcessAfterPayment();
        return res;
    }

    void postProcessBeforePayment(){
        System.out.println("支付前需要做的一些事情~~~ 我要支付啦!");
    }

    void postProcessAfterPayment(){
        System.out.println("支付后需要做的一些事情~~~支付完啦!");
    }
}

2. 代理对象的创建

为了使代码更加优雅,能够统一创建代理对象的接口(即提供被代理对象+抽象出来的切面对象(这里是handler)),这里就对代理对象的创建再做一层封装。

package myAOP.proxy;

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

/**
 * @author czf
 * @Date 2020/5/7 6:00 下午
 */
public class JDKDynamicProxyUtil {
    public static <T> T newProxyInstance(T proxyObject, InvocationHandler handler){
        ClassLoader classLoader = proxyObject.getClass().getClassLoader();
        Class<?>[] interfaces = proxyObject.getClass().getInterfaces();
        // classloader是被代理对象的classloader, interfaces是被代理对象实现的所有接口
        Object res = Proxy.newProxyInstance(classLoader, interfaces, handler);
        return (T)res;
    }
}

package myAOP.proxy;

import myAOP.proxy.impl.ToCPaymentImpl;


/**
 * @author czf
 * @Date 2020/5/7 5:52 下午
 */
public class proxyMain {
    public static void main(String[] args) {
        Pay userPay = new ToCPaymentImpl();
        PaymentHandler paymentHandler = new PaymentHandler(userPay);
        Pay pay = JDKDynamicProxyUtil.newProxyInstance(userPay, paymentHandler);
        pay.pay();
	}
}

但使用JDK自带的动态代理库的话,被代理对象一定要实现了某一个接口,如果被代理对象没有实现任何接口的话,就不能用这个方法… 例如下面这个情况。

package myAOP.proxy;

/**
 * @author czf
 * @Date 2020/5/7 6:53 下午
 */
public class PayWithoutInterface {
    public void pay(){
        System.out.println("用户进行了支付(本操作未实现接口)");
    }
}

package myAOP.proxy;

import myAOP.proxy.impl.ToCPaymentImpl;


/**
 * @author czf
 * @Date 2020/5/7 5:52 下午
 */
public class proxyMain {
    public static void main(String[] args) {
        // JDK提供的动态代理,是通过实例化一个实现了被代理对象实现的接口,这样它们就都能够被同一个接口给接着
        // 但如果被代理对象根本就没有实现任何接口,那么就会报错,例如下面这个:
        PayWithoutInterface userPay = new PayWithoutInterface();
        PaymentHandler paymentHandler = new PaymentHandler(userPay);
        PayWithoutInterface pay = JDKDynamicProxyUtil.newProxyInstance(userPay, paymentHandler);
        pay.pay();
        // 就会抛出Exception in thread "main" java.lang.ClassCastException:
        // com.sun.proxy.$Proxy0 cannot be cast to myAOP.proxy.PayWithoutInterface
        //	at myAOP.proxy.proxyMain.main
	}
}

而如果使用Cglib的话,就不存在这一问题了。。

2.2 使用cglib实现动态代理

cglib是第三方库,基于asm实现的,即直接对字节码进行代码织入

1. 创建将切面逻辑抽象化的Callback对象
package myAOP.proxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author czf
 * @Date 2020/5/7 7:00 下午
 */
public class PayInterceptor implements MethodInterceptor {
    /**
     *
     * @param o   代理对象 (注意,是代理对象,不是被代理对象!!!
     * @param method   需要调用的被代理对象的方法..
     * @param args  方法参数
     * @param methodProxy  包装了被代理对象方法的对象...可以调用invokeSuper
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        postProcessBeforePayment(); // 增强的逻辑
        Object res = methodProxy.invokeSuper(o, args); // 实际调用的核心方法
        postProcessAfterPayment(); // 增强的逻辑
        return res;
    }

    void postProcessBeforePayment(){
        System.out.println("支付前需要做的一些事情~~~ 我要支付啦!");
    }

    void postProcessAfterPayment(){
        System.out.println("支付后需要做的一些事情~~~支付完啦!");
    }
}

这里看似没有实现Callback接口,是因为MethodInterceptor接口已经继承了Callback接口了

public interface MethodInterceptor extends Callback {
    Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}
2. 代理对象的创建

这里同样对创建逻辑进行了一层封装

package myAOP.proxy;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

import java.lang.reflect.Method;

/**
 * @author czf
 * @Date 2020/5/7 7:04 下午
 */
public class CglibUtil {
    public static <T> T newInstance(T targetObject, Callback methodInterceptor){
        return (T) Enhancer.create(targetObject.getClass(), methodInterceptor);
    }
}
package myAOP.proxy;

import myAOP.proxy.impl.ToCPaymentImpl;
import net.sf.cglib.proxy.Callback;


/**
 * @author czf
 * @Date 2020/5/7 5:52 下午
 */
public class proxyMain {
    public static void main(String[] args) {
//        Pay userPay = new StaticPayProxy(new ToCPaymentImpl());
//        userPay.pay();
//        Pay userPay = new ToCPaymentImpl();
        userPay.pay();
//        PaymentHandler paymentHandler = new PaymentHandler(userPay);
//        Pay pay = JDKDynamicProxyUtil.newProxyInstance(userPay, paymentHandler);
//        pay.pay();

        // JDK提供的动态代理,是通过实例化一个实现了被代理对象实现的接口,这样它们就都能够被同一个接口给接着
        // 但如果被代理对象根本就没有实现任何接口,那么就会报错,例如下面这个:
//        PayWithoutInterface userPay = new PayWithoutInterface();
//        PaymentHandler paymentHandler = new PaymentHandler(userPay);
//        PayWithoutInterface pay = JDKDynamicProxyUtil.newProxyInstance(userPay, paymentHandler);
//        pay.pay();
        // 就会抛出Exception in thread "main" java.lang.ClassCastException:
        // com.sun.proxy.$Proxy0 cannot be cast to myAOP.proxy.PayWithoutInterface
        //	at myAOP.proxy.proxyMain.main

        // 对于这种情况,可以考虑使用cglib
        PayWithoutInterface userPay = new PayWithoutInterface();
        Callback interceptor = new PayInterceptor();
        PayWithoutInterface userPayProxy = CglibUtil.newInstance(userPay, interceptor);
        userPayProxy.pay();
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值