为了更好的理解代理模式,首先根据生活中实际场景进行模拟,让我们在生活中去体验设计思想的美妙。
“病从口入”这句成语告诉我们注意饮食健康,小六同学想吃苹果,在吃苹果之前需要清洗一下苹果和洗一下手,吃完苹果后,需要洗一下手保持个人卫生;十分钟后。。。小六同学又想吃一个大鸭梨,清洗鸭梨--洗手--吃鸭梨--吃完洗手。
苹果和鸭梨都属于食物,创建一个食物接口
public interface Foods { void eatApple(); void eatpear(); } 复制代码
小六同学吃苹果和鸭梨的动作,相当于实现类
public class People implements Foods { @Override public void eatApple() { System.out.println("eat apple"); } @Override public void eatpear() { System.out.println("eat pear"); } } 复制代码
小六同学谨记“病从口入”这句成语,所以在吃食物之前需要清洗食物洗手,吃完食物后需要洗手。so easy~~直接在People实现类上加上这两个动作就可以,但是小五同学说我吃苹果和鸭梨之前只洗手不洗食物,为了实现小五这个动作需要重新写接口,重写实现类。那可不可以在不改变实现类的前提下实现呢,答案是肯定的,那就用到静态代理来实现。
public static void main(String []args){ People people = new Perople(); System.out.println("吃前:洗食物洗手"); people.eatApple(); System.out.println("吃后:洗手"); System.out.println("吃前:洗食物洗手"); people.eatpear(); System.out.println("吃后:洗手"); } 复制代码
小六同学最近变胖了,原因是越来越能吃了,一天需要吃苹果、鸭梨、香蕉、樱桃、橘子、橙子。。。等一百种水果才能吃饱!虽然饮食控制不住,但小六同学还是每次吃食物之前都洗食物洗手,吃完食物后洗手的好习惯,随之食量的增大,每次都需要洗食物洗手,费力费时间,小六心生一计,不如干脆找个管家,每次想吃东西时只需喊一声,管家帮忙洗食物洗手,自己只负责吃,棒极了。
Java 实现动态代理有两种方式,一种是 Java 自带的 JDK 动态代理,还有一种是使用字节码增强技术实现的 CGLIB 库动态代理。
两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
小六委托管家来代理洗食物和洗手,小六属于委托对象,管家属于代理对象。
JDK动态代理主要两个相关类:
每一个代理实例都必须要实现InvocationHandler这个接口,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用,所以要想对方法(吃食物)加强(洗食物洗手)就需要在invoke方法中实现。
public class FoodsHandler implements InvocationHandler{ private Object object;//委托对象(小六同学) public FoodsHandler(Object object){ this.object = object; } /*invoke方法的三个参数: proxy: 指代我们所代理的那个真实对象 method: 指代的是我们所要调用真实对象的某个方法的Method对象 args: 指代的是调用真实对象某个方法时接受的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ System.out.println("吃前:洗食物洗手"); //当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的handler对象的invoke方法来进行调用 Object result = method.invoke(object, args); System.out.println("吃后:洗手"); return result; } } 复制代码
public static void main(String []args){ //委托对象(小六同学) People people = new People(); //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 FoodsHandler inter = new FoodsHandler(people); //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); //获取代理类实例foods Foods foods = (Foods)(Proxy.newProxyInstance(Foods.class.getClassLoader(), new Class[] {Foods.class}, fh)); //通过代理类对象调用代理类方法,实际上会转到invoke方法调用 foods.eatApple(); foods.eatpear(); } /*newProxyInstance方法三个参数的解释如下 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上 */ 复制代码
上面的 JDK 动态代理需要定义一个接口,实现类实现接口中的方法,如果实现类不能实现接口时,我们就需要 CGLIB 动态代理。
使用 CGLIB 动态代理之前需要导入相关 jar 包,可以单独导入 cglib-.jar 包和 asm-.jar 包,也可以只导入一个 cglib-nodep-.jar 包(包含了 asm)。 下载链接
public class People { public void eatApple() { System.out.println("eat apple"); } public void eatpear() { System.out.println("eat pear"); } } 复制代码
public class PeopleCglib implements MethodInterceptor { @Override // object 代表要增强的对象,method代表要拦截的方法,objects 代表方法中的参数,methodProxy 代表对方法的代理 public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{ System.out.println("吃前:洗食物洗手"); methodProxy.invokeSuper(object,objects); System.out.println("吃后:洗手"); return object; } } 复制代码
public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); // 增强类对象 enhancer.setSuperclass(People.class); // 设置要增强的类(People) PeopleCglib peopleCglib = new PeopleCglib(); enhancer.setCallback(peopleCglib); // 设置要增强的方法(peopleCglib) People people = (People) enhancer.create(); // 生成增强过的子类对象 people.eatApple(); // 调用方法实际为增强过的方法 people.eatApple(); } } 复制代码
输出结果
吃前:洗食物洗手 eat apple 吃后:洗手 吃前:洗食物洗手 eat apple 吃后:洗手 复制代码