Java的三种代理模式

  面向对象设计模式有个原则叫“开闭原则”,就是对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的,对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。
  代理(Proxy)设计模式中的一种,就是为一个对象创建一个替身,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,而不用修改原始类的代码。

静态代理

代理模式

接口

1
2
3
public interface Subject {
void request();
}

实现类

1
2
3
4
5
6
7
public class RealSubject implements Subject {
public void request() {
System.out.println("实现类运行")
}
}

代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StaticProxy implements Subject {
private RealSubject realSubject;
public StaticProxy(Subject subject) {
realSubject = subject;
}
public void request() {
preRequest();
System.out.println("实现类运行");
postRequest();
}
private void preRequest() {
System.out.println("运行之前操作");
}
private void postRequest() {
System.out.println("运行之后操作");
}
}

1
2
3
4
5
6
7
8
public class ProxyTest {
public void testProxy() {
Subject subject = new RealSubject();
Subject proxy = new StaticProxy(subject);
proxy.request();
}
}

运行结果是:

1
2
3
4
运行之前操作
实现类运行
运行之后操作

  从运行的结果看来,StaticProxy代理类既保留了RealSubject原始类的操作,又在其方法的前与后扩展了新的功能,有没有对RealSubject的源码进行修改。但这种方式也有明显的缺点:

  1. 代理类必须与目标对象实现一样的接口。
  2. 如果接口有变更,比如增加了方法,代理类又得重新维护。
  3. 对于多个接口的代理,可能需要创建多个代理类,代码量增加。

  针对这些缺点,Java引入了动态代理。

动态代理

调用处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler{
private Subject subject;
public ProxyHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("运行前操作");
method.invoke(subject, args);
System.out.println("运行后操作");
return null;
}
}

  InvocationHandler是代理实例的调用处理程序 实现的接口,每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Proxy;
import org.junit.Test;
public class ProxyTest {
@Test
public void testProxy() {
Subject subject = new RealSubject();
ProxyHandler handler = new ProxyHandler(subject);
Subject proxySubject = (Subject)Proxy.newProxyInstance(ProxyHandler.class.getClassLoader(),
new Class [] {Subject.class}, handler);
proxySubject.request();
}
}

运行结果:
1
2
3
4
运行之前操作
实现类运行
运行之后操作

  和静态代理的区别是,proxySubject这个代理对象是动态创建的,执行这个代理对象的任何方法是,会被指派到ProxyHandler类的invoke()方法。
  动态代理只需通过一个简单的类简单的方法,就能够代理多个接口的多个方法,有点类似是个门面,在执行被代理对象的方法时,所有方法最终都会执行这个代理类的invoke()方法。
  ProxyHandler(Subject subject)这个构造方法注入了一个Subject对象,这个步骤不是必须,ProxyHandler和Subject,这两个类可以完全没有任何关联。下面Mybatis-Spring中使用动态代理的例子中,就没有任何关联。

InvocationHandler的Lambda表达式

  InvocationHandler是个函数式接口,可以直接用lambda表达式

1
2
3
4
5
6
Subject proxySubject = (Subject)Proxy.newProxyInstance(ProxyHandler.class.getClassLoader(),
new Class [] {Subject.class},
(proxy,method,args) - > {
method.invoke(obj,args);
}
);

Mybatis-Spring中使用动态代理

  在用mybatis的数据操作时,调用方法之前自动获取SqlSession对象,在调用方法之后自动提交事务和关闭SqlSession对象。这里会用AOP技术,而在Mybatis-Spring就是用动态代理来实现AOP。
SqlSessionTemplate的应用
SqlSessionTemplate
阅读SqlSessionTemplate的源码会发现,在这个类创建了一个动态代理类。
SqlSessionTemplate
  获取SqlSession对象->提交事务->关闭SqlSession对象,这些操作都是SqlSessionTemplate这个类直接处理,所以我们在Dao写数据操作时,直接调用SqlSessionTemplate的方法,而不用写事务相关的代码。

Cglib代理

  上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
目标对象类

1
2
3
4
5
6
7
8
9
/**
* 目标对象,没有实现任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}

Cglib代理工厂类
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
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}

测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}

参考资料