下一篇文章《Java 设计模式 —— 动态代理模式(拦截器)》将讲述如何利用反射,将动态代理模式封装成拦截器来使用
动态代理模式就是生成一个代理对象,来代替真实对象控制对真实对象的访问。
举一个非常简单的例子:
我们公司的高总日理万机,每天要处理上百个需求,忙的那是焦头烂额,正处于崩溃边缘……
有一天,高总突然开窍了,他为自己招募到了一个新员工——小彭,作为自己的代理人。
自从小彭做了高总的代理人,高总开始天天坐在办公室哼个小曲,喝个小茶,瞬间感觉都年轻了许多。
为什么呢?
原来,所有来找高总谈需求的客户,都要先经过小彭的接待,然后再进行筛选,低于一个亿的项目免谈!
超过一个亿的项目才有机会见到高总去继续谈需求
谈完之后,不管成功与否,高总只需要喊一声:小彭,送客!
然后继续做着喝茶哼曲儿,乐得轻松
在上述例子中,高总就是真实对象,小彭就是代理对象,而客户就是调用者。
显而易见,代理对象的作用,就是在真实对象被访问之前(洽谈需求之前)或者之后(洽谈需求之后)加入对应的逻辑(之前:接待客人;之后:送客人离开),或者根据自定义的规则来控制是否使用真实对象(小于一个亿的项目不允许访问高总)。
常见的动态代理模式有两种:一种是 JDK 动态代理,必须借助接口实现;另一种是 CGLIB 动态代理,使用非抽象类实现。
JDK 动态代理
JDK 动态代理必须借助接口才能产生代理对象。
-
首先,我们定义一个接口,并实现它
package test; public interface LeaderGao { void talkingWithCustomers(); }
package test; public class LeaderGaoImpl implements LeaderGao { @Override public void talkingWithCustomers() { System.out.println("与客户洽谈中 ..."); } }
-
接下来,建立代理对象和真实对象的关系并获取代理对象,实现代理逻辑。JDK 动态代理的实现代理逻辑类必须实现
java.lang.reflect.InvocationHandler
接口package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxyExample implements InvocationHandler { // 定义全局变量,保存真实对象 private Object target = null; /** * 代理方法逻辑 * @param proxy 代理对象(就是 bind 方法生成的对象) * @param method 当前调度的方法 * @param args 当前调度方法参数 * @return 代理结果返回 * @throws Throwable 异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("进入代理逻辑方法 —— 见到了代理人小彭"); System.out.println("在访问真实对象之前 —— 接待客人并筛选客人是否可以访问高总"); Object obj = method.invoke(target, args); System.out.println("在访问真实对象之后 —— 送客"); return obj; } /** * 建立代理对象和真是对象的代理关系,并返回代理对象 * @param target 真实对象 * @return 代理对象 */ public Object bind(Object target){ // 保存真实对象 this.target = target; // 建立并生成代理对象 /* Proxy.newProxyInstance 方法需要三个参数: 1、类加载器。 这里采用 target 类本身的加载器 2、表示最终生成的动态代理对象挂载在哪些接口下。 这里将其挂在 target 类实现的接口下 3、定义实现方法逻辑的代理类,必须实现 InvocationHandler 的 invoke 方法 这里 this 表示当前对象 */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } }
-
测试 JDK 动态代理
package test; import org.junit.Test; public class TestProxyPattern { @Test public void testJdkProxy(){ JDKProxyExample jdk = new JDKProxyExample(); // 绑定关系,传入真实对象,因为挂在接口 LeaderGao 下,所以声明代理对象 LeaderGao。 // JDK 动态代理之所以必须借助一个接口的,就是因为其创建动态代理必须传入一个参数(动态代理挂在的接口),而这个接口,就是在这里获取到动态代理之后的对象类型 LeaderGao proxy = (LeaderGao)jdk.bind(new LeaderGaoImpl()); // 此时,HelloWorld 对象已经是一个代理对象,它会进入代理的逻辑方法 invoke 里 proxy.talkingWithCustomers(); } }
-
运行结果
CGLIB 动态代理
CGLIB 动态代理不需要提供接口,但是它是第三方包,需要手动去下载(部分框架中已经集成)。
- 首先,需要下载
cglib
jar 包并 add 到 classpath 中。下载链接:http://www.java2s.com/Code/Jar/c/Downloadcglibnodep213jar.htm
,如果链接失效,大家自行搜索即可;如果环境中已经集成,跳过该步骤。
-
接下来,定义一个类,不实现任何接口,因此也无法使用 JDK 动态代理
package test; public class LeaderGaoImpl { public void talkingWithCustomers() { System.out.println("高总正在与客户洽谈中 ..."); } }
-
然后,建立代理对象和真实对象的关系,获取代理对象,实现代理逻辑。
package test; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * CGLIB 动态代理对象获取及代理逻辑。 * 至于这里为什么实现 MethodInterceptor 接口。 * 是因为使用 CGLIB 动态代理时,其代理类需要实现 MethodInterceptor 接口 * 而这里,我们 echancer.setCallback(this) 的意义就是将当前对象设置为代理类,所以当前对象就需要实现该接口,并复写 intercept 方法提供代理逻辑 */ public class CGLIBProxyExample implements MethodInterceptor { // 定义全局变量,保存真实对象 private Object target = null; /** * 生成 CGLIB 代理对象 * @param target 真实对象 * @return 真实对象的 CGLIB 代理对象 */ public Object bind(Object target){ this.target = target; // CGLIB 的增强类对象 enhancer Enhancer enhancer = new Enhancer(); // 设置增强类型 enhancer.setSuperclass(this.target.getClass()); // 定义代理逻辑对象为当前对象 this。要求当前对象必须实现 MethodInterceptor 接口(intercept 方法) enhancer.setCallback(this); // 设置类加载器 enhancer.setClassLoader(target.getClass().getClassLoader()); // 生成并返回代理对象 return enhancer.create(); } /** * 代理逻辑方法 * @param proxy 代理对象 * @param method 方法 * @param args 方法参数 * @param methodProxy 方法代理 * @return 代理逻辑返回 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{ System.out.println("进入代理逻辑方法 —— 见到了代理人小彭"); System.out.println("在访问真实对象之前 —— 接待客人并筛选客人是否可以访问高总"); Object obj = methodProxy.invokeSuper(proxy, args); System.out.println("在访问真实对象之后 —— 送客"); return obj; } }
-
最后,测试 CGLIB 动态代理
package test; import org.junit.Test; public class TestProxyPattern { @Test public void testCGLIBProxy(){ CGLIBProxyExample cglibProxyExample = new CGLIBProxyExample(); LeaderGaoImpl proxyObj = (LeaderGaoImpl)cglibProxyExample.bind(new LeaderGaoImpl()); proxyObj.talkingWithCustomers(); } }
-
运行结果
-
最后,友情提示,如果遇到了如下报错,请检查是否正确引入 cglib 包,建议使用
cglib-nodep-2.1.3.jar
版本java.lang.NoClassDefFoundError: Could not initialize class net.sf.cglib.proxy.Enhancer