对于每一个Java开发来说,代理这个词或多或少都会听说过。你可能听到过的有代理模式、动态代理、反向代理等。那么,到底什么是代理,这么多代理又有什么区别呢。本文就来简要分析一下。
代理技术,其实不只是Java语言特有的技术,其实在互联网早期就已经出现了这种技术。
在计算机网络层面,常用的代理技术有:正向代理、反向代理和透明代理。最常用到的就是正向代理和反向代理。
正向代理(forward proxy):是一个位于客户端和目标服务器之间的服务器(代理服务器),为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的配置才能使用正向代理。一般情况下,如果没有特别说明,代理技术默认是指正向代理技术。
这种代理其实在生活中是比较常见的,比如科学上网技术,其用到的就是代理技术。
小明想要访问某国外网站,该网站无法在国内直接访问,但是小明可以访问到一个代理服务器,这个代理服务器可以访问到这个国外网站。这样呢,小明对该国外网站的访问就需要通过代理服务器来转发请求,并且该代理服务器也会将请求的响应再返回给小明。这个上网的过程就是用到了正向代理。
在通过正向代理服务器的时候,目标服务器是不知道真正的客户端是谁的,只知道是代理服务器在发送请求。
突破访问限制:通过代理服务器,可以突破自身IP访问限制,访问国外网站,教育网等。
最近一些年,随着VPN技术的不断发展,一般的突破访问限制都是通过VPN来实现的,如果你仔细了解一下VPN的话,你会发现,其实VPN也是正向代理的一种实现,其本质上也是一个代理服务器。
提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,会将部分请求的响应保存到缓冲区中,当其他用户再访问相同的信息时, 则直接由缓冲区中取出信息,传给用户,以提高访问速度。
隐藏客户端真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。
反向代理(reverse proxy):是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
对于常用的场景,就是我们在Web开发中用到的负载均衡服务器,客户端发送请求到负载均衡服务器上,负载均衡服务器再把请求转发给一台真正的服务器来执行,再把执行结果返回给客户端。
前面我们说过,通过正向代理服务器访问目标服务器,目标服务器是不知道真正的客户端是谁的,甚至不知道访问自己的是一个代理。而通过反向代理服务器访问目标服务器时,客户端是不知道真正的目标服务器是谁的,甚至不知道自己访问的是一个代理。这也是正向代理和反向代理的区别。
隐藏服务器真实IP:使用反向代理,可以对客户端隐藏服务器的IP地址。
负载均衡:反向代理服务器可以做负载均衡,根据所有真实服务器的负载情况,将客户端请求分发到不同的真实服务器上。
提高访问速度:反向代理服务器可以对于静态内容及短时间内有大量访问请求的动态内容提供缓存服务,提高访问速度。
提供安全保障:反向代理服务器可以作为应用层防火墙,为网站提供对基于Web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等。还可以为后端服务器统一提供加密和SSL加速(如SSL终端代理),提供HTTP访问认证等。
前面介绍的是计算机网络层的代理机制,一般指的是服务请求的代理。在Java的日常开发中还会接触到另外一种代理,或者说叫做代理模式。
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
按照代理类的创建时期,代理类可分为两种,即动态代理类和静态代理类。就是我们经常提到的静态代理和动态代理中主要用到的类。
所以,静态代理和动态代理的主要区别就是代理类创建的时间不同。
静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
所谓静态代理,就是代理类是由程序员自己编写的,在编译期就确定好了的。来看下下面的例子:
public interface HelloSerivice { public void say(); } public class HelloSeriviceImpl implements HelloSerivice{ @Override public void say() { System.out.println("hello world"); } }
上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下类定义代理对象。
public class HelloSeriviceProxy implements HelloSerivice{ private HelloSerivice target; public HelloSeriviceProxy(HelloSerivice target) { this.target = target; } @Override public void say() { System.out.println("记录日志"); target.say(); System.out.println("清理数据"); } }
上面就是一个代理类,他也实现了目标对象的接口,并且扩展了say方法。下面是一个测试类:
public class Main { @Test public void testProxy(){ //目标对象 HelloSerivice target = new HelloSeriviceImpl(); //代理对象 HelloSeriviceProxy proxy = new HelloSeriviceProxy(target); proxy.say(); } } // 记录日志 // hello world // 清理数据
这就是一个简单的静态的代理模式的实现。代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的。
控制真实对象的访问权限:通过代理对象控制对真实对象的使用权限。
避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
增强真实对象的功能:这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
前面介绍了静态代理,虽然静态代理模式很好用,但是静态代理还是存在一些局限性的,比如使用静态代理模式需要程序员手写很多代码,这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多,或者需要同时代理多个对象的时候,这无疑会增加很大的复杂度。
有没有一种方法,可以不需要程序员自己手写代理类呢。这就是动态代理啦。
动态代理中的代理类并不要求在编译期就确定,而是可以在运行期动态生成,从而实现对目标对象的代理功能。
JDK动态代理:java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
Cglib动态代理:Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
关于这两种动态代理的写法本文就不深入展开了,读者感兴趣的话,后面我再写文章单独介绍。本文主要来简单说一下这两种动态代理的区别和用途。
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
Cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
Cglib与动态代理最大的区别就是:
Java的动态代理,在日常开发中可能并不经常使用,但是并不代表他不重要。Java的动态代理的最主要的用途就是应用在各种框架中。因为使用动态代理可以很方便的运行期生成代理类,通过代理类可以做很多事情,比如AOP,比如过滤器、拦截器等。
在我们平时使用的框架中,像servlet的filter、包括spring提供的aop以及struts2的拦截器都使用了动态代理功能。我们日常看到的mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些几乎全部由动态代理的身影。
本文为你介绍了网络层面的正向代理和反向代理、开发模式层面的静态代理和动态代理。希望通过阅读本文,你会对代理技术有一定的了解。当然,这些概念也不要搞混哦。
【本文是51CTO专栏作者Hollis的原创文章,作者微信公众号Hollis(ID:hollischuang)】
戳这里,看该作者更多好文