咸鱼开发修炼之路

设计模式-代理模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息、过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。 为了保持行为的一致性,代理类和委托类通常会实现相同的接口
fa30df16.png

简介

  1. 代理模式的作用:为其他对象提供一种代理以控制对这个对象的访问。
  2. 代理模式的好处:在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用;引入代理能够控制对委托对象的直接访问,可以很好的隐藏和保护委托对象,也更加具有灵活性。
  3. 代理模式涉及角色:
  • 抽象角色:声明真实对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
  • 代理角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象,同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  • 真实角色:定义了代理对象所代表的目标对象,代理角色所代表的真实对象,是我们最终要引用的对象,定义了代理对象所代表的目标对象。

代理模式可以分为静态代理和动态代理。
本文中的代码: 代理模式

静态代理

静态代理事实上就是装饰者模式,区别是装饰者模式一般都是传入一个被装饰类在构造器中,而静态代理则是自己创建一个类作为成员变量。

实现方式一 实现接口

88e55b0b.png

抽象角色:定义接口

1
2
3
public interface Image {
void display();
}

真实角色:实现Image接口
1
2
3
4
5
6
7
8
9
10
11
12
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
System.out.println("ImageName :" + this.fileName);
}
}

代理角色:代理类同样继承Image接口,在代理类中创建了真实对象,通过代理类调用真实对象方法前后可以进行一些操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyImage implements Image {
private RealImage realImage;
public ProxyImage(String fileName) {
this.realImage = new RealImage(fileName);
}
@Override
public void display() {
System.out.println("--格式校验--");
realImage.display();
System.out.println("--记录日志--");
}
}

测试类:通过代理创建Image对象,调用方法
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
image.display();
}
}

测试结果:
9f74d222.png

实现方式二 继承

通过继承的方式也能实现静态代理
db98c6f2.png

抽象角色:定义父类(此处使用抽象类)

1
2
3
public abstract class Image {
abstract void display();
}

真实角色:继承抽象类,重写父类方法
1
2
3
4
5
6
7
8
9
10
11
12
public class RealImage extends Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
}
@Override
void display() {
System.out.println("file name :" +this.fileName);
}
}

代理角色:代理类同样继承抽象类并重写方法,通过代理类的方法调用真实对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyImage extends Image {
private RealImage realImage;
public ProxyImage(String fileName) {
realImage = new RealImage(fileName);
}
@Override
void display() {
System.out.println("--格式校验--");
this.realImage.display();
System.out.println("--记录日志--");
}
}

静态代理的缺陷

当需要代理的方法比较多时,需要为每个方法编写包装代码,代码量会十分庞大,但是逻辑总是相似的;
另一方面,当我们接口改变的时候,需要更改每一个代理类。

为了解决这个问题,提出了动态代理的方案。

动态代理

Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地制定一组接口及委托类对象,便能动态地获得代理类。
代理类会负责将所有的方法调用分配到委托对象上反射执行。

动态代理可以通过jdk自带的类和CGLIB实现。

JDK动态代理原理是利用反射机制生成一个实现代理接口的匿名类
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

Java内置的动态代理

相关类和接口

  • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
1
2
3
4
5
6
7
8
9
10
11
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
  • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。
  • java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
    每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

代理机制及使用方法

使用 Java 动态代理。具体有如下四步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..);
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
简化的动态代理对象创建过程
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );

生成的代理类的特点

  1. 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
  2. 类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
  3. 类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
  4. 类继承关系:该类的继承关系如图9f188756.png

example

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
public class ProxyImageHandler implements InvocationHandler {
private Image realImage;
private ProxyImageHandler(String fileName) {
this.realImage = new RealImage(fileName);
}
/**
* 通过Proxy的newProxyInstance方法来创建我们的代理对象
* 第一个参数 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
* 第二个参数 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
* 第三个参数 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
* @param fileName
* @return
*/
public static Image createImage(String fileName){
Image image = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new Class[]{Image.class},new ProxyImageHandler(fileName) );
System.out.println("动态创建对象类名为:"+image.getClass().getName());//动态生成的代理类名格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类
return image;
}
/**
*
* @param proxy 指代我们所代理的那个真实对象
* @param method 指代的是我们所要调用真实对象的某个方法的Method对象
* @param args 指代的是调用真实对象某个方法时接受的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("display")){
System.out.println("--身份认证--");
method.invoke(this.realImage , args);//调用真实对象的方法
System.out.println("--记录日志--");
}else {
method.invoke(this.realImage , args);
}
return null;
}
}

测试:

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args){
Image image1 = ProxyImageHandler.createImage("test.jpg");
image1.display();
image1.delete();
}
}

运行结果:

1
2
3
4
5
6
"C:\Program Files\Java\jdk1.8.0_101\bin\java" ...
动态创建对象类名为:com.sun.proxy.$Proxy0
--身份认证--
display:test.jpg
--记录日志--
delete:test.jpg

JDK动态代理的局限

JDK动态代理原理采用的是静态代理中实现interface的方式,不支持对普通类的代理。
如果需要代理普通的类,就需要使用CGLIB等第三方库。

CGLIB动态代理

什么是CGLIB

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。
Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。
CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib。
相对JDK动态代理,CGLIB功能更为强大,使用也相对复杂,详细说明可以参考 这里

CGLIB组成结构

a2916974.png

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。
除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。

example

由于CGLIB属于第三方库,在使用时需要引入相关架包。
maven:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

Enhancer可能是CGLIB中最常用的一个类,和JDK1.3动态代理中引入的Proxy类差不多。
和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。
Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。
Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。
基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ProxyImage {
public static Image createProxyImage(String fileName){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Image.class); //设置父类型
Image image = new Image(fileName);
enhancer.setCallback((InvocationHandler) (o, method, objects) -> { //setCallback可传入一个实现InvocationHandler或MethodInterceptor等继承自Callback的接口的对象
if(method.getName().equals("display")){
System.out.println("--身份认证--");
method.invoke(image, objects);//调用真实对象的方法
System.out.println("--记录日志--");
}else {
method.invoke(image, objects);
}
return null;
});
Image imageProxy =(Image) enhancer.create();//创建代理对象
System.out.println("代理类名称:"+ imageProxy.getClass().getName());
return imageProxy;
}
}

测试类:
1
2
3
4
5
6
7
public class Client {
public static void main(String[] args){
Image image = ProxyImage.createProxyImage("test.jpg");
image.display();
image.delete();
}
}

运行结果:
1
2
3
4
5
代理类名称:proxy.dynamicProxy.cglib.Image$$EnhancerByCGLIB$$b16c030d //使用CGLIB生成的类为被代理类的一个子类
--身份认证--
display:test.jpg
--记录日志--
delete:test.jpg

对比

Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

参考资料

https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/

http://blog.csdn.net/danchu/article/details/70238002

https://www.zhihu.com/question/20794107