标签:pen oca void show none 自己 rri throw mamicode
一、什么是代理设计模式
所谓代理,用生活中的事务来做解释的话可以理解为中介,比如:买房、租房,我们都可以找中介,可以省去很多的时间和麻烦。
在程序编码中的代理,就是用一个代理类将目标类封装起来,我们通过调用代理类中的方法,就可以执行目标类当中的方法,同时,我们在代理类中的方法还能增加一些额外的功能,这样可以实现给目标类的方法扩展新功能而不用修改目标类的代码。代理类和目标类的方法名最好设置得一样,这样的好处是我们使用代理类就和使用目标类是一样的。
二、代理模式的使用场景
远程代理,为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。
虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。
安全代理,用来控制真是对象访问时的权限。
智能指引,当调用真实对象时,代理处理另外一些事。
三、静态代理
在 Java 中的代理分为两种,静态代理和动态代理。静态代理就是在源码阶段我们手动的写个代理类将目标类给包装起来,这种方式比较水,因为要自己写代码,最好可以自动生成这个代理类就最好了,于是就有了动态代理,动态代理就是在运行阶段由 JVM 自动生成这个代理类,我们直接用就好。下面我们来演示一下静态代理的写法:
先来一个接口,描述目标类需要实现的方法
public interface Animal { void eat(); void run(); }
再来是我们的目标类
public class Dog implements Animal { @Override public void eat() { System.out.println("Dog...eat"); } @Override public void run() { System.out.println("Dog...run"); } }
最后是代理类
public class DogProxy implements Animal { private Dog dog; public DogProxy(Dog dog) { this.dog = dog; } @Override public void eat() { System.out.println("狗开始吃东西了"); dog.eat(); } @Override public void run() { System.out.println("狗开始跑步了"); dog.run(); } }
最后来看一下客户端调用和结果
public class DogProxy implements Animal { private Dog dog; public DogProxy(Dog dog) { this.dog = dog; } @Override public void eat() { System.out.println("狗开始吃东西了"); dog.eat(); } @Override public void run() { System.out.println("狗开始跑步了"); dog.run(); } }
这样,我们如果需要对目标类中的一些方法扩展起来就很容易,甚至不需要知道目标类中的代码实现。但是,这个代理类需要我们自己来写,如果需要对很多目标类来进行扩展,比如说:打印某个包下所有类中方法的执行时间,这样我们就需要写很多结构一致的代理类,这个时候我们可以考虑使用动态代理来实现。
四、动态代理
由于静态代理存在着一定的缺陷,使用起来并不方便和友好,所以就有了动态代理,它可以自动生成代理类,不需要我们再自己手写代理类了。Java 中,动态代理有两种:一种是 JDK 的动态代理,另外一种是 CGLIB 动态代理。
JDK动态代理
JDK 动态代理除了有目标类和代理类以外,还有一个中间类,这个中间类起什么作用呢?我们先来看一下下面这个图:
从上图可以看出,代理类中的所有方法实际上都是通过调用中间类(也就是自定义的 handler类)的 invoke() 方法来执行的,而在 invoke() 方法中才是真正去调用目标类的方法,我们对目标类进行的功能扩展就算在中间类的 invoke() 方法中来实现。
JDK 提供给我们的代理类是 java.lang.reflect.Proxy,我们可以看看这个类中主要的东西:有参构造是传递进去一个 InvocationHandler 类型的参数然后复制给属性h,然后就是 newProxyInstance() 方法,这个方法最主要的是其中的三个参数,第一个参数是类加载器,任意类加载器都行,通常用目标类的类加载器即可。第二个参数是目标类实现的接口,跟静态代理差不多,这里是为了让代理类和目标类的方法名一样;第三个参数是一个 InvocationHandler 类型的参数,注意,这个 h 是我们要自己写代码实现的,而不是上面属性中的那个h。
下面我们用来实现一下:
首先,是一个接口
public interface IInfoService { String addInfo(String info); String updateInfo(String info); String delInfo(String info); String queryInfo(String info); }
然后是接口的实现类,也就是被代理的目标类
public class InfoServiceImpl implements IInfoService { @Override public String addInfo(String info) { System.out.println("InfoServiceImpl.addInfo"); return "InfoServiceImpl.addInfo"; } @Override public String updateInfo(String info) { System.out.println("InfoServiceImpl.updateInfo"); return "InfoServiceImpl.updateInfo"; } @Override public String delInfo(String info) { System.out.println("InfoServiceImpl.delInfo"); return "InfoServiceImpl.delInfo"; } @Override public String queryInfo(String info) { System.out.println("InfoServiceImpl.queryInfo"); return "InfoServiceImpl.queryInfo"; } }
再来是中间类,实现接口 InvocationHandler,给目标类中的方法增加某些功能
public class CostTimeHandler implements InvocationHandler { private Object proxyObj; public CostTimeHandler(Object infoService) { this.proxyObj = infoService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); Object response = method.invoke(proxyObj, args); long end = System.currentTimeMillis(); System.out.println(String.format("方法 %s 执行时间为:%s", method.getName(), (end - start))); return response; } }
很明显,上面这个中间类的作用是计算目标类方法的执行时间
最后,我们来增加一个类,通过调用代理类 Proxy 的 newProxyInstance() 方法来生成代理类对象,其中 InvocationHandler 类型的参数由中间类 CostTimeHandler 来替代。
public class ServiceProxy { public static <T> T proxyService(T service) { return (T) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), new CostTimeHandler(service) ); } }
最后来看一下调用和结果:
public class App { public static void main(String[] args) { //生成$Proxy0的class文件,也就是代理类的字节码文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); IInfoService infoService = ServiceProxy.proxyService(new InfoServiceImpl()); infoService.addInfo(""); infoService.delInfo(""); infoService.updateInfo(""); infoService.queryInfo(""); } }
最后,我们来分析一下生成的代理类字节码文件
CGLIB 动态代理
标签:pen oca void show none 自己 rri throw mamicode
原文地址:https://www.cnblogs.com/L-Test/p/13225598.html