Java的代理就是客户类不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理。为啥要这样呢,是因为使用代理有2个优势:
可以隐藏委托类的实现
可以实现客户与委托类之间的解耦,在不修改委托类代码的情况下能够做一些额外的处理
我们举个很常见的例子:工厂会生产很多的玩具,但是我们买玩具都是到商店买的,而不是到工厂去买的,工厂怎么生产我们并不关心,我们只知道到商店可以买到自己想要的玩具,并且,如果我们需要送人的话商店可以把这些玩具使用礼品盒包装。这个工厂就是委托类,商店就是代理类,我们就是客户类。
在Java中我们有很多场景需要使用代理类,比如远程RPC调用的时候我们就是通过代理类去实现的,还有Spring的AOP切面中我们也是为切面生成了一个代理类等等。
代理类主要分为静态代理、JDK动态代理和CGLIB动态代理,它们各有优缺点,没有最好的,存在就是有意义的,在不同的场景下它们会有不同的用武之地。
1.Java静态代理
首先,定义接口和接口的实现类,然后定义接口的代理对象,将接口的实例注入到代理对象中,然后通过代理对象去调用真正的实现类,实现过程非常简单也比较容易理解,静态代理的代理关系在编译期间就已经确定了的。它适合于代理类较少且确定的情况。它可实现在怒修改委托类代码的情况下做一些额外的处理,比如包装礼盒,实现客户类与委托类的解耦。缺点是只适用委托方法少的情况下,试想一下如果委托类有几百上千个方法,岂不是很难受,要在代理类中写一堆的代理方法。这个需求动态代理可以搞定
// 委托接口 public interface IHelloService { /** * 定义接口方法 * @param userName * @return */ String sayHello(String userName); } // 委托类实现 public class HelloService implements IHelloService { @Override public String sayHello(String userName) { System.out.println("helloService" + userName); return "HelloService" + userName; } } // 代理类 public class StaticProxyHello implements IHelloService { private IHelloService helloService = new HelloService(); @Override public String sayHello(String userName) { /** 代理对象可以在此处包装一下*/ System.out.println("代理对象包装礼盒..."); return helloService.sayHello(userName); } } // 测试静态代理类 public class MainStatic { public static void main(String[] args) { StaticProxyHello staticProxyHello = new StaticProxyHello(); staticProxyHello.sayHello("isole"); } }
2.动态代理技术
代理类在程序运行时创建的代理方式被成为动态代理。在了解动态代理之前,我们先简回顾一下JVM的类加载机制中的加载阶段要做的三件事情
(附Java中的类加载器)
通过一个类的全名或其它途径来获取这个类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的Class对象,作为方法区中对这个类访问的入口
而我们要说的动态代理,主要就发生在第一个阶段,这个阶段类的二进制字节流的来源可以有很多,比如zip包、网络、运行时计算生成、其它文件生成(JSP)、数据库获取。其中运行时计算生成就是我们所说的动态代理技术,在Proxy类中,就是运用了ProxyGenerator.generateProxyClass来为特定接口生成形式为*$Proxy的代理类的二进制字节流。所谓的动态代理就是想办法根据接口或者目标对象计算出代理类的字节码然后加载进JVM中。实际计算的情况会很复杂,我们借助一些诸如JDK动态代理实现、CGLIB第三方库来完成的
另一方面为了让生成的代理类与目标对象(就是委托类)保持一致,我们有2种做法:通过接口的JDK动态代理和通过继承类的CGLIB动态代理。(还有一个使用了ASM框架的javassist太复杂了,我还没研究过,这里TODO下)
3.JDK动态代理
在Java的动态代理中,主要涉及2个类,java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler
我们需要一个实现InvocationHandler接口的中间类,这个接口只有一个方法invoke方法,方法的每个参数的注释如下代码。
我们对处理类中的所有方法的调用都会变成对invoke方法的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数判断是哪个方法)。中间类(实现了InvocationHandler的类)有一个委托类对象引用,在Invoke方法中调用了委托类对象的相应方法,通过这种聚合的方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。
实际上,中间类与委托类构成了静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。
public interface InvocationHandler { /** * 调用处理 * @param proxy 代理类对象 * @param methon 标识具体调用的是代理类的哪个方法 * @param args 代理类方法的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } Demo 如下: // 委托类接口 public interface IHelloService { /** * 方法1 * @param userName * @return */ String sayHello(String userName); /** * 方法2 * @param userName * @return */ String sayByeBye(String userName); } // 委托类 public class HelloService implements IHelloService { @Override public String sayHello(String userName) { System.out.println(userName + " hello"); return userName + " hello"; } @Override public String sayByeBye(String userName) { System.out.println(userName + " ByeBye"); return userName + " ByeBye"; } } // 中间类 public class JavaProxyInvocationHandler implements InvocationHandler { /** * 中间类持有委托类对象的引用,这里会构成一种静态代理关系 */ private Object obj ; /** * 有参构造器,传入委托类的对象 * @param obj 委托类的对象 */ public JavaProxyInvocationHandler(Object obj){ this.obj = obj; } /** * 动态生成代理类对象,Proxy.newProxyInstance * @return 返回代理类的实例 */ public Object newProxyInstance() { return Proxy.newProxyInstance( //指定代理对象的类加载器 obj.getClass().getClassLoader(), //代理对象需要实现的接口,可以同时指定多个接口 obj.getClass().getInterfaces(), //方法调用的实际处理者,代理对象的方法调用都会转发到这里 this); } /** * * @param proxy 代理对象 * @param method 代理方法 * @param args 方法的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke before"); Object result = method.invoke(obj, args); System.out.println("invoke after"); return result; } } // 测试动态代理类 public class MainJavaProxy { public static void main(String[] args) { JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService()); IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance(); helloService.sayByeBye("paopao"); helloService.sayHello("yupao"); } }
在上面的测试动态代理类中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。
首先通过newProxyInstance方法获取代理类的实例,之后就可以通过这个代理类的实例调用代理类的方法,对代理类的方法调用都会调用中间类(实现了invocationHandle的类)的invoke方法,在invoke方法中我们调用委托类的对应方法,然后加上自己的处理逻辑。
java动态代理最大的特点就是动态生成的代理类和委托类实现同一个接口。java动态代理其实内部是通过反射机制实现的,也就是已知的一个对象,在运行的时候动态调用它的方法,并且调用的时候还可以加一些自己的逻辑在里面。(附:Java反射)
3.2 Proxy.newProxyInstance源码阅读
上面说过,Proxy.newProxyInstance通过反射机制用来动态生成代理类对象,为接口创建一个代理类,这个代理类实现这个接口。具体源码如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 检查空指针 Objects.requireNonNull(h); // 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 final Class<?>[] intfs = interfaces.clone(); // 获取系统的安全接口,不为空的话需要验证是否允许访问这种关系的代理访问 final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * 生成代理类 Class,通过类加载器和接口 */ Class<?> cl = getProxyClass0(loader, intfs); /* * 通过构造器来创建实例 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //获取所有的构造器 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; // 构造器不是public的话需要设置可以访问 if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } // 返回创建的代理类Class的实例对象 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
4.CGLIB动态代理
JDK动态代理依赖接口实现,而当我们只有类没有接口的时候就需要使用另一种动态代理技术CGLIB动态代理。首先CGLIB动态代理是第三方框架实现的,在maven工程中我们需要引入cglib的包,如下:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency>
CGLIB代理是针对类来实现代理的,原理是对指定的委托类生成一个子类并重写其中业务方法来实现代理。代理类对象是由Enhancer类创建的。CGLIB创建动态代理类的模式是:
查找目标类上的所有非final的public类型的方法(final的不能被重写)
将这些方法的定义转成字节码
将组成的字节码转换成相应的代理的Class对象然后通过反射获得代理类的实例对象
实现MethodInterceptor接口,用来处理对代理类上所有方法的请求
// 委托类,是一个简单类 public class CglibHelloClass { /** * 方法1 * @param userName * @return */ public String sayHello(String userName){ System.out.println("目标对象的方法执行了"); return userName + " sayHello"; } public String sayByeBye(String userName){ System.out.println("目标对象的方法执行了"); return userName + " sayByeBye"; } } /** * CglibInterceptor 用于对方法调用拦截以及回调 * */ public class CglibInterceptor implements MethodInterceptor { /** * CGLIB 增强类对象,代理类对象是由 Enhancer 类创建的, * Enhancer 是 CGLIB 的字节码增强器,可以很方便的对类进行拓展 */ private Enhancer enhancer = new Enhancer(); /** * * @param obj 被代理的对象 * @param method 代理的方法 * @param args 方法的参数 * @param proxy CGLIB方法代理对象 * @return cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升 * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("方法调用之前"); Object o = proxy.invokeSuper(obj, args); System.out.println("方法调用之后"); return o; } /** * 使用动态代理创建一个代理对象 * @param c * @return */ public Object newProxyInstance(Class<?> c) { /** * 设置产生的代理对象的父类,增强类型 */ enhancer.setSuperclass(c); /** * 定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterceptor 接口 */ enhancer.setCallback(this); /** * 使用默认无参数的构造函数创建目标对象,这是一个前提,被代理的类要提供无参构造方法 */ return enhancer.create(); } }
//测试类 public class MainCglibProxy { public static void main(String[] args) { CglibProxy cglibProxy = new CglibProxy(); CglibHelloClass cglibHelloClass = (CglibHelloClass) cglibProxy.newProxyInstance(CglibHelloClass.class); cglibHelloClass.sayHello("isole"); cglibHelloClass.sayByeBye("sss"); } }
对于需要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比JDK动态代理还要快。值得注意的是,我们传入目标类作为代理的父类。不同于JDK动态代理,我们不能使用目标对象来创建代理。目标对象只能被CGLIB创建。在例子中,默认的无参构造方法被使用来创建目标对象。
总结
静态代理比较容易理解,需要被代理的类和代理类实现自同一个接口,然后在代理类中调用真正实现类,并且静态代理的关系在编译期间就已经确定了。而动态代理的关系是在运行期间确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。
JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
静态代理和动态代理都是基于接口实现的,而对于那些没有提供接口只是提供了实现类的而言,就只能选择CGLIB动态代理了
JDK动态代理和CGLIB动态代理的区别
JDK动态代理基于Java反射机制实现,必须要实现了接口的业务类才能用这种方法生成代理对象。
CGLIB动态代理基于ASM框架通过生成业务类的子类来实现。
JDK动态代理的优势是最小化依赖关系,减少依赖意味着简化开发和维护并且有JDK自身支持。还可以平滑进行JDK版本升级,代码实现简单。基于CGLIB框架的优势是无须实现接口,达到代理类无侵入,我们只需操作我们关系的类,不必为其它相关类增加工作量,性能比较高。
描述代理的几种实现方式?分别说出优缺点?
代理可以分为"静态代理"和"动态代理",动态代理又分为"JDK动态代理"和"CGLIB动态代理"实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是Real Object.
优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。*
缺点:不同的接口要有不同的代理类实现,会很冗余
JDK动态代理:
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK动态代理只需要实现InvocationHandler接口,重写invoke方法便可以完成代理的实现,
jdk的代理是利用反射生成代理类Proxyxx.class代理类字节码,并生成对象
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB代理:
由于JDK动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现方式实现MethodInterceptor接口,重写intercept方法,通过Enhancer类的回调方法来实现。
但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
相关资讯
详解Java 静态代理、Java动态代理、CGLIB动态代理
Java的代理就是客户类不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理。为啥要这样呢,是因为使用代理有2个优势:可以隐藏委托类的实现可以实
来源:黑核动态ip代理
2020-01-09 15:28:00
怎么利用动态ip抓取豆瓣电影数据
学爬虫有一段时间了,作为一个悬疑片爱好者总是无限片荒,泛滥的Top250爬取以及填补不了我饥渴的内心。于是果然地决定:自己动手,爬完豆瓣,丰衣足食。(一)反爬虫
来源:黑核混拨ip代理
2019-03-29 10:55:32
动态ip地址有什么优点和缺点
动态IP地址(DynamicIP)指的是在需要的时候才进行IP地址分配的方式。动态IP地址和静态IP地址是对应的。所谓动态就是指当你每一次上网时,电信会随机分配
来源:黑核混拨ip代理
2019-04-01 10:26:14
使用动态IP服务器的好处
动态IP服务器就是DHCP服务器的一个俗称,动态主机设置协议(DynamicHostConfigurationProtocol,DHCP)是一个局域网的网络协议
来源:黑核混拨ip代理
2019-04-01 11:09:46
使用动态ip代理让你成为"隐形人"
使用动态ip代理让你成为"隐形人"!现如今,互联网已经深入人们生活中的衣食住行,可你知道吗,当你接入互联网的那一刻起,你的个人信息可能就已经泄露了。拿到你个人信
来源:黑核动态ip代理
2019-04-04 14:11:48
如何处理动态IP地址?
当前时代是一个构筑在网络基础之上的信息化时代,基于IP的各种新应用如网络视频、架设WEB、网络视频服务器、邮件系统、远程监控、游戏平台等等,也表现出强大的市场需
来源:黑核混拨ip代理
2019-04-08 10:32:30
动态IP地址的捕获及其应用
一、IP地址与IP地址的动态分配1.IP地址基本概念Internet依靠TCP/IP协议,在全球范围内实现不同硬件结构、不同操作系统、不同网络系统的互联。在In
来源:
2019-04-08 10:39:54
动态ip、静态ip、pppoe拨号的区别
要把路由器设置得能上网,无非就是设置WAN外网接口连接而已。WAN接口能上网,则连接的电脑就能上网,反之则上不了网。只不过WAN接口往往有pppoe,动态IP,
来源:黑核动态ip代理
2019-04-15 10:27:07
详解socks5代理使用和安装配置
Socks简介:详情可以百度,不过我猜你对那一大堆术语也会像我一样觉得无语,所以我觉得你可以简单理解为它可以比较完美代理http/ftp/smtp等多种协议的代
来源:黑核混拨ip代理
2019-04-02 10:06:54
什么是ip代理软件?可以修改ip地址吗?
经常碰到朋友问,代理IP是做啥的,有什么用。我对他一顿解释,然后他还是云里雾里,好吧,可能是我口才不好,这里写文再解释下。代理IP对于很多人来说,用处很大,他们
来源:黑核混拨ip加速器网
2018-12-27 17:27:18
网页代理是不是socks代理、HTTP代理、VPN代理
网页代理是什么?一般网页代理页成为在线代理。在线代理英文全称是(WebProxyServer)。代理服务器其功能就是代理网络用户去取得网络信息。形象的说:它是网
来源:黑核混拨ip加速器网
2019-01-07 14:07:48
推荐阅读
Copyright © | 广州网连信息技术有限公司动态ip代理版权所有 备案号:粤ICP备18088888号-15 网站地图