代理模式

Java本身给我们提供了静态代理和动态代理两种代理方式,spring提供了基于类的代理方式:cglib.

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

1、介绍

意图:为其他对象提供一种代理,从而控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题。

关键代码:实现与被代理类的组合。

优点

  1. 职责清晰,高扩展性,智能化。
  2. 建立一套触发机制。

缺点

  1. 增加了代理对象,某些情况下会造成请求的处理速度变慢
  2. 需要额外的编码,某些情况实现很复杂

注意事项

  1. 与适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  2. 与装饰着模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

2、静态代理

静态代理:代理类在程序运行前就已存在,即人为的编写代理类代码。代理类与委托类实现同一接口或派生自相同的父类。

以下是静态代理的简单实现:

1
2
3
public interface House {
void sayHi();
}

1
2
3
4
5
6
7
8
public class HouseImpl implements House {

@Override
public void sayHi() {
System.out.println("welcome to my house!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProxyHouse implements House {

private House house;

public ProxyHouse(House house) {
this.house = house;
}

@Override
public void sayHi() {
System.out.println("I'm proxy");
house.sayHi();
}

}
1
2
3
4
5
6
7
public static void main(String[] args) {
House h = new HouseImpl();
h.sayHi();

h = new ProxyHouse(h);
h.sayHi();
}

可以实现客户与委托类之间的解耦,在不修改委托类的前提下能够做一些额外的处理。
静态代理的局限在于运行前必须编写好代理类,且针对每个需要代理方法都必须单独实现。

3、动态代理

动态代理:在程序运行时创建的代理方式,即代理类是动态生成的,而不是提前编写的。
相比于静态代理,动态代理可以很方便的对代理类进行统一处理,而不用修改每个代理类的函数,常用于权限控制、日志记录等场景。

约束:委托类必须实现接口。

以下是动态代理的简单实现:在对某个类的一个方法的调用前和调用后都要做一下日志操作

1
2
3
4
//一个普通的接口
public interface House {
void sayHi();
}
1
2
3
4
5
6
7
8
9
//委托类,需要增加日志的类
public class HouseImpl implements House {

@Override
public void sayHi() {
System.out.println("welcome to my house!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//动态代理的中介类,关键
public class DynamicProxy implements InvocationHandler {

private Object obj;//目标对象的引用,这里设计成Object类型,更具通用性

public DynamicProxy(Object obj) {
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object res = method.invoke(obj, args);//调用目标对象的方法
System.out.println("方法执行后");
return res;
}

}
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
House h = new HouseImpl();
//创建中介类,代理了委托类
DynamicProxy dynamicProxy = new DynamicProxy(h);
//获取代理类实例,代理了中介类
House instance = (House)Proxy.newProxyInstance(House.class.getClassLoader(), new Class[]{House.class}, dynamicProxy);
//代理对象调用方法
instance.sayHi();

}

Java的动态代理,实际上是两次静态代理的组合。动态代理的重点是中介类的实现,需要实现java.lang.reflect.InvocationHandler接口。

动态代理与静态代理的区别:

  1. Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现AOP编程,这是静态代理无法实现的;
  3. 解耦,如果用在web业务下,可以实现数据层和业务层的分离;
  4. 动态代理的优势就是实现无侵入式的代码扩展。静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题

4、CGLIB

在Spring AOP中,通常会用它来生成AopProxy对象。不仅如此,在Hibernate中PO(Persistant Object 持久化对象)字节码的生成工作也要靠它来完成。

以下是简单实现:

1
2
3
4
5
6
7
//一个普通类,委托类
public class HelloServiceImpl {

void sayHello() {
System.out.println("I'm helloServiceImpl");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//实现MethodInterceptor接口,回调函数,是增强方法的具体实现
public class CglibCallBack implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before");
Object invokeSuper = proxy.invokeSuper(obj, args);
System.out.println("after");
return invokeSuper;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置委托类
enhancer.setSuperclass(HelloServiceImpl.class);
//设置回调,即增强实现的类
enhancer.setCallback(new CglibCallBack());
//创建代理类
HelloServiceImpl helloService = (HelloServiceImpl)enhancer.create();
//执行方法
helloService.sayHello();
}
}

小结:

  1. 生成的代理类 HelloServiceImpl\$\$EnhancerByCGLIB$$1a4d04e8@32e6e9c3 继承委托类 HelloServiceImpl。注意:如果委托类被final修饰,那么该类不可被继承,也就不能被代理;同样,如果方法被final修饰,该方法也不可被代理。
  2. 代理类会为委托类的委托方法生成两个方法,一个是重写的sayHello()方法,另一个是CGLIB\$sayHello$()方法,直接调用父类的方法。执行代理方法时,先判断是否存在实现MethodInterceptor接口的回调类对象,如果存在,则调用MethodInterceptor中的intercept方法,否则调用父类方法(不代理)。

    此处输入图片的描述

  3. cglib中的方法调用不是通过反射实现的,而是直接的方法调用。有一个数组存放着方法的引用。

5、三者对比

代理方式 实现 优点 缺点 特点
JDK静态代理 代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 实现简单,容易理解 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 好像没啥特点
JDK动态代理 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 不需要硬编码接口,代码复用率高 只能够代理实现了接口的委托类 底层使用反射机制进行方法的调用
CGLIB动态代理 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 不能对final类以及final方法进行代理 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

参考

  1. 深入理解CGLIB动态代理机制
  2. 深入理解JDK动态代理机制
坚持原创技术分享,您的支持将鼓励我继续创作!
0%