What
代理模式(Proxy Design Pattern),旨在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
场景
- 业务系统的非功能性需求开发:
代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
如果你熟悉 Java 语言和 Spring 开发框架,这部分工作都是可以在 Spring AOP 切面中完成的。前面我们也提到,Spring AOP 底层的实现原理就是基于动态代理。
- 在 RPC中的应用:
实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
- 在缓存中的应用:
假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。
How
静态代理
适合被代理类很少,且增强逻辑并不普适的场景。比如:不能改变源码的前提下,做一些具体业务逻辑的增强。
- 静态代理存在的问题:
如果有50个被代理类需要添加相同的增强(比如记录记录执行时间),那我们就要创建 50 个对应的代理类。这会导致项目中类的个数成倍增加,增加了代码维护成本。并且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。
通过接口实现
- 场景:
所有自己实现的代理类,首选通过接口的方式实现。 - 步骤:
- 代理类和原始类实现相同的接口;
- 原始类只负责业务逻辑,代理类负责业务代码前后附加其他非业务逻辑;
- 代理类通过委托的方式调用原始类来执行业务代码。
```java
/**
- 接口式 *
- @author abley
- @date 2022/1/5 10:12 */ public class InterfaceWay { public static void main(String[] args) { CircleProxy circleProxy = new CircleProxy(new Circle()); circleProxy.draw(); } }
/**
- 图形接口类
/
interface Shape {
/*
- 画图接口 */ void draw(); }
/**
圆形 */ class Circle implements Shape {
@Override public void draw() {
System.out.println("圆形绘制中...");
} }
/**
圆形代理类 */ class CircleProxy implements Shape { private Circle circle;
public CircleProxy() {
this.circle = new Circle();
}
@Override public void draw() {
System.out.println("准备开始画圆形:"); circle.draw(); System.out.println("圆形画图完毕!");
} } ```
通过继承实现
- 场景:
如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的,对于这种外部类的扩展,我们一般都是采用继承的方式。 - 步骤:
- 代理类继承原始类;
- 原始类只复杂业务逻辑,代理类负责业务代码前后附加其他非业务逻辑;
- 代理类通过直接调用父类(原始类)的方式来执行业务代码。
```java
/**
- 继承式 *
- @author abley
- @date 2022/1/5 10:13 */ public class ExtendsWay { public static void main(String[] args) { new TriangleProxy().draw(); } }
/**
三角形 */ class Triangle {
public void draw() {
System.out.println("三角形形绘制中...");
} }
/**
三角形代理类 */ class TriangleProxy extends Triangle {
@Override public void draw() {
System.out.println("准备绘制三角形:"); super.draw(); System.out.println("三角形绘制完成!");
} }
<a name="V4Er7"></a>
## 动态代理
> **动态代理**(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。因此,动态代理可以解决静态代理存在的问题。
- **优点:**
动态代理中所说的“动态”,是针对使用Java代码实际编写了代理类的“静态”代理而言的,它的真正优势不在于省去了编写代理类那一点编码工作量,而是**实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景之中**。
在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLIB 代理效率,只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理。
<a name="xsmD8"></a>
### 基于Java反射实现
> 利用**反射机制**,动态生成一个代理接口的匿名类,通过InvokeHandler 来代理替换调用。
注意:因为JDK动态代理是采用的接口式的,所以如果想要实现 JDK 动态代理那么代理类必须实现接口,否则不能使用。<br />**HOW**
```java
/**
* JDK动态代理
* <p>
* 利用拦截器(必须实现 InvocationHandler 接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。
* 注意:因为JDK动态代理是采用的接口式的,所以如果想要实现 JDK 动态代理那么代理类必须实现接口,否则不能使用。
*
* @author abley
* @date 2022/1/5 10:48
*/
public class JdkDynamicProxyWay {
@SneakyThrows
public static void main(String[] args) {
ILanguage chinese = new TimeProxy<>(new Chinese()).bind();
//实际会调用TimeProxy#invoke
chinese.sayHi();
ILanguage english = new TimeProxy<>(new English()).bind();
english.sayHello();
/**
* 外部创建方式
*/
Chinese chinese1 = new Chinese();
//创建一个InvocationHandler对象
InvocationHandler handler = new TimeProxy<>(chinese1);
//使用Proxy生成一个动态代理类
Class<?> proxyClass = Proxy.getProxyClass(chinese1.getClass().getClassLoader(), ILanguage.class);
//获取动态代理类中带一个InvocationHandler参数的构造器
Constructor<?> ctor = proxyClass.getConstructor(InvocationHandler.class);
//通过ctor构造对象来创建动态实例
ILanguage chinese2 = (ILanguage) ctor.newInstance(handler);
chinese2.sayHello();
}
}
/**
* 语言接口
*/
interface ILanguage {
void sayHello();
void sayHi();
}
/**
* 中文
*/
class Chinese implements ILanguage {
@Override
public void sayHello() {
System.out.println("哈喽!");
}
@Override
public void sayHi() {
System.out.println("嗨!");
}
public String to() {
return "ChinaHello#toString";
}
}
class English implements ILanguage {
@Override
public void sayHello() {
System.out.println("hello!");
}
@Override
public void sayHi() {
System.out.println("hi!");
}
}
/**
* 时间代理类
* 记录被代理函数的执行时间
*/
class TimeProxy<T> implements InvocationHandler {
/**
* 被代理对象
*/
T originalObj;
/**
* 构造
*
* @param originalObj 被代理对象
*/
public TimeProxy(T originalObj) {
this.originalObj = originalObj;
}
/**
* 绑定代理类
* 该代理类同样实现了原始类所有实现的接口,并持有InvocationHandler对象
*
* @return 代理类
*/
T bind() {
return (T) Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),
originalObj.getClass().getInterfaces(), this);
}
/**
* @param proxy 代理对象
* @param method 正在执行的方法
* @param args 调用目标方法时传入的实参
* @return 代理方法返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("start:" + start);
//调用原始类的方法(具体那个方法会根据原始类动态调用的方法决定)
Object invoke = method.invoke(originalObj, args);
long end = System.currentTimeMillis();
System.out.println("end:" + end);
System.out.println(originalObj.getClass().getName() + "#" + method.getName() + " 方法耗时:" + (end - start));
return invoke;
}
}
基于CGLib实现
利用 ASM 框架,对代理对象类生成的 class 文件加载进来,通过修改其字节码生成子类来进行代理。 注意:如果想要使用 CGlib 动态代理,那么代理类不能使用 final 修饰类和方法。
HOW
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public interface UserService {
void addUser();
void updateUser(String str);
}
/**
* 被代理类
*/
public class UserServiceImpl {
public void addUser() {
System.out.println("添加了一个用户");
}
public void deleteUser() {
System.out.println("删除了一个用户");
}
}
/**
* UserServiceCGlib 代理
*/
public class UserServiceCGlib implements MethodInterceptor {
private Object target;
public UserServiceCGlib() {
}
public UserServiceCGlib(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("增强开始~~~");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("增强结束~~~");
return result;
}
}
public class test {
public static void main(String[] args) {
UserServiceCGlib serviceCGlib = new UserServiceCGlib(new UserServiceImpl());
UserServiceImpl userService = (UserServiceImpl)serviceCGlib.getProxyInstance();
userService.addUser();
System.out.println();
userService.deleteUser();
}
}
=======console=======
增强开始~~~
添加了一个用户
增强结束~~~
增强开始~~~
删除了一个用户
增强结束~~~
场景
Spring AOP
以下是 Spring AOP 创建代理的方法:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//如果
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
- 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理;
- 如果目标对象实现了接口,也可以强制使用 CGLIB3、如果目标对象没有实现了接口,必须采用 CGLIB 库,spring 会自动在 JDK 动态代理和 CGLIB 之间转换。
如果需要强制使用 CGLIB 来实现 AOP,需要配置 spring.aop.proxy-target-class=true 或@EnableAspectJAutoProxy(proxyTargetClass= true