深入理解代理模式:以明星和经纪人示例,不修改原代码扩展功能的访问方式
这三种代理模式属于 Java 的范畴。

代理是一种设计模式。它提供了对目标对象的另外一种访问方式。也就是通过代理对象来访问目标对象。这样做有好处,能够在目标对象实现的基础上,增强额外的功能操作,也就是扩展目标对象的功能。
这里运用了编程里的一个理念:不要随意对他人已写好的代码或方法进行修改。倘若需要修改,能够借助代理的方式来对该方法进行扩展。
以邀请一位明星为例来阐释代理的作用:倘若我们想要邀请一位明星,并非直接与明星取得联系,而是去联系明星的经纪人,以此来达成相同的目的。明星是目标对象,其只需负责活动中的节目,而其他繁杂琐碎的事宜则交由他的代理人(经纪人)去处理。这便是代理思想在现实中的一个实例。
用图表示如下:
代理模式的关键点在于:存在代理对象以及目标对象。代理对象对目标对象进行了扩展,并且会调用目标对象。
1.1.静态代理
使用静态代理时,需要定义接口或者父类。被代理对象与代理对象要一起实现相同的接口,或者继承相同的父类。
下面举个案例来解释:
模拟保存动作,定义一个接口叫 IUserDao.java 用于保存动作。接着目标对象实现这个接口的方法在 UserDao.java 中。倘若使用静态代理方式,那就得在代理对象(UserDaoProxy.java)里也实现 IUserDao 接口。在调用时,通过调用代理对象的方法来调用目标对象。
需要注意的是,代理对象需实现与目标对象相同的接口,接着通过调用相同的方法,从而实现调用目标对象的方法。
代码示例:
接口:IUserDao.java
/**
* 接口 */ public interface IUserDao { void save(); }
目标对象:UserDao.java
/**
* 接口实现 * 目标对象 */ public class UserDao implements IUserDao { public void save() { System.out.println("----已经保存数据!----"); } }
代理对象:UserDaoProxy.java
/**
* 代理对象,静态代理 */ public class UserDaoProxy implements IUserDao{ //接收保存目标对象 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("开始事务..."); target.save();//执行目标对象的方法 System.out.println("提交事务..."); } }
测试类:App.java
/**
* 测试类 */ public class App { public static void main(String[] args) { //目标对象 UserDao target = new UserDao(); 代理对象将目标对象传递给自身,从而建立起代理关系,即 UserDaoProxy proxy =new UserDaoProxy(target); proxy.save();//执行的是代理的方法 } }
静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
1.2.动态代理
动态代理有以下特点:
1.代理对象,不需要实现接口
代理对象的生成,是借助 JDK 的 API,在内存中动态地构建出代理对象。并且,我们需要指定创建代理对象或目标对象所实现的接口的类型。
3.动态代理也叫做:JDK代理,接口代理
JDK中生成代理对象的API
代理类所在的包是 java.lang.reflect.Proxy 。
JDK 实现代理只需运用 newProxyInstance 方法,然而此方法需要接收三个参数,其完整的写法为:
static Object newProxyInstance(ClassLoader loader, Class>接口、调用处理器 h 。
该方法位于 Proxy 类中,它是一个静态方法,接收的三个参数分别为:
代码示例:
接口类 IUserDao.java 以及其接口实现类,目标对象为 UserDao,二者是相同的,且未进行修改。在此基础上,新增一个代理工厂类(ProxyFactory.java),把代理类写在该类中。接着在测试类(需要使用到代理的代码)中,先建立目标对象与代理对象的联系,然后调用代理对象的同名方法。
代理工厂类:ProxyFactory.java
/**
* 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */ public class ProxyFactory{ //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象生成代理对象 public Object getProxyInstance(){ 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("开始事务2"); 执行目标对象的方法,得到返回值 Object returnValue ,这个返回值是由 method 产生的。invoke(target, args); System.out.println("提交事务2"); 返回 returnValue 。
测试类:App.java
/**
* 测试类 */ public class App { public static void main(String[] args) { // 目标对象 IUserDao target = new UserDao(); 【原始类型为 cn.itcast.b_dynamic.UserDao】System.out.println(target.getClass()); 为目标对象创建代理对象,该代理对象为 IUserDao 类型,即 IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance(); 在内存中动态生成的代理对象是 class $Proxy0 ,它属于 System 。out.println(proxy.getClass()); // 执行方法 【代理对象】 proxy.save(); } }
总结:
代理对象无需实现接口,然而目标对象必须实现接口,不然就无法使用动态代理。
1.3.Cglib代理
上面的静态代理模式和动态代理模式都要求目标对象是实现一个接口的目标对象。然而,有时候目标对象只是一个单独的对象,并未实现任何接口。在这种情况下,就可以通过以目标对象子类的方式来实现代理,这种方法被叫做 Cglib 代理。
Cglib 代理,也被称作子类代理。它通过在内存中构建一个子类对象,以此来实现对目标对象功能的扩展。
Cglib子类代理实现方法:
需要引入 cglib 的 jar 文件。然而,Spring 的核心包中已经包含了 Cglib 功能。因此,只需直接引入 spring-core-3.2.5.jar 就可以了。
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
目标对象的方法若为 final 或 static ,则不会被拦截,也就不会执行目标对象额外的业务方法。
代码示例:
目标对象类:UserDao.java
/**
* 目标对象,没有实现任何接口 */ public class UserDao { public void save() { System.out.println("----已经保存数据!----"); } }
Cglib代理工厂:ProxyFactory.java
/**
* 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此为一个包含对象(obj)、方法(method)、对象数组(args)以及方法代理(proxy)的参数组合。其中对象(obj)用于特定的操作或数据承载;方法(method)指定了要执行的具体行为;对象数组(args)则为方法提供了所需的参数值;方法代理(proxy)在某些情况下用于对方法的调用进行代理或处理。throws Throwable { System.out.println("开始事务..."); 执行目标对象的方法,得到的返回值为 Object returnValue,这个返回值是通过调用 method 得到的。invoke(target, args); System.out.println("提交事务..."); return returnValue; } }
测试类:
/**
* 测试类 */ public class App { @Test public void test(){ //目标对象 UserDao target = new UserDao(); 代理对象是 UserDao 的代理,它被转换为了 (UserDao) 类型。new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.save(); } }
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理