思索:演员为什么要找替人呢?(为什么要利用代理模式呢?)在Java程序中的代理模式的作用:
第一个原因: 拍本身受伤,找个替人(保护本身)
第二个原因: 本身完成不来,这种高度的动作,替人演员可以完成。
是由于不想让观众知道替人演员,这里的观众实在就是“客户端程序”举例子:
如果你利用代理模式的话,对于客户端程序来说,客户端是无法察觉到的,客户端
在利用代理对象的时候就像在利用目标对象。
代理模式是GoF23种计划模式之一。属于结构型计划模式。
- 生活场景1:牛村的牛二看上了隔壁村小花,牛二不好意思直接找小花,于是牛二找来了媒婆王妈妈。这里面就有一个非常典型的代理模式。牛二不能和小花直接对接,只能找一个中间人。其中王妈妈是代理类,牛二是目标类。王妈妈代替牛二和小花先见个面。(现实生活中的婚介所)【在程序中,对象A和对象B无法直接交互时。】
- 生活场景2:你刚到北京,要租房子,可以本身找,也可以找链家帮你找。其中链家是代理类,你是目标类。你们两个都有共同的行为:找房子。不过链家除了满意你找房子,另外会收取一些费用的。(现实生活中的房产中介)【在程序中,功能必要加强时。】
- 西游记场景:八戒和高小姐的故事。八戒要强抢民女高翠兰。悟空得知此事之后怎么做的?悟空幻化成高小姐的模样。代替高小姐与八戒碰面。其中八戒是客户端程序。悟空是代理类。高小姐是目标类。那天夜里,在八戒眼里,眼前的就是高小姐,对于八戒来说,他是不知道眼前的高小姐是悟空幻化的,在他内心里这就是高小姐。所以悟空代替高小姐和八戒亲了嘴儿。这是非常典型的代理模式实现的保护机制。代理模式中有一个非常紧张的特点:对于客户端程序来说,利用代理对象时就像在利用目标对象一样。【在程序中,目标必要被保护时】
- 业务场景:系统中有A、B、C三个模块,利用这些模块的前提是必要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可。代理的逻辑是:哀求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,而且代码也得到了复用。】
其中 Thread.sleep() 方法的调用是为了模拟操作耗时。第一种方案:直接修改Java源代码,:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。如下:
项目已上线,而且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统举行优化。于是项目负责人就下达了这个需求。首先必要搞清晰是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?
解决方案一:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。方案二: 编写业务类的子类,让子类集成业务类,对每个业务方法举行编写。
- 这种方案的缺点:
缺点一:功能需求上虽然实现了,但是违背了OCP开闭原则(修改关闭,这里修改(改了源代码),扩展打开)
复制代码
- 缺点二:代码没有得到复用(相同的代码写了很多遍)
解决方案二:编写业务类的子类,让子类集成业务类,对每个业务方法举行编写第三种方案:利用代理模式(这里采用静态代理)
复制代码
- 缺点一:虽然解决了OCP开闭原则,但是这种方式会导致耦合度很高,因为采用了继承关系
复制代码
- 缺点二:代码没有得到复用(相同的代码写了很多遍)
复制代码
- 缺点三:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象。
解决方案三:代理模式4. 动态代理
目前我们利用的是静态代理,这个静态代理的缺点是什么?
类爆炸,假设系统中N给你有1000个接口,那么每个接口都必要对于代理类,如许类会急剧膨胀,不好维护。再比如:如果系统中业务接口许多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。
怎么解决类爆炸问题?
可以利用动态代理来解决这个问题。
- 动态代理是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态的生成一个class字节码,
- 这个字节码就是代理类。
- 在内存动态的生成字节码代理类的技术。叫做:动态代理
- 由于在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不必要我们写了。类爆炸解决了,而且代码只必要写一次,代码也会得到复用。
Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器)上面的知识铺垫完成了,下面就开始,来实现JDK动态代理复制代码
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h) {
- 第一个参数:ClassLoader Loader 类加载器。在内存中生成了字节码也就是class 文件,要想执行这个字节码,就必要先把这个字节码加载到内存当中的,而加载到内存当中,就必要类加载器,(加载类,就必要类加载器)所以要指定利用哪个类加载器加载。而且用JDK要求,目标类的类加载器器必须和代理类的类加载器利用同一个(基本上都是一致的,由于这里我们就只有目标类的类加载器,代理类在动态生成)。可以通过 对象.getClass().getClassLoader() 获取到对应的类加载器(就是目标对象的类加载器)。
- 第二个参数:Class[] interfaces 接口范例。留意:代理类和目标类要实现同一个接口或同一些接口,在内存当中生成代理类的时候,这个代理类必要知道你告诉它实现哪些接口,从而重写哪些方法()。可以通过对象.getClass().getInterfaces() ** 获取到对应实现类实现的公共接口(就是公共接口**)。
- 第三个参数:Invocation Handler h 调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。是一个接口,在调用处理器接口中编写的就是:加强代码(你要添加扩展的内容) ,由于具体生成的代理类(动态代理类即使),它是猜不到的,你要(具体加强什么代码,扩展什么功能的),没有那么神,所以必要你本身编写,既然是接口,就要写接口的实现类(这里类里面,就是编写你要加强的代码,扩展的内容)。留意:虽然这里的可以用Lamda表达式写(从而不必要单独写个实现类),但是这么做的复用性,并没有单独写好为一个类,复用性高
- 可能会疑问?本身还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会, 由于这种调用处理器是写一次就好了。
末了一个参数:编写实现了InvocationHandler 处理器接口的实现类(用于编写,我们要加强的代码,扩展的功能)。这里我们编写一个名为:TimerInvocationHandler 的类,实现该 InvocationHandler 处理器接口
该InvocationHandler 处理器接口,要重写其中的一个 public Object invoke(Object proxy, Method method, Object[] args) 方法
留意: invoke方法不是我们程序员负责调用,是JDK负责调用的
invoke 方法什么时候被调用呢?
当代理对象调用代理方法(就是目标对象(公共接口)中的方法,)的时候,注册在 InvocationHandler 调用处理器当中的的invoke()方法被调用。这里是:proxy.generate();调用代理对象的代理方法
invoke 方法的三个参数:复制代码
- public Object invoke(Object proxy, Method method, Object[] args)
- invoke 方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这个三个参数
- 我们可以在invoke 方法的大括号中直接使用这三参数,这三个参数并不是 NULL值,而是有值的。
- 第一个参数:Object proxy 代理对象的引用,这个参数使用较少
- 第二个参数:Method method 目标对象上的目标方法,(要执行的目标方法就是它,目标对象的方法就存储在这个参数里)
- 第三个参数:Object[] args 目标方法上的参数
- 返回值:Object 是目标方法中的返回值,如果要接收目标方法中的返回值,就需要用这个:Object[] args 参数 return 回去。
为什么明明调用了,目标对象当中的,generate( )
modify( ),detail( ) 方法 ,却没有执行呢,反而执行的是:调用处理器实现 InvocationHandler
接口中的实现类(当中加强代码,扩展代码/功能)的 invoke( ) 方法呢。
那是由于,我们来必要在,
在invoke() 方法大括号下,调用其中的参数,method.invoke() 方法,执行目标对象当中的方法。
留意这个[method.invoke() ] 调用目标对象上的目标方法;只要代理对象当中注册的InvocationHandler的方法被调用了,这里就会被调用复制代码
- // 方法四要素: 哪个对象,哪个方法,传什么参数,返回什么值
- // target 目标对象,method方法,args 参数,reValue 返回值(返回值,只能返回一个值)
- Object reValue = method.invoke(target, args);
第一步:创建字节码加强器对,利用 new Enhancer()
第二步:告诉CGLIB父类是谁,告诉CGLIB目标类是谁。继续没有其他的就是,父类,就是目标类本身了。复制代码
- // 创建字节码增强器对象
- // 这个对象是CGLIB库当中的核心对象,就是依靠它生成代理类
- Enhancer enhancer = new Enhancer();
第三步:设置回调(等同于JDK动态代理当中的调用处理器复制代码
- // 告诉CGLIB父类是谁,告诉CGLIB目标类是谁。
- enhancer.setSuperclass(UserService.class);
在CGLIB当中不是invocationHandler 接口,是方法拦截器接口,MethodInterceptor
同样和JDK动态代理一样,我们通过实现该接口(增长具体什么代码,增长功能扩展),new对象。如下: 和JDK动态代理原理差不多,在CGLIB中必要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor复制代码
- // 设置回调(等同于JDK动态代理当中的调用处理器:invocationHandler)
- //在CGLIB当中不是invocationHandler 接口,是方法拦截器接口,MethodInterceptor
- enhancer.setCallback(new TimerMethodInterceptor());
编写MethodInterceptor接口实现类:复制代码
- MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
- 第一个参数:目标对象
- 第二个参数:目标方法
- 第三个参数:目标方法调用时的实参
- 第四个参数:代理方法
- 在MethodInterceptor的intercept()方法中调用目标以及添加增强:
第四步:创建代理对象,这一步会做两件事
这里父类(目标对象)是UserService ,子类这个代理类一定是UserService。由于代理类和目标类是继续关系,所以可以强制范例转换(向下转型)
- 第一件事: 在内存中生成UserService类的子类,实在就是代理类的字节码
- 第二件事: 创建代理对象。
复制代码
- // 创建代理对象
- // 这一步会做两件事
- /*
- 第一件事: 在内存中生成UserService类的子类,其实就是代理类的字节码
- 第二件事: 创建代理对象。
- 父类是UserService ,子类这个代理类一定是UserService
- */
- UserService userServiceProxy = (UserService) enhancer.create();
第五步:调用代理对象的代理方法复制代码
- // 建议大家能够把CGLIB动态代理生成的代理对象的名字格式有点印象
- // 根据这个名字可以推测框架底层是否使用了CGLIB动态代理
- System.out.println(userServiceProxy);
- // 底层本质,继承,内存当中生成的继承代理类,同时实例化该代理对象
- // com.rainbowsea.proxy.service.UserService$$EnhancerByCGLIB$$59206fe7@5d3411d extends UserService{}
完整的演示代码:复制代码
- // 调用代理对象的代理方法
- boolean success = userServiceProxy.login("admin", "123");
VM options 填(--add-opens java.base/java.lang=ALL-UNNAMED)如果没有表现:VM options,点击 Modify options 手动添加上。
Program arguments 填(--add-opens java.base/sun.net.util=ALL-UNNAMED)
6. 末了:
- 在Java程序中的代理模式的作用:
- 第一个作用: 当一个对象必要受到保护的时候,可以思量利用代理对象去完成某个行为。
- 第二个作用:必要给某个对象的功能举行功能加强的时候,可以思量找一个代理举行加强。
- 第三个作用:A对象无法和B对象直接交互时,也可以利用代理模式来解决。
- 代理模式中有三大角色:
- 第一个角色:目标对象(演员)
- 第二个角色:代理对象(替人演员)
- 第三个角色:目标对象和代理对象的公共接口,(只有演员和替人演员具有相同的行为动作,才能不被客户端察觉你是)
- 代理模式中的公共接口,让代理类和目标类,具有相同的功能,也可以加强功能,增长了扩展的同时,又防止l 客户端知道我们的目标类的,保护目标类
- 代理模式在代码实现上,包括两种形式: 静态代理,动态代理
- 动态代理:
- JDK动态代理技术:只能代理接口。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继续的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- Javassist 动态代理技术:Javassist 是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和盘算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已参加了开放源代码JBoss 应用服务器项目,通过利用Javassist对字节码操作为JBoss实现动态"AOP"框架
- JDK动态代理技术 Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器),三个参数的利用的作用,以及当代理对象调用代理方法(就是目标对象(公共接口)中的方法,)的时候,注册在 InvocationHandler 调用处理器当中的的invoke()方法被调用。这里是:proxy.generate();调用代理对象的代理方法 invoke 方法中的的三个参数的具体作用。
- JDK动态代理中(获取到目标对象中目标方法的返回值)的方式,这个 method.invoke( ) 方法会,获取到getName()的返回值,虽然可以获取到返回值,但也仅仅只能获取到一个返回值,这一点必要格外的留意。
- CGLIB动态代理:在CGLIB当中不是invocationHandler 接口,是方法拦截器接口,MethodInterceptor
“在这个末了的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) | Powered by Discuz! X3.4 |