代理模式是一种设计模式,其意义在于生成一个占位,来代替真实的对象,从而控制对真实对象的访问。 其实在现实生活中,代理很容易理解。假设这样一个场景:你的公司是一家服装公司,客户来定制服装,肯定不是直接找公司的裁缝谈的,而是去找商务谈,这时客户就会认为商务就代表公司。
可见这是一个间接的过程,那么商务(代理对象)的意义何在?商务可以进行一些额外逻辑例如谈判、价格等等,也就是说,代理的作用是:在委托对象访问之前或者之后加入对应逻辑,或者根据其它条件判断是否要访问委托对象,这个场景很显然商务控制了客户对裁缝的访问。
那么我们的代码示例也将以此为场景,一一讲解各个代理模式。
在讲解之前,我们先定义衣服实体类,衣服制作接口,裁缝类(实现衣服制作接口) 这几个基本类:
衣服实体类:
packagecom.example.javaproxy.model; importlombok.Getter; importlombok.Setter; /*** 衣服类*/publicclassClothing { /*** 衣服名*/privateStringname; /*** 尺码(S,M,L,XL,XXL)*/privateStringsize; }
衣服制作接口:
packagecom.example.javaproxy.service; importcom.example.javaproxy.model.Clothing; /*** 衣服制作接口*/publicinterfaceClothesMaking { /*** 衣服制作* @param name 衣服名* @param size 衣服大小* @return 衣服实例*/ClothingmakeClothing(Stringname, Stringsize); }
裁缝类(实现衣服接口,为委托对象):
packagecom.example.javaproxy.service.impl; importcom.example.javaproxy.model.Clothing; importcom.example.javaproxy.service.ClothesMaking; /*** 裁缝(委托类)*/publicclassTailorimplementsClothesMaking { publicClothingmakeClothing(Stringname, Stringsize) { Clothingclothing=newClothing(); clothing.setName(name); clothing.setSize(size); returnclothing; } }
1,静态代理
静态代理其实很简单,相当于直接创建代理人类,间接地访问裁缝类。
我们直接上代码,代理人(类):
packagecom.example.javaproxy.proxy.staticproxy; importcom.example.javaproxy.model.Clothing; importcom.example.javaproxy.service.ClothesMaking; importcom.example.javaproxy.service.impl.Tailor; /*** 裁缝代理人(代理类,静态代理)*/publicclassTailorStaticAgentimplementsClothesMaking { /*** 代理人的裁缝实例(委托对象)*/privateClothesMakingtailor=newTailor(); /*** 向代理人定制衣服** @param name 衣服名字* @param size 客户身高* @return 衣服*/publicClothingmakeClothing(Stringname, Stringsize) { System.out.println("[进入静态代理逻辑]"); System.out.println("裁缝代理人已接到需求!"); returntailor.makeClothing(name, size); } }
然后在主方法测试:
ClothesMakingagent=newTailorStaticAgent(); ClothingcustomClothing=agent.makeClothing("衬衫", "XL"); System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());
结果:
可见静态代理非常容易理解,就是新建一个代理类,利用这个代理类(裁缝的代理人) 去访问 委托类(裁缝),客户只需要找代理人即可间接地访问裁缝。
可见静态代理中,代理类和委托类需要实现同一个接口,且静态代理是一种编译后再代理的模式,不太利于扩展。
2,动态代理
上述例子可见,静态代理的代理类编译后也变为class文件,而动态代理是在运行时动态生成代理对象的,在运行时动态生成类字节码,并加载到JVM中。
(1) JDK动态代理
利用JDK自带的API实现在内存中创建动态的代理对象,我们无需向上面静态代理一样建立代理类,只需要建立一个代理逻辑类,这个逻辑类需要使用InvocationHandler
接口,在逻辑类里面写下代理方法逻辑,并将代理对象和委托对象建立联系即可,然后这个代理逻辑类就可以生成动态代理对象。
这里贴上代理逻辑类的代码:
packagecom.example.javaproxy.proxy.jdkproxy; importcom.example.javaproxy.service.ClothesMaking; importjava.lang.reflect.InvocationHandler; importjava.lang.reflect.Method; importjava.lang.reflect.Proxy; /*** JDK动态代理逻辑实现*/publicclassJDKProxyBindimplementsInvocationHandler { /*** 代理人的裁缝代理实例(委托对象)*/privateClothesMakingtailor; /*** 将委托对象和代理对象建立关系** @param origin 委托对象* @return 代理对象*/publicObjectbind(ClothesMakingorigin) { this.tailor=origin; returnProxy.newProxyInstance(tailor.getClass().getClassLoader(), tailor.getClass().getInterfaces(), this); } /*** 代理逻辑方法** @param proxy 代理对象* @param method 当前调度方法* @param args 当前方法参数* @return 代理结果* @throws Throwable 异常*/publicObjectinvoke(Objectproxy, Methodmethod, Object[] args) throwsThrowable { System.out.println("[进入JDK动态代理逻辑]"); System.out.println("裁缝代理人已接到需求!"); // 调用裁缝(委托类)的方法Objectresult=method.invoke(tailor, args); System.out.println("衣服定制完成!"); returnresult; } }
然后去主方法测试:
// 首先创建JDK代理逻辑实现类的实例,用于将代理对象和委托对象联系起来JDKProxyBindjdkProxyBind=newJDKProxyBind(); // 新建裁缝实例,绑定关系ClothesMakingtailor=newTailor(); ClothesMakingagent= (ClothesMaking) jdkProxyBind.bind(tailor); // 这个时候agent对象已经是一个代理对象了,它会进入代理的逻辑方法invoke中去ClothingcustomClothing=agent.makeClothing("衬衫", "XL"); System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());
结果:
可见JDK动态代理,并没有手动建立代理类,只是写了代理人的代理逻辑部分。其主要分为两大步:
- 建立代理对象和委托对象的关系:在上述我们建立了代理逻辑类,其中定义了
bind
方法,方法中首先用类的属性tailor
保存了委托对象,然后通过Proxy
类的newProxyInstance
方法,建立代理对象。这个方法有三个参数:
- 第一个是类加载器,使用委托类的加载器
- 第二个是要把生成的动态代理对象挂载至哪个接口之下,这里很显然就是要挂在委托类的接口下
- 第三个是定义实现方法逻辑的代理类,
this
表示当前对象。代理逻辑类必须实现InvocationHandler
接口的invoke
方法,这个方法就是代理逻辑方法的实现方法。 我们就将代理逻辑放在本类
- 实现代理方法:在代理逻辑类的
invoke
方法中实现代理逻辑,该方法三个参数意义如下:
- proxy 代理对象,就是bind方法生成的对象
- method 当前调度的方法
- args 调度方法的参数
我们使用了代理方法调度方法之后,就会进入到invoke
方法中去。
实现了代理逻辑类,在主方法中使用时,可见只需要使用代理逻辑类将裁缝(委托类)绑定关系,就可以直接生成代理对象,执行代理逻辑。
(2) CGLib动态代理
我们发现,无论是静态代理还是JDK动态代理,都需要提供接口。若在不能提供接口的环境中,我们可以使用CGLib代理。它只需要提供一个委托类就能实现动态生成代理对象实现代理。
首先我们需要在Maven中引入CGLib的依赖:
<!-- CGLib代理 --><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
然后也是创建一个代理逻辑类,这个和JDK动态代理有点类似。
还是贴上代理逻辑类的代码:
packagecom.example.javaproxy.proxy.cglibproxy; importnet.sf.cglib.proxy.Enhancer; importnet.sf.cglib.proxy.MethodInterceptor; importnet.sf.cglib.proxy.MethodProxy; importjava.lang.reflect.Method; /*** CGLib动态代理逻辑实现*/publicclassCGLibProxyBindimplementsMethodInterceptor { /*** 生成CGLib代理对象** @param cls 真实对象的类* @return 代理对象*/publicObjectgetProxy(Classcls) { // CGLib增强类型对象Enhancerenhancer=newEnhancer(); // 设定委托类为父类enhancer.setSuperclass(cls); // 设定代理逻辑类为本类enhancer.setCallback(this); // 生成并返回代理对象returnenhancer.create(); } /*** 代理逻辑方法** @param proxy 代理对象* @param method 方法* @param args 方法参数* @param methodProxy 方法代理* @return 代理逻辑返回* @throws Throwable 异常*/publicObjectintercept(Objectproxy, Methodmethod, Object[] args, MethodProxymethodProxy) throwsThrowable { System.out.println("[进入CGLib动态代理逻辑]"); System.out.println("裁缝代理人已接到需求!"); // 调用裁缝(委托类)的方法Objectresult=methodProxy.invokeSuper(proxy, args); System.out.println("衣服定制完成!"); returnresult; } }
然后在主方法测试:
// 首先创建CGLib代理逻辑类实例,把代理对象和委托对象联系起来CGLibProxyBindcgLibProxyBind=newCGLibProxyBind(); // 生成裁缝的代理对象Tailoragent= (Tailor) cgLibProxyBind.getProxy(Tailor.class); // 这个时候agent已经是一个代理对象了,尽管它是Tailor类ClothingcustomClothing=agent.makeClothing("衬衫", "XL"); System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());
结果:
可见这里用了CGLib的加强者Enhancer
,通过设定超类为委托类,并设定本类为代理逻辑类(代理逻辑类需要实现MethodInterceptor
接口),来生成动态代理对象。
可见在使用上面,CGLib动态代理和JDK动态代理还是很相似的。
不过这里需要提醒一下,使用JDK9及其以上版本执行CGLib动态代理会抛出异常java.lang.ExceptionInInitializerError
,因为在JDK高版本中,--illegal-access
选项默认为deny
,这会导致深度反射失败。
可以使用JDK8版本来实现CGLib代理。
若要使用JDK9及其以上版本,那么就修改运行JVM参数--illegal-access
的值为warn
(不过这样还是会弹出警告)。
命令行运行加上参数即可:
java --illegal-access=warn -jar xxx.jar
使用IDEA的话,在运行配置中设定运行参数:
最后应用、确定即可!
3,总结
代理模式就是一种间接访问的模式,在很多地方其实都有着应用。
静态代理很简单,不过需要手动定义代理类,不利于业务扩展。
动态代理只需要实现代理方法逻辑即可,在运行过程中就会动态地生成代理对象,常用的就是JDK动态代理和CGLib动态代理。
而JDK动态代理和CGLib动态代理最大的区别就是,前者需要依赖接口而后者不需要。JDK动态代理的实质是通过反射代理方法,而CGLib是通过生成类字节码实现代理。