设计模式之代理模式

什么是代理模式?

日常生活中我们经常会碰到代理模式,例如我们找房产中介帮我们介绍房子,找婚姻中介帮我们介绍对象,找保洁帮我们打理房间,找律师帮我们进行诉讼等。我们在无形中都运用到了代理模式。

为什么要使用代理?

运用代理可以使我们的生活更加便利,有了代理,我们不需要自己去找房子,不需要自己去找对象,不需要自己去打理房间,不需要自己去诉讼。当然,你也可以选择一切都自己来干,但是存在前提条件,一是你是否都具备这样的资源和能力来做这些事情,二是你是否愿意花费这么多精力和时间来做这些事情。总之,代理模式使我们各专其事,我们可以将时间浪费在美好的事情上,而不用天天被一些琐事所羁绊。

代理模式有哪些实现?

代理的实现分动态代理和静态代理,静态代理的实现是对已经生成了的JAVA类进行封装。

动态代理则是在运行时生成了相关代理类,在JAVA中生成动态代理一般有三种方式。

静态代理

- 由程序员创建或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态代理

- 在程序运行时运用反射机制动态创建而成。

静态代理

代理接口:UserDao.java

1
2
3
public interface UserDao {
void save();
}

目标对象:UserDaoImpl.java

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("正在保存用户信息。。。");
}
}

代理对象:TransactionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TransactionHandler implements UserDao {

private UserDaoImpl target;

public TransactionHandler(UserDaoImpl target){
this.target = target;
}

@Override
public void save() {
System.out.println("开启事务控制。。。");
target.save();
System.out.println("关闭事务控制。。。");
}

public static void main(String[] args) {
UserDaoImpl target = new UserDaoImpl();
UserDao userDao = new TransactionHandler(target);
userDao.save();
}
}

总的来说静态代理实现简单也容易理解,但是静态代理不能使一个代理类反复作用于多个目标对象,代理对象直接持有目标对象的引用,这导致代理对象和目标对象类型紧密耦合了在一起。如果UserDao接口下还有另一个实现类也需要进行事务控制,那么就要重新写一个代理类,这样就会产生许多重复的模版代码,不能达到代码复用的目的。而动态代理就可以很好的解决这样的问题。

动态代理

Spring AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

Spring AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理(核心)。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势

这里仍然使用上面同样的代理接口和目标对象。

InvocationHandler方式实现动态代理(JDK方式)
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* InvocationHandler方式实现动态代理(JDK动态代理)
*
*/
public class TransactionHandler1 implements InvocationHandler {

private Object target;

public TransactionHandler1(Object target) {
this.target = target;
}

/**
* @param proxy 参数是Object类型,是目标类的类加载器
* @param method 参数是Method类型,是目标类实现的接口集合
* @param args 参数是Object类型,是InvocationHandler实例
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务控制。。。");
Object result = method.invoke(target, args);
System.out.println("关闭事务控制。。。");
return result;
}

public static void main(String[] args) {
Object target = new UserDaoImpl();
TransactionHandler1 handler = new TransactionHandler1(target);
UserDao userDao = (UserDao) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
userDao.save();
}
}

之前我们发现了静态代理会产生许多重复代码,不能很好的进行代码复用,而动态代理能够很好的解决这个问题,代理类TransactionHandler实现了InvocationHandler接口,并且它持有的目标对象类型是Object,因此事务控制代理类TransactionHandler能够代理任意的对象,为任意的对象添加事务控制的逻辑。因此动态代理才真正的将代码中横向切面的逻辑剥离了出来,起到代码复用的目的。

但是JDK动态代理也有缺点,一是它的实现比静态代理更加复杂也不好理解;二是它存在一定的限制,例如它要求目标对象必须实现了某个接口;三是它不够灵活,JDK动态代理会为接口中的声明的所有方法添加上相同的代理逻辑。

cglib动态代理实现

CGLIB是一个开源项目,官方网址是:http://cglib.sourceforge.net/,可以去上面下载最新JAR包,

Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是才用类似jdk的方式,通过持有target对象来达到拦截方法的效果。

CGLIB的核心类:
​ cglib.proxy.Enhancer – 主要的增强类
​ cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
​ cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
​ Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。

本项目用的是springframework中的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
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* CGLIB方式实现动态代理(Spring使用这种方式实现)
*
*/

public class TransactionHandler2 implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy)throws Throwable{
System.out.println("开启事务控制。。。");
Object result = methodProxy.invokeSuper(target,args);
System.out.println("关闭事务控制。。。");
return result;
}

public static void main(String[] args) {
TransactionHandler2 cglibProxy = new TransactionHandler2();

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDaoImpl.class);
enhancer.setCallback(cglibProxy);

UserDao userDao = (UserDao)enhancer.create();
userDao.save();
}
}

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
38
39
40
41
42
43
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* CGLIB代理工厂方式实现动态代理(Spring使用这种方式实现)
*
*/

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 target, Method method, Object[] args, MethodProxy methodProxy)throws Throwable{
System.out.println("开启事务控制。。。");
Object result = methodProxy.invokeSuper(target,args);
System.out.println("关闭事务控制。。。");
return result;
}

public static void main(String[] args) {
// 目标对象
UserDao target = new UserDaoImpl();
// 代理对象
UserDao userDao = (UserDao)new ProxyFactory(target).getProxyInstance();
userDao.save();
}
}
通过编译期提供的API实现动态代理

假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:

我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。

参考:

http://www.cnblogs.com/cenyu/p/6289209.html

https://www.cnblogs.com/liuyun1995/p/8144628.html

https://blog.csdn.net/heyutao007/article/details/49738887

https://my.oschina.net/LiuLangEr/blog/1606667

http://www.cnblogs.com/springsource/archive/2012/08/30/2664050.html

https://yq.aliyun.com/articles/629146

看官可在此打赏