背景:据说Java设计模式中的代理模式是进入BAT的必经之路。java
一、代理模式:nginx
给某一对象提供一个代理对象,并由代理对象控制对原对象的引用;简而言之,就是在不改变源代码的状况下,实现对目标功能的扩展;好比,你计划下个月结婚,固然你能够本身筹划婚礼的过程,那样太闹腾了,因而你就把筹备婚礼的过程交由婚庆公司布置,而且只须要在婚礼当天使用婚庆公司筹划婚礼的结果便可。算法
二、代理模式的结构:spring
a、抽象对象角色编程
声明了目标对象和代理对象的共同接口,那么在任何可使用目标对象的地方也都可使用代理对象;设计模式
b、目标对象角色服务器
定义了代理对象所表明的目标对象;架构
c、代理对象角色app
代理对象内部包含目标对象的引用,So能够自由地操做目标对象,代理对象提供一个与目标对象相同的接口,So能够在任什么时候候替换目标对象;负载均衡
三、静态代理
从JVM加载类的角度看,静态代理和动态代理本质上是同样的,两者都是在原有类的基础上,加入一些多出的行为,甚至彻底替换原有的行为;而静态代理采用的方式是咱们手动的将某些行为进行替换,产生一个新的与原有类接口相同却行为不一样的类型;
以淘宝网为例,模拟访问网站的场景;现在你们都常常访问淘宝网,几乎全部的Web项目尤为是像淘宝这样的大型网站,是不可能采用集中式的架构,使用的必定是分布式架构,而分布式架构对于用户来讲,当咱们发起连接的时候,连接指向的并非最终的应用服务器,而是代理服务器如Nginx,用做负载均衡[用户访问淘宝网 --> 代理服务器 --> 最终服务器]。
定义一个服务器Server,以及用于获取页面标题的方法getPageTitle:
1 /**
2 * 用于获取网站数据的服务器接口 3 */
4 public interface Server { 5
6 //根据url获取页面标题
7 public String getPageTitle(String url); 8
9 }
访问淘宝网的TaobaoServer,传入url,获取页面标题:
1 /**
2 * 淘宝服务器 3 */
4 public class TaobaoServer implements Server{ 5
6 @Override 7 public String getPageTitle(String url) { 8 //简单的if..else判断
9 if ("http://www.taobao.com".equals(url)) { 10 return "淘宝首页"; 11 }else if ("http://www.tianmao.taobao.com".equals(url)) { 12 return "淘宝-天猫商城"; 13 } 14 return "空空如也"; 15 } 16 }
a、如果不使用代理,那么用户访问就至关于直接 new TaobaoServer()而且调用getPageTitle()方法便可;
b、而因为分布式架构的存在,所以须要一个NginxProxy类做为代理,到时候用户直接访问的就是NginxProxy而不直接与TaobaoServer打交道,由NginxProxy负责与最终的TaobaoServer打交道;
NginxProxy代理类:
1 import java.util.List; 2 import java.util.UUID; 3 import com.google.common.collect.Lists; 4
5 /**
6 * Nginx代理 7 */
8 public class NginxProxy implements Server{ 9
10 //淘宝服务器列表
11 private static final List<String> TAOBAO_SERVER_ADDRESSES = Lists.newArrayList("127.0.0.1","127.0.0.2","127.0.0.3"); 12
13 private Server server; 14
15 public NginxProxy(Server server) { 16 this.server = server; 17 } 18
19 @Override 20 public String getPageTitle(String url) { 21 //UUID模拟请求原始Ip,正常状况下是传入Request的
22 String remoteIp = UUID.randomUUID().toString(); 23 //路由选择的算法,这里简单的定义为对remoteIp的Hash之的绝对值取模
24 int index = Math.abs(remoteIp.hashCode()) % TAOBAO_SERVER_ADDRESSES.size(); 25 //选择淘宝服务器IP
26 String realTaobaoIp = TAOBAO_SERVER_ADDRESSES.get(index); 27 return "【页面标题: " + server.getPageTitle(url) + "】, 【来源IP: " + realTaobaoIp + "】"; 28
29 } 30 }
用于测试,简单设计服务器列表几个IP,同时因为只是传入一个url而不是具体的Request,每次随机一个UUID而且对其hashCode的绝对值取模,模拟该请求被路由到哪台服务器上:
1 /**
2 * 静态代理测试类 3 */
4 public class StaticProxyTest { 5 public static void main(String[] args) { 6 //访问淘宝服务器
7 TaobaoServer taobaoServer = new TaobaoServer(); 8 //访问Nginx代理
9 NginxProxy nginxProxy = new NginxProxy(taobaoServer); 10 System.out.println(nginxProxy.getPageTitle("http://www.taobao.com")); 11 } 12 }
因为淘宝服务器和代理服务器实际上都是服务器,So它们可使用相同的接口Server;用户不和最终目标对象角色TaobaoServer打交道,而是与代理对象角色NginxProxy打交道,由代理对象角色NginxProxy控制用户的访问;屡次运行来源IP会改变,其结果以下:
四、静态代理的缺点
静态代理的的特色是静态代理的代理类是由程序猿建立的,在程序运行以前静态代理的.class文件就已经存在了;静态代理在代理量较小时还OK,可是代理量增长就会存在两个比较明显的缺点:
a、静态代理的内容,即NginxProxy路由的选择这几段代码,只能服务一路Server该接口,而不能服务于其余接口,如果其余接口想要使用这几行代码如新增一个代理类,而不断的新增,又因为静态代理的内容没法复用,则必然会形成静态代理类过于庞大;
b、Server接口中如果新增了一个方法,如getPageInfo(String url)方法,而实际对象实现了这个方法,代理对象也必须新增getPageInfo(String url)方法,为其增长代理内容;
五、动态代理
因为静态代理的局限性,孕育而生了动态代理;静态代理是死的,在咱们编译期间即按下CTRL+S的那一刻就给被代理对象生成了一个不可动态改变的代理类,而动态代理就是在运行期间动态生成代理类的一种更为灵活的代理模式;动态代理是JDK自带的功能,它须要实现一个InvocationHandler接口,并调用Proxy的静态方法产生代理类。
Nginx InvocationHandler:
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 import java.util.List; 4 import java.util.UUID; 5 import com.google.common.collect.Lists; 6 /**
7 * Nginx InvocationHandler 8 */
9 public class NginxInvovationHandler implements InvocationHandler { 10
11 //淘宝服务器列表
12 private static final List<String> TAOBAO_SERVER_ADDRESSES = Lists.newArrayList("127.0.0.1","127.0.0.2","127.0.0.3"); 13
14 private Object object; 15
16 public NginxInvovationHandler(Object object) { 17 this.object = object; 18 } 19
20 @Override 21 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 22 String remoteIp = UUID.randomUUID().toString(); 23 int index = Math.abs(remoteIp.hashCode()) % TAOBAO_SERVER_ADDRESSES.size(); 24 String realTaobaoIp = TAOBAO_SERVER_ADDRESSES.get(index); 25 //执行速度:StringBuilder > StringBuffer > String
26 StringBuilder sb = new StringBuilder(); 27 sb.append(" 【页面标题: "); 28 sb.append(method.invoke(object, args)); 29 sb.append("】,【来源Ip: "); 30 sb.append(realTaobaoIp); 31 sb.append("】"); 32 return sb.toString(); 33
34 } 35 }
上面的动态代理NginxInvovationHandler 将选择服务器的逻辑抽象成公共的代码,由于调用的是Object中的method,而Object又是全部类的超类/父类,So并不仅限定于接口Server,任意的其余接口均可以使用该服务,所以该NginxInvovationHandler能够灵活的各处复用。
动态代理测试:
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Proxy; 3
4 /**
5 * 动态代理测试类 6 */
7 public class DynamicProxyTest { 8 public void testDynamicProxy(String url) { 9 TaobaoServer taobaoServer = new TaobaoServer(); 10 InvocationHandler invocationHandler = new NginxInvovationHandler(taobaoServer); 11 //使用Proxy的newProxyInstance方法能够产生对目标接口对一个代理,代理内容则由InvocationHandler实现
12 Server proxy = (Server)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {Server.class}, invocationHandler); 13 System.out.println(proxy.getPageTitle(url)); 14 } 15 public static void main(String[] args) { 16 String url = "http://www.taobao.com"; 17 DynamicProxyTest dynamicProxyTest = new DynamicProxyTest(); 18 dynamicProxyTest.testDynamicProxy(url); 19 } 20
21 }
动态代理程序运行结果:
六、动态代理的优/缺点
优势:
a、减小了类的数量,看起来比较直观;
b、代理内容能够复用;
c、最重要的是动态代理能够在不修改源代码的基础上在源代码上进行操做,如在吐司上抹果酱的AOP原理;
缺点:
其最大的缺点就是只能针对接口生成代理,不能针对某一个类生成代理,如咱们在调用Proxy的newProxyInstance方法时,第二个参数传入的是某个具体类的getClass(),程序就会抛出以下错误:
Exception in thread "main" java.lang.IllegalArgumentException: proxy.DynamicHelloWorldImpl is not an interface
这是由于java.lang.reflect.Proxy 的newProxyInstance 方法会判断传入的Class是否是一个接口,源码以下:
... /* * Verify that the Class object actually represents an * interface. */
if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } ...
而在实际编程中,要为某一个单独的类实现代理很正常,这种状况下咱们就可使用CGLIB(一种字节码加强技术)来为某一个类实现代理。
七、GCLIB动态代理
HelloService业务类:
1 /**
2 * 没有实现任何接口的业务类 3 */
4 public class HelloService { 5
6 public HelloService() { 7 System.out.println("HelloService构造"); 8 } 9
10 //该方法不被覆盖,cglib没法代理由final修饰的方法
11 final public String sayOthers(String name) { 12 System.out.println("HelloService : sayOthers >> " + name); 13 return null; 14 } 15
16 public void sayHello() { 17 System.out.println("HelloService : sayHello"); 18 } 19
20 }
自定义MethodInterceptor类:
1 import java.lang.reflect.Method; 2 import org.springframework.cglib.proxy.MethodInterceptor; 3 import org.springframework.cglib.proxy.MethodProxy; 4
5 /**
6 * 自定义MethodInterceptor 7 */
8 public class MyMethodInterceptor implements MethodInterceptor{ 9
10 /**
11 * sub: cglib生成的代理对象 12 * method: 被代理对象方法 13 * objects: 方法入参 14 * methodProxy: 代理方法 15 */
16 @Override 17 public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 18 System.out.println("<<<<<<<<<<<<插入前置通知>>>>>>>>>>>>"); 19 Object object = methodProxy.invokeSuper(sub, objects); 20 System.out.println("<<<<<<<<<<<<插入后置通知>>>>>>>>>>>>"); 21 return object; 22 } 23
24 }
CGLIB代理对象调用目标方法:
1 import org.assertj.core.internal.cglib.core.DebuggingClassWriter; 2 import org.springframework.cglib.proxy.Enhancer; 3
4 /**
5 * CGLIB代理对象调用目标方法 6 */
7 public class Client { 8 public static void main(String[] args) { 9 //代理类class文件存入本地磁盘,方便反编译查看源码
10 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/TJT/Code"); 11 //经过cglib动态代理 获取代理对象的过程
12 Enhancer enhancer = new Enhancer(); 13 //设置enhance 对象的父类
14 enhancer.setSuperclass(HelloService.class); 15 //设置enhance 的回调对象
16 enhancer.setCallback(new MyMethodInterceptor()); 17 //建立代理对象
18 HelloService proxy = (HelloService)enhancer.create(); 19 //经过代理对象调用目标方法
20 proxy.sayHello(); 21 System.out.println(); 22 //sayOthers() 被fianl修饰不能被cglib代理
23 proxy.sayOthers("涛姐涛哥"); 24 System.out.println(); 25 proxy.sayHello(); 26 } 27
28 }
CGLIB动态代理程序运行结果以下: