前言
外包、中介、经纪人... ...。在我们生活中,类似于这种委托与代理的工作形式均可以称为代理模式。委托者将自己的工作、商品、服务等等委托给另一方,让其去完成相应的工作、服务,以保证整个环节的专业、效率、成本等等受到影响。
概念
为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
类图

从上类图我们可以看到代理接口(Subject)、代理类(ProxySubject)、委托类(RealSubject)形成一个“品”字结构。
根据代理类的生成时间可以将其分为动态代理和静态代理。动态代理又可以分为jdk代理和cglib代理,后者是spring的,所以我们今天只谈静态代理和jdk代理。
静态代理
静态代理是需要开发人员手动的去自己扩展编写代理对象及实现,相比灵活度低于动态代理,另外,静态代理和装饰者模式非常相像,两者的重点在于装饰者模式在于对原对象的功能扩展,着重点在于扩展,而静态代理在于对被代理对象进行业务方法的限制;另外装饰者模式被装饰的对象对外是暴露的,而静态代理隐藏的。
案例(静态代理)
以下我们根据上边类图模拟一个案例,委托类只有一个打印方法,代理类需要执行委托类的委托任务,还要计算出耗时
一个公共的任务接口
public interface IUserDao {
void save() throws InterruptedException;
}
一个接口实现(委托类)
public class UserDao implements IUserDao {
public void save() throws InterruptedException {
Thread.sleep(2000);
System.out.println("----------save---------");
}
}
另一个接口实现(代理类)它需要实现与委托类同样的接口,因为要完成委托类的任务
public class UserDaoProxy implements IUserDao {
//接收目标对象
private IUserDao target;
public UserDaoProxy (IUserDao target){
this.target = target;
}
public void save() throws InterruptedException {
Long start = System.currentTimeMillis();
//System.out.println("执行扩展功能,如开启事务... ...");
System.out.println("start:" + start);
target.save();//执行目标对象的方法
Long end = System.currentTimeMillis();
//System.out.println("执行扩展功能,如关闭事务... ...");
System.out.println("end:" + end);
System.out.println("处理时间为:" + (end-start) + " 毫秒");
}
}
一个测试类
public class App {
@Test
public void testSave() throws InterruptedException{
IUserDao target = new UserDao();
//代理
IUserDao proxy = new UserDaoProxy(target);
proxy.save();//这里执行的是代理对象的save方法,代理对象的save方法是目标对象save方法的扩展
}
/**
* 静态代理:
*
* 实现原则:代理对象,要实现与目标对象一样的接口;
* 1,代理类一般要持有一个被代理的对象的引用。
2,对于我们不关心的方法,全部委托给被代理的对象处理。
3,自己处理我们关心的方法。
*
* 举例:
保存用户(模拟)
Dao , 直接保存
DaoProxy, 给保存方法添加事务处理
*
* 优点:可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
* 缺点:
* 1.因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。
* 2.一旦接口增加方法,目标对象与代理对象都要维护。
*/
}

静态代理优缺点
优点:业务类只需要关注业务逻辑本身
缺点:(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 (2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
动态代理在于可以动态的对被代理对象进行方法的动态扩展或者约束。动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
动态代理主要基于javaAPI中java.lang.reflect.Proxy 实现,这是java代理机制的父类,它提供了一组static方法方便开发人员调用。以下为Proxy源码
// 方法 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)
这里最主要的方法莫过于newProxyInstance了,从方法名字中我们可以猜出这是用来实例代理对象的方法。这个方法有三个重要的参数,第一个是ClassLoader类加载器,这个可以通过当前类的Class文件进行获取、第二个Class[],即要代理类的接口数组,最后一个是InvocationHandler对象,而最终要的就是这个参数,InvocationHandler 是一个接口,很明显这里需要的是其实现了,看InvocationHandler 源码我们可以发现,其接口内定义了一个方法,我们来看看其源码
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过以上源码我们可以看到,InvocationHandler 接口中只有一个方法invoke,这个方法同样有三个参数,第一个Object势必是被代理对象,第二个Method则是代理对象所需要运行的方法,第三个则是一个参数数组,当被代理对象所执行的方法需要参数时,就可以传递至此。当代理对象进行方法调用时,主要通过对invoke方法的重写和扩展使得达到业务要求。先来借用向问天老师一张图

在简单了解动态代理所需的javaAPI后我们来简单实现一个案例,此案例,以访问数据库为例
一个代理接口
public interface IUserDao {
void save() throws InterruptedException;
}
一个代理接口的实现类(委托对象)
public class UserDao implements IUserDao {
public void save() throws InterruptedException {
System.out.println("----------save---------");
}
}
一个代理对象类(代理对象)
public class DynamicProxy implements InvocationHandler {
private Object source;
public DynamicProxy(Object source){
super();
this.source = source;
}
/**
* 重写代理方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();
Object result = method.invoke(source, args);
after();
return result;
}
public void before(){
System.out.println("在方法前做一些事,比如打开事务");
}
public void after(){
System.out.println("在方法返回前做一些事,比如提交事务");
}
public Object getProxyInstance() {
//接口的class
return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);
}
}
一个测试类
public class App {
@Test
public void fun1() throws InterruptedException {
// 目标对象
IUserDao target = new UserDao();
// 【原始的类型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new DynamicProxy(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.save();
}
/**
* 原则:动态代理有一个强制性要求,就是被代理的类必须实现了某一个接口,或者本身就是接口
*
* 道理其实很简单,这是因为动态代理生成的代理类是继承Proxy类的,并且会实现被你传
* 入newProxyInstance方法的所有接口,所以我们可以将生成的代理强转为任意一个代理的
* 接口或者Proxy去使用,但是Proxy里面几乎全是静态方法,没有实例方法,所以转换成Proxy
* 意义不大,几乎没什么用。假设我们的类没有实现任何接口,那么就意味着你只能将生成的代理类转
* 换成Proxy,那么就算生成了,其实也没什么用,而且就算你传入了接口,可以强转,你也用不了这
* 个没有实现你传入接口的这个类的方法。
*/
}

以上就是一个简单的动态代理,另外也可以直接以工厂方法的模式去创建代理,贴上代码就不做过多介绍了,其核心就是InvocationHandler实例方法invoke的重写。
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory (Object target){
this.target = target;
}
//给目标对象生成代理对象
public Object getProxyInstance () {
/**
* Proxy.newProxyInstance(
* loader, 指定当前目标对象使用类加载器
* interfaces, 目标对象实现的接口的类型
* h 事件处理器
* )
*/
//这里的Proxy.newProxyInstance即创建了一个代理对象,对象内执行了回调函数
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//在创建代理对象的时候,其三个参数是一直不变的,第三个参数始终是InvocationHandler
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("扩展方法执行,如开启事务!");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("扩展方法执行,如关闭事务!");
return returnValue;
}
});
}
}
步骤
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
动态代理机制特点
首先是动态生成的代理类本身的一些特点。
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 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
动态代理优缺点
优点:相比静态代理更为方便,处理更灵活,使AOP编程成为可能
缺点:可以写想不出来吗?
网友评论