定义
Proxy(代理),顾名思义,代替客户对象和目标对象打交道,这里可能有人会疑问这跟装饰器模式有啥区别,装饰器也是可以代替客户对象访问目标对象的。
确实如此,但是装饰器强调更多是拓展功能上的设计,而代理强调更多是对 对象的控制,而且这种对象往往是不能直接可得的,而装饰器模式中,只要愿意,还是可以直接访问的,如:
- 跨服务器的访问
- 对象状态很难判定,在目标对象工作真正开始工作前并不存在,如:Spring的AOP代理模式,Hibernate的延迟加载
- 具有访问权限的一些特殊行为的对象,如在并发环境下,出于安全考虑,可能会采用代理来处理业务,而拒接直接的访问
下面从创建的角度,描述Java的三种代理模式:
传统的访问对象方式:
public Client{
public void method(){
// 创建一个FileUtil对象
FileUtil obj = new FileUtil();
// 调用对象的方法,将括号里的语句写到文件中
obj.wirteToFile("your will,my hands")
}
}
如果现在需要隐藏FileUtil对象,或者说FileUtil加了安全认证,对于直接访问它的请求,它是拒接的,此时,我们可以采用代理模式进行改进:
静态代理
//定义统一接口
public interface File {
void writeToFile(String src);
}
// 具体业务
public class FileImpl implements File {
@Override
public void writeToFile(String src) {
System.out.println("将"+src+"保存至文件中");
}
}
// 代理服务
public class FileProxy implements File {
private File target;
public FileProxy(File target){
this.target = target;
}
@Override
public void writeToFile(String src) {
System.out.println("开启事务");
target.writeToFile(src);//执行目标对象的方法
System.out.println("提交事务");
}
}
public class MainApp {
public static void main(String[] args) {
//构建目标对象
FileProxy proxy = new FileProxy(new FileImpl());
proxy.writeToFile("your will, my hands");
}
}
动态代理
上面程序是一个简单的静态代理处理的程序,它可以通过代理,在不修改目标对象的功能前提下对目标功能扩展,但是,因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护,解决方式是通过借助JDK提供的api接口实现动态代理。
jdk中,在java.lang.reflect包中有Proxy类,它字节实现的方法有4个,而且都是静态的形式:
创建的时候我们需要用到的方法是newProxyInstance:
参数:
- loader - 申明代理对象需要的类加载器(要和加载目标对象的类加载器一致)
- interfaces - 通过接口指定生成哪个对象的代理对象
- h -自己编写handler接口的实现来指定生成的代理对象的方法里干什么事
在InvocationHandler中的invoke方法又有三个参数:
- proxy:代理类实例
- method:Method类的引用,通过它可以找到例子中的writeToFile()方法
- args:表示方法的参数
public interface File {
void writeToFile(String src);
}
public class FileImpl implements File {
@Override
public void writeToFile(String src) {
System.out.println("将"+src+"保存至文件中");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
//维护一个对象
private Object target;
public DynamicProxy(Object target){
this.target = target;
}
public Object getProxObject(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象的类加载器
target.getClass().getInterfaces(), // 目标对象的接口
new 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 class MainApp {
public static void main(String[] args) {
//构建目标对象
FileProxy proxy = new FileProxy(new FileImpl());
proxy.writeToFile("your will, my hands");
}
}
Cglib(code generation library)代理
CGLIB是一个强大的高性能的代码生成包,它广泛的被许多AOP的框架使用,譬如Spring中拦截器设置。它可以弥补Java自带的Proxy框架的不足。
在上文中,我们通过静态代理和动态代理来实现请求的间接转发,但他们都有一个缺点,就是要求目标对象是实现一个接口的目标对象(FileImpl 要实现File接口),但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用Cglib代理以目标对象子类的方式类实现代理。
说明:Cglib需要我们引入jar包,这里有Maven依赖可直接用,如果有Spring了,也可以直接用pring-core-3.2.5.jar架包。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
还是以上面说的File类为例子,此时,我们将String保存到文件的类没有实现接口了,而是直接作为单独的一个类存在。
package com.fang.facade;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class File{
public void writeToFile(String src) {
System.out.println("将"+src+"保存至文件中");
}
}
class ProxyFactory implements MethodInterceptor{
//维护的对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxy(){
//创建工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws InvocationTargetException, IllegalAccessException {
System.out.println("开启事务");
//执行目标对象的方法
Object value = method.invoke(target,args);
System.out.println("提交事务");
return value;
}
}
class App {
public static void main(String[] args) {
//目标对象
File target = new File();
//代理对象
File proxy = (File)new ProxyFactory(target).getProxy();
//执行代理对象的方法
proxy.writeToFile("your will my hands");
}
}
小结
通过测试,CGLIB创建的动态代理对象比JDK创建的动态代理对象花费的时间要高的多,就上面的简单例子而言,运行时间相差就已经很明显了。
CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。在Spring的AOP编程中,如果加入容器的目标对象有实现接口,用JDK代理如果目标对象没有实现接口用Cglib代理
代理的分类
关于代理,其实有很多实际应用的例子,像远程代理中的RMI,remote method invoke,远程方法调用框架,都是代理模式的体现。