代理模式
在正式介绍代理模式之前,我们需要思考一下,我们为什么需要使用到代理类,如果可以直接访问和使用到目标资源对象,我们干嘛还要脱裤子放屁找一个代理类呢?
问题就出在这一点,有一些场景下,我们确实无法直接访问到目标资源,例如:
- 出于安全性的考虑,目标资源不能直接暴露给最终用户
- 出于易用性的考虑,目标资源过于复杂,很多接口是用户不会使用到的,直接使用会增大用户使用成本
- 出于监控、审计等方面的考虑,目标资源每一次的访问都需要被监控和记录到
- 只有达到某个规则或者要求才能访问到目标资源,而这些规则跟资源本身没有直接关联,耦合在一起也不方便各自的维护
当出现类似上述需求场景的情况下,那么你可能需要使用到代理模式了。
与装饰器模式对比
- 装饰器模式侧重于对被装饰对象的功能增强
- 代理模式侧重于对被代理对象的访问控制
静态代理 VS 动态代理
- 静态代理:一个代理场景需要构造一个代理类,造成的问题是导致系统中的代理类过多
- 动态代理:被代理的类是代码运行时指定的,JDK中可通过实现
InvocationHandler
接口来实现动态代理
JDK 动态代理也有不足之处,它要求被代理类一定要实现某个接口,比如上面的 Station 类实现了 TicketSell 接口。如果我们的类原本是没有实现接口的,总不能为了用代理而特意去给它加一个接口吧?
优缺点比较
- 优点
- 扩展性强,对象更智能。
- 缺点
- 代理类由于做了很多额外的操作,可能使请求速度变慢。
实例
静态代理
Calculator.java
/**
* @Description 计算器的接口
* @Date 2020/3/8 17:27
**/
public interface Calculator {
int add(int a, int b);
}
CalculatorImpl.java
/**
* @Description 计算器接口Calculator的一个具体实现类
* @Date 2020/3/8 17:50
**/
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
CalculatorProxy.java
/**
* @Description 计算器的代理类
* @Date 2020/3/8 17:50
**/
public class CalculatorProxy implements Calculator {
private Calculator calculator;
public CalculatorProxy(Calculator calculator) {
this.calculator = calculator;
}
@Override
public int add(int a, int b) {
// 具体执行前可以做的工作
int result = calculator.add(a, b);
// 具体执行后可以做的工作
return result;
}
}
单元测试
public class ProxyTest {
@Test
public void testStaticProxy() {
Calculator calculator = new CalculatorImpl();
CalculatorProxy calculatorProxy = new CalculatorProxy(calculator);
int result = calculatorProxy.add(1, 2);
System.out.println(result);
}
}
在上面的代码中,我们定义了一个接口Calculator
、一个具体的实现类CalculatorImpl
和一个代理类CalculatorProxy
。在CalculatorProxy
类的实现中,我们可以看到add方法中调用了真正的实现类的add方法,并且在调用的真实方法之前和之后都有机会做一些额外的工作,例如记录日志、记录执行时间等。这种方式看上去非常直接,实现页比较方便,不过存在一个问题,即如果需要对多个类进行代理,并且即使代理类所需要实现的功能时一致的,我们依然需要为每个实现类都封装一个代理类,这里会涉及到大量的冗余代码的编写,后面这部分冗余代码一旦发生修改,改动量将会非常巨大。
使用动态代理将会帮助我们解决掉这个麻烦,下面我们具体看一下动态代理的实现方案。
动态代理
LogHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Description 日志打印处理器
* @Author louxiujun
* @Date 2020/3/8 18:02
**/
public class LogHandler implements InvocationHandler {
private Object object;
public LogHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 模拟前置处理逻辑
this.doBefore();
Object result = method.invoke(object, args);
// 模拟后置处理逻辑
this.doAfter();
return result;
}
private void doBefore() {
System.out.println("do something before action");
}
private void doAfter() {
System.out.println("do something after action");
}
}
编写动态代理类DynamicProxy.java
import com.netease.learn.designPattern.proxy.staticProxy.Calculator;
import java.lang.reflect.Proxy;
/**
* @Description 动态代理类
* @Author louxiujun
* @Date 2020/3/8 18:06
**/
public class DynamicProxy implements Calculator {
Calculator calculator;
public DynamicProxy(Calculator calculator) {
this.calculator = calculator;
}
@Override
public int add(int a, int b) {
LogHandler logHandler = new LogHandler(calculator);
Calculator proxy = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(),
calculator.getClass().getInterfaces(),
logHandler);
return proxy.add(a, b);
}
}
单元测试:
public class ProxyTest {
/**
* 动态代理测试
*/
@Test
public void testDynamicProxy() {
Calculator calculator = new CalculatorImpl();
DynamicProxy dynamicProxy = new DynamicProxy(calculator);
int result = dynamicProxy.add(1, 2);
System.out.println(result);
}
}
输出结果:
do something before action
do something after action
3
从上面的代码我们可以看到动态代理的使用方式及动态代理本身的实现。通过Proxy.newProxyInstance
来创建代理的方法可以为不同的委托类都创建代理类,在具体代理的实现上,所给出的是通用的实现方式,被代理的方法都会进入invoke方法中,我们可以在invoke的内部做很多的事情。从上面的代码可以看到,使用动态代理之后,对于同样的事情代理都只需要实现一次即可,就可以提供给多个不同的委托类使用了,提高了在需要使用到代理类的场景下代码的复用性和后期的可维护性。
小结
代理在设计模式这边叫代理模式,它的具体实现上通常有静态代理和动态代理之分,本身主要借助JDK提供的API给出了动态代理实现的Demo,它是借助反射JDK反射的机制来实现的,大家在后面学习Spring AOP章节的时候,还会接触到cdlib动态代理的实现,它则是通过更高级的字节码增强技术来实现的。
网友评论