由代理的创建时期,可以将代理分为静态代理动态代理

动态代理可以类比多态

定义

代理模式就是给一个对象提供一个代理,我们可以通过代理对象实现对原对象的控制和引用

代理对象就是对原对象的代理,任何想与原对象进行交互的行为都需要通过代理对象来实现。

代理模式使得原对象不必暴露在外,由代理对象作为中介与外部交互,在一定程度上保护了原对象。同时也减少了系统的耦合度。

代理模式可以:一 保护目标对象,二 增强目标对象。

优缺点

优点: 1、职责清晰。 2、高扩展性。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

模式结构

  • 抽象主题类( Subject

    抽象主题类主要是接口或者抽象类

  • 真实主题类 (类, Real Subject

    真实主题类是实现了接口或者抽象类中方法的类,是代理对象所代理的真实对象,

  • 代理类 ( Proxy

类图

image.png

注:图片来自菜鸟教程

静态代理

静态代理指的是代理类人为地创建代理类,即在程序运行前代理类的 .class 文件就已经存在。

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
package proxy;
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}

运行结果:

1
2
3
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。

🌰2:

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
47
48
49
50
51
52
53
public interface Image {
void display();
}

public class RealImage implements Image {

private String fileName;

public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}

@Override
public void display() {
System.out.println("Displaying " + fileName);
}

private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}

public class ProxyImage implements Image{

private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

public class ProxyPatternDemo {

public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");

// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}

动态代理

动态代理指的是运行时通过反射机制动态地创建。

JDK 动态代理

JDK 动态代理机制中有两个重要的类和接口:

  • InvocationHandler(接口)
  • Proxy(类)

Proxy 和 InvocationHandler 是 JDK 实现动态代理的核心

InvocationHandler

官方描述

{@code InvocationHandler} is the interface implemented by the invocation handler of a proxy instance.

When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the {@code invoke} method of its invocation handler.

解释:

首先,InvocationHandler 是一个接口。

其次,Proxy 类中存在 InvocationHandler 成员变量,每个代理实例都要关联一个实现了 InvocationHandler 接口的调用类。

当我们通过动态代理对象调用一个方法的时候,这个方法的调用就会被转发到实现 InvocationHandler 接口类的 invoke 方法来调用

1
2
3
4
5
6
7
/**
* proxy : 动态代理类实例 com.sun.proxy.$Proxy0
* method : 我们所要调用某个对象真实的方法的Method对象
* args : 指代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

Proxy

Proxy 是一个可以用来创建代理类实例的类,newProxyInstance方法可以创建一个代理类对象,返回的是指定接口的代理类实例

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation

​ handler.

1
2
3
public static Object newProxyInstance(ClassLoader loader, 
Class<?>[] interfaces,
InvocationHandler h)
  • ClassLoader loader:一个 classloader 对象,定义了由哪个 classloader 对象对生成的代理类进行加载
  • Class<?>[] interfaces:一个interface对象数组,表示我们代理对象需要实现的接口。如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口代理类就可以调用接口中声明的所有方法
  • InvocationHandler h:一个 InvocationHandler 对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上

代理对象不需要实现接口,但是目标对象(被代理的对象)一定要实现接口,否则不能用动态代理。

代码实现

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
47
48
49
50
51
52
53
54
55
56
57
public interface Eat {
public void eat();
}
public class Breakfast implements Eat{
@Override
public void eat(){
System.out.println("恰早餐");
}
}
package proxy.dynamic;

public class Dinner implements Eat{
@Override
public void eat(){
System.out.println("恰晚饭");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class EatHandler implements InvocationHandler {
private Eat eat;
public EatHandler(){ }
public EatHandler(Eat eat){
this.eat = eat;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
System.out.println("proxy.getClass().getName(): " + proxy.getClass().getName());
System.out.println("eat.getClass().getName(): " + eat.getClass().getName());
System.out.println("method.getClass().getName(): " + method.getName());
Object result = method.invoke(this.eat,args);
after();
return result;
}

private void before(){
System.out.println("做饭");
}
private void after(){
System.out.println("洗碗");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
public static void main(String args[]){
Eat eatLunch = new Lunch();
InvocationHandler eatHandler = new EatHandler(eatLunch);

Eat eatLunchProxy = (Eat)Proxy.newProxyInstance(eatLunch.getClass().getClassLoader(),eatLunch.getClass().getInterfaces(),eatHandler);
eatLunchProxy.eat();
}
}
1
2
3
4
5
6
做饭
proxy.getClass().getName(): com.sun.proxy.$Proxy0
eat.getClass().getName(): proxy.dynamic.Lunch
method.getClass().getName(): eat
恰午饭
洗碗

invoke 接口中的 proxy 参数不能用于调用所实现接口的某些方法否则会栈溢出

CGLib 动态代理

JDK动态代理基于接口的方式

  • 被代理类需要有接口

  • 代理类和目标类需要实现同一个接口

CGLib动态代理代理类去继承目标类(不强制被代理类(目标类)有接口),然后重写其中目标类的方法,这样也可以保证代理类拥有目标类的同名方法。

JDK 动态代理提供一个 Proxy 类来创建代理类;

CGLib 动态代理提供类 Enhancer 创建代理类.

代码实现:

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
47
48
49
50
51
52
53
54
55
56
public class Dog {
public void run(){
System.out.println("dog run" );
}
}
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLibInterceptor implements MethodInterceptor{

@Override
/**
* 方法拦截借口
* Object o: 代理类对象
* Method method: 被拦截的方法
* Object[] objects: 被拦截方法的参数
* MethodProxy methodProxy: 代理类对应目标类的代理方法
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.printf("发起调用 obj: %s, method: %s, proxy: %s\n",
o.getClass().getCanonicalName(),
method.getName(),
methodProxy.getClass().getCanonicalName()
);
System.out.println("CGLIB 调用前");
// 不是用的反射
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("CGLIB 调用后");
return object;
}
}
package proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;

public class CGLibProxy {
static public void main(String[] args){
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Dog.class);
//设置回调函数
enhancer.setCallback(new CGLibInterceptor());
// 创建代理类dog
Dog dogProxy = (Dog)enhancer.create();
// 调用代理类的方法
dogProxy.run();

}
}
发起调用 obj: proxy.cglib.Dog$$EnhancerByCGLIB$$7adef180,
method: run, proxy: org.springframework.cglib.proxy.MethodProxy
CGLIB 调用前
dog run
CGLIB 调用后

动态代理和静态代理的区别

静态代理和动态代理主要有以下几点区别:

  • 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  • 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

应用场景

  • 远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  • 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

参考

菜鸟教程

C语言中文网

互联网优秀博客