代理模式是开发中经常使用的一种设计模式,每一种设计模式的出现都会极大的解决某方面的问题,代理模式也是同样,本文将会用通俗的语言来解释什么是代理模式?代理模式的种类、代码示例、每种代理模式的优缺点和代理模式适用的场景。html
首先咱们用一个小故事来描述下什么是代理模式,这会让你更快的理解代理模式的相关角色,为后面的各类代理打下基础。java
假如,你是一个大明星,人气很旺,粉丝也特别多。由于人气高,因此不少商家想找你代言广告,可是想要找你代言的人特别多,每一个商家你都须要进行商务洽谈,若是聊得不错决定合做,后续还须要签署不少合同文件、记录、备案等。这么多商家找你代言,其中你只能选择其中几个代言,即使只选择几个,你也忙不过来。因而你就想了一个办法,给本身找了一个经纪人,给经纪人制定标准让他去对接各商家,经纪人作事很认真负责,不只剔除了不少不良的商家还对有资格的商家作了详细的记录,记录商家的代言费、商家详细信息、商家合同等信息。因而在商务代言这件事情上你只须要专心代言拍广告,其余的事情交由经纪人一并处理。面试
分析下整个事件,能够知道,经纪人就是代理人,明星就是被代理人。在明星的广告代言中,经纪人处理的商务洽谈和签约环节至关于代理,这就是代理模式在实际生活中的简单案例。spring
其实不止经纪人和明星,生活中还有不少行为本质就是代理模式,好比:某些大牌的饮料三级代理销售、酒水的省市县的代理人、三国时曹操挟天子以令诸侯等等。数据库
说了这么多案例,都是关于代理模式的,那既然这么多人都在用代理模式,那代理模式必定解决了生活中的某些棘手的问题,那到底是什么问题呢?编程
在明星和经纪人这个案例中,由于把代言这个商业行为作了细分,让明星团队中每一个人负责代言的一部分,使每人只须要专一于本身的事,提升每一个人的专业度的同时,也提升了效率,这就叫专业,专人专事。由于经纪人专一广告代言的代理行为,商业经验丰富,因此经纪人也能够用他的专业知识为其余明星作广告代言的代理,这就叫能力复用。设计模式
那么,如何使用代码展现经纪人代理明星的广告行为呢?这其中有是如何运用代理模式的呢?数组
类比上面的明星和经纪人的例子:安全
假若有个明星类,咱们想在调用明星类的代言方法以前作一些其余操做好比权限控制、记录等,那么就须要一个中间层,先执行中间层,在执行明星类的代言方法。多线程
那讲到这里,想必又有人问,直接在明星类上加一个权限控制、记录等方法不就好了么,为何非要用代理呢?
这就是本文最重要的一个核心知识,程序设计中的一个原则:类的单一性原则。这个原则很简单,就是每一个类的功能尽量单一,在这个案例中让明星类保持功能单一,就是对代理模式的通俗解释。
那为何要保持类的功能单一呢?
由于只有功能单一,这个类被改动的可能性才会最小,其余的操做交给其余类去办。在这个例子中,若是在明星类里加上权限控制功能,那么明星类就再也不是单一的明星类了,是明星加经纪人二者功能的合并类。
若是咱们只想用权限控制功能,使用经纪人的功能给其余明星筛选广告商家,若是二者合并,就要建立这个合并类,可是咱们只使用权限功能,这就致使功能不单一,长期功能的累加会使得代码极为混乱,难以复用。
因此类的单一性原则和功能复用在代码设计上很重要,这也是使用代理模式的核心。
而这整个过程所涉及到的角色能够分为四类:
在java语言的发展中,出现了不少种代理方式,这些代理方式能够分类为两类:静态代理和动态代理,下面咱们就结合代码实例解释下,各种代理的几种实现方式,其中的优缺点和适用的场景。
主题接口
package com.shuai.proxy; public interface IDBQuery { String request(); }
真实主题
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class DBQuery implements IDBQuery { public DBQuery() { try { Thread.sleep(1000);//假设数据库链接等耗时操做 } catch (InterruptedException ex) { ex.printStackTrace(); } } @Override public String request() { return "request string"; } }
代理类
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class DBQueryProxy implements IDBQuery { private DBQuery real = null; @Override public String request() { // TODO Auto-generated method stub System.out.println("在此以前,记录下什么东西吧....."); //在真正须要的时候才能建立真实对象,建立过程可能很慢 if (real == null) { real = new DBQuery(); }//在多线程环境下,这里返回一个虚假类,相似于 Future 模式 String result = real.request(); System.out.println("在此以后,记录下什么东西吧....."); return result; } }
Main客户端
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class Test { public static void main(String[] args) { IDBQuery q = new DBQueryProxy(); //使用代里 q.request(); //在真正使用时才建立真实对象 } }
能够看到,主题接口是IDBQuery,真实主题是DBQuery 实现了IDBQuery接口,代理类是DBQueryProxy,在代理类的方法里实现了DBQuery类,而且在代码里写死了代理先后的操做,这就是静态代理的简单实现,能够看到静态代理的实现优缺点十分明显。
优势:
使得真实主题处理的业务更加纯粹,再也不去关注一些公共的事情,公共的业务由代理来完成,实现业务的分工,公共业务发生扩展时变得更加集中和方便。
缺点:
这种实现方式很直观也很简单,但其缺点是代理类必须提早写好,若是主题接口发生了变化,代理类的代码也要随着变化,有着高昂的维护成本。
针对静态代理的缺点,是否有一种方式弥补?可以不须要为每个接口写上一个代理方法,那就动态代理。
动态代理,在java代码里动态代理类使用字节码动态生成加载
技术,在运行时生成加载类。
生成动态代理类的方法不少,好比:JDK 自带的动态处理、CGLIB、Javassist、ASM 库。
这里介绍两种很是经常使用的动态代理技术,面试时也会经常用到的技术:JDK 自带的动态处理
、CGLIB
两种。
Java提供了一个Proxy类,使用Proxy类的newInstance方法能够生成某个对象的代理对象,该方法须要三个参数:
初次看见会有些不理解,不要紧,下面用一个实例来详细展现JDK动态代理的实现:
代理类的实现
package com.shuai.proxy.jdkproxy; import com.shuai.proxy.staticproxy.DBQuery; import com.shuai.proxy.IDBQuery; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DBQueryHandler implements InvocationHandler { private IDBQuery realQuery = null;//定义主题接口 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //若是第一次调用,生成真实主题 if (realQuery == null) { realQuery = new DBQuery(); } if ("request".equalsIgnoreCase(method.getName())) { System.out.println("调用前作点啥,助助兴....."); Object result = method.invoke(realQuery, args); System.out.println("调用后作点啥,助助兴....."); return result; } else { // 若是不是调用request方法,返回真实主题完成实际的操做 return method.invoke(realQuery, args); } } static IDBQuery createProxy() { IDBQuery proxy = (IDBQuery) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), //当前类的类加载器 new Class[]{IDBQuery.class}, //被代理的主题接口 new DBQueryHandler() // 代理对象,这里是当前的对象 ); return proxy; } }
Main客户端
package com.shuai.proxy.jdkproxy; import com.shuai.proxy.IDBQuery; public class Test { // 客户端测试方法 public static void main(String[] args) { IDBQuery idbQuery = DBQueryHandler.createProxy(); idbQuery.request(); } }
用debug的方式启动,能够看到方法被代理到代理类中实现,在代理类中执行真实主题的方法先后能够进行不少操做。
虽然这种方法实现看起来很方便,可是细心的同窗应该也已经观察到了,JDK动态代理技术的实现是必需要一个接口才行的,因此JDK动态代理的优缺点也很是明显:
优势:
缺点:
因为必需要有接口才能使用JDK的动态代理,那是否有一种方式能够没有接口只有真实主题实现类也可使用动态代理呢?这就是第二种动态代理:CGLIB
;
使用 CGLIB
生成动态代理,首先须要生成 Enhancer
类实例,并指定用于处理代理业务的回调类。在 Enhancer.create()
方法中,会使用 DefaultGeneratorStrategy.Generate()
方法生成动态代理类的字节码,并保存在 byte 数组中。接着使用 ReflectUtils.defineClass()
方法,经过反射,调用 ClassLoader.defineClass()
方法,将字节码装载到 ClassLoader 中,完成类的加载。最后使用 ReflectUtils.newInstance()
方法,经过反射,生成动态类的实例,并返回该实例。基本流程是根据指定的回调类生成 Class 字节码—经过 defineClass()
将字节码定义为类—使用反射机制生成该类的实例。
真实主题
package com.shuai.proxy.cglibproxy; class BookImpl { void addBook() { System.out.println("增长图书的普通方法..."); } }
代理类
package com.shuai.proxy.cglibproxy; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class BookImplProxyLib implements MethodInterceptor { /** * 建立代理对象 * * @return */ Object getBookProxyImplInstance() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(BookImpl.class); // 回调方法 enhancer.setCallback(this); // 建立代理对象 return enhancer.create(); } // 回调方法 @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始..."); proxy.invokeSuper(obj, args); System.out.println("结束..."); return null; } }
Main客户端
package com.shuai.proxy.cglibproxy; public class Test { public static void main(String[] args) { BookImplProxyLib cglib = new BookImplProxyLib(); BookImpl bookCglib = (BookImpl) cglib.getBookProxyImplInstance(); bookCglib.addBook(); } }
优势:
CGLIB经过继承的方式进行代理、不管目标对象没有没实现接口均可以代理,弥补了JDK动态代理的缺陷。
缺点:
代理模式有多种应用场合,以下所述: