设计模式学习04:代理模式
本文转载:https://blog.csdn.net/u012153129/article/details/81806602
作者:SUN_Eudemon
什么是代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。
这句话看起来比较抽象,不太好理解,举个现实的例子:外卖。在有饿了么之类的外卖出现前,我们如果点了外卖很多时候都是由店家自己来配送的。但是在有了饿了么等外卖平台后,商家就把送餐的业务委托给外卖平台,由外卖小哥来配送食品。外卖平台在这里充当一个“中介”的角色,代理模式就跟这个很相似。
代码实现
静态代理
以送外卖为例子来举个例子,整个过程中的核心是配送,所以我们首先定义一个接口,接口里有一个配送方法
public interface FoodDelivered { /**配送*/ void send(); }
上段代码中有个send()方法,该方法是用来实现配送功能的,按照以前的方式,配送服务需要商家来自己来配送,所以我们定义一个商家类,该商家类实现FoodDelivered接口
public class Restaurant implements FoodDelivered { @Override public void send() { System.out.println("派送食物"); } }
商家类实现FoodDelivered接口,并实现了send方法进行配送食品。再写一个测试类来测试一下上述代码
public static void main(String[] args) { Restaurant r = new Restaurant(); r.send(); }
控制台打印(执行结果)
派送食物
派送食物
Process finished with exit code 0
测试方法里创建了一个商家对象,并调用send方法来进行配送服务,可以看到控制台里打印了派送食品,这里表示商家进行了配送服务。现在我们有了外卖平台,商家就可以把派送服务委托给外卖小哥来完成,外卖小哥便成了代理者,因为我们的小哥也是进行配送服务的,所以我们再定义一个外卖小哥的类,并实现
FoodDelivered接口
public class Deliveryman implements FoodDelivered { private Restaurant restaurant; public Deliveryman(Restaurant restaurant) { this.restaurant = restaurant; } @Override public void send() { System.out.println("外卖小哥派送"); this.restaurant.send(); } }
这里我们注意一下,Deliveryman类里有个成员是Restaurant类型的,这个类型是商家的类型,就是我们的委托人(注:这里构造方法的参数是Restaurant类型,也就是说我们的委托人可以是Restaurant类及其子类,这样灵活性很差。这个参数可以换成FoodDelivered类型,这样只要实现了FoodDelivered接口的所有类都可以作为委托人,灵活性会提高)。同时构造方法里会传一个Restaurant对象过来,已完成对成员restaurant的初始化。然后在send方法里调用委托人的send方法,并在前边打印一句“外卖小哥派送”,然后编写测试代码
public static void main(String[] args) { Restaurant restaurant = new Restaurant(); Deliveryman deliveryman = new Deliveryman(restaurant); deliveryman.send(); }
控制台打印(执行结果)
外卖小哥派送
派送食物
Process finished with exit code 0
这里可以我们首先创建一个商家对象,然后创建一个外卖小哥对象,并把商家对象作为参数传递给外卖小哥对象,最后我们调用外卖小哥的send方法,而不是商家的send方法,这样就把配送的服务委托给了外卖小哥来完成。控制台打印结果可以看到这个配送是由外卖小哥来配送的。
以上方式我们称之为静态代理,静态代理是在代码编译期前进行的,代码编译后就无法再改变了。引入代理模式好处,很多时候我们的业务可能会增加部分功能,比如事务控制,日志记录或者其他一些功能。如果在每次有需求增加的时候,我们都直接去修改当前业务的代码,这个并不符合开闭原则(对修改关闭,对扩展开放),当我们使用代理模式后,可以修改代理类的方法,原业务代码并不做任何修改。还是按之前的例子来说,比如这个时候客户需要一瓶饮料,这个时候我们不用修改商家的配送方法,只需要在外卖小哥的配送方法里实现买饮料的功能就可以了,这样就实现了在不修改配送方法的前提下对配送功能进行了扩充。对于我们在平常的开发中,比如有个业务需要增加日志功能,如果要去修改原业务代码,一是破坏开闭原则,二是增加了工作量,这个时候我们就可以用代理模式对业务做很好的扩充。与静态代理对应的还有一种是动态代理。
动态代理
动态代理跟静态代理一个很重要的区别在于,动态代理是在内存是中的,是在代码编译期后在内存是实现的,而静态代理是我们自己编写代理类,编译后生成class文件。动态代理需要借助两个类:java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy。我们还是以上边的例子来实现动态代理。首先需要创建一个类,并实现java.lang.reflect.InvocationHandler接口
public class FoodProxy<T> implements InvocationHandler { public T obj; public FoodProxy(T t) { obj = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始。。。"); Object result = method.invoke(obj, args); System.out.println("结束。。。"); return result; } }
其中有一个泛型成员变量obj,通过构造法方法接收一个变量来对成员obj初始化。重点看invoke方法,这里用到了反射相关的知识,第二个参数是Method的类型的变量,在方法里我们通过method.invoke()来执行这个方法,并且在方法执行前后各打印一行字符串。现在我们来编写一个测试类
public class FoodProxyMain { public static void main(String[] args) { Restaurant restaurant = new Restaurant(); FoodProxy<FoodDelivered> proxy = new FoodProxy<>(restaurant); FoodDelivered r = (FoodDelivered) Proxy.newProxyInstance(FoodDelivered.class.getClassLoader(), new Class[]{FoodDelivered.class}, proxy); r.send(); } }
首先创建一个要代理的对象(商家):restaurant,然后再创建一个InvocationHandler接口的实现类对象,也就是FoodProxy对象,因为我们要代理商家,所以把restaurant作为参数传递给FoodProxy的构造方法。接着通过Proxy.newProxyInstance()方法来创建我们的代理对象。我们可以看一下newProxyInstance的参数说明
第一个参数是代理类的加载器,第二个参数是代理类要实现的接口列表,第三个就是要绑定InvocationHandler对象。然后把执行返回的对象强转为FoodDelivered类型的对象,并调用send()方法,看控制台打印
开始。。。
派送食物
结束。。。
Process finished with exit code 0
可以看一下,控制台打印内容是InvocationHandler的实现类FoodProxy的invoke里的内容,我们在调用r.send()的时候,并不是直接去执行被代理类(Restaurant)的send()方法,而是委托给了FoodProxy的invoke去执行,也就是invoke方法里的
Object result = method.invoke(obj, args);
这行代码。这样就实现了对商家(Restaurant)的动态代理。这里我们可以修改一下测试类的代码,把Proxy.newProxyInstance的第一个参数和第二个参数换成实现类试试
public class FoodProxyMain { public static void main(String[] args) { Restaurant restaurant = new Restaurant(); FoodProxy<FoodDelivered> proxy = new FoodProxy<>(restaurant); FoodDelivered r = (FoodDelivered) Proxy.newProxyInstance(Restaurant.class.getClassLoader(), new Class[]{Restaurant.class}, proxy); r.send(); } }
注意:这里参数都改为了实现类的类型,然后我们运行一下代码
Exception in thread "main" java.lang.IllegalArgumentException: com.memory.proxy.Restaurant is not an interface at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:590) at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557) at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230) at java.lang.reflect.WeakCache.get(WeakCache.java:127) at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419) at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719) at com.memory.proxy.FoodProxyMain.main(FoodProxyMain.java:14)
程序出现了异常,异常信息:com.memory.proxy.Restaurant is not an interface。提示Restaurant不是一个接口。这里就说明,我们要动态代理的必须是个接口,如果你要动态实现的类没有实现任何接口,那很抱歉,你将无法进行动态代理。因为我们的Restaurant类已经实现了FoodDelivered接口,所以我们在这里参数使用FoodDelivered接口就不会有问题。
总结
代理模式是JAVA设计模式里非常重要,也是经常使用的设计模式,代理模式可以在不改变原程序代码的前提下,对功能进行扩充。通过代理模式,我们可以很方便的给代码添加日志记录,事务控制,权限控制等一系列功能。Spring AOP(面向切面编程)就用到了动态代理模式,在往后也会对Spring AOP进行研究和分析。