0%

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

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

代理模式

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

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

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

代理模式的两种实现方式

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

一、静态代理

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

1
2
3
4
5
6
7
8
9
package myAOP.proxy;

/**
* @author czf
* @Date 2020/5/7 5:50 下午
*/
public interface Pay {
void pay();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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("支付后需要做的一些事情~~~支付完啦!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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)),这里就对代理对象的创建再做一层封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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自带的动态代理库的话,被代理对象一定要实现了某一个接口,如果被代理对象没有实现任何接口的话,就不能用这个方法… 例如下面这个情况。

1
2
3
4
5
6
7
8
9
10
11
package myAOP.proxy;

/**
* @author czf
* @Date 2020/5/7 6:53 下午
*/
public class PayWithoutInterface {
public void pay(){
System.out.println("用户进行了支付(本操作未实现接口)");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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接口了

1
2
3
public interface MethodInterceptor extends Callback {
Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}

2. 代理对象的创建

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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();
}
}

在这里插入图片描述