1.动态代理95
在程序运行阶段,为了减少代理的数量,在内存中动态生成代理,称为动态代理。解决代码重用的问题。
1.1动态生成技术在内存中很常见,包括:95●JDK动态代理技术:只能代理接口。
●CGLIB动态代理技术:CGLIB(CodeGenerationLibrary)这是一个开源项目。它是一个强大、高性能、高质量的Code生成类库,可以在运行期间扩展Java类,实现Java接口。它既可以代理接口,也可以代理接口,底层是通过继承来实现的。性能优于JDK动态代理。(底层有一个小而快的字节码处理框ASM。)
●Javassist动态代理技术:Javassist是一个开源分析、编辑和创建Java字节码的类库。它是由东京工业大学数学与计算机科学系的ShigeruChiba(千叶滋)创建的。通过使用Javassist对字节码操作为Jboss实现动态,加入了开源代码JBoss应用服务器项目AOP"框架。
2.JDK动态代理96我们仍然使用静态代理的例子:一个接口和一个实现类。
2.1orderservice接口
package com.powernode.proxy.service;/** * 订单业务界面 96 **/public interface OrderService { // 代理对象和目标对象的公共接口。 String getName(); /** * 生成订单 */ void generate(); /** * 修改订单信息 */ void modify(); /** * 查看订单细节 */ void detail();}
2.2接口实现OrderServiceimplepl
package com.powernode.proxy.service;/** * 接口实现类 96 **/public class OrderServiceImpl implements OrderService{ // 目标对象 @Override public String getName() { System.out.println("getName()执行方法“); return "张三"; } @Override public void generate() { // 目标方法 // 模拟生成订单的耗时 try { Thread.sleep(1234); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(”订单已经生成."); } @Override public void modify() { // 目标方法 // 模拟修改订单的耗时 try { Thread.sleep(456); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(”订单已修改."); } @Override public void detail() { // 目标方法 // 模拟查询订单的耗时 try { Thread.sleep(111); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(”请参阅订单详情."); }}
2.3Client客户端程序966
当我们在静态代理时,除了上述接口和实现类别外,我们是否想写一个代理UserserviceProxy!在动态代理中,可以动态生成UserserviceProxy代理。这个类别不需要写。我们可以直接写客户端程序
package com.powernode.mall;import com.powernode.mall.service.OrderService;import com.powernode.mall.service.impl.OrderServiceImpl;import java.lang.reflect.Proxy;动态代理//jdk 客户端程序 96public class Client { public static void main(String[] args) { // 第一步:创建目标对象: OrderService target = new OrderServiceImpl(); // 第二步:创建代理对象: OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象); // 第三步:调用代理对象的代理方法 orderServiceProxy.detail(); orderServiceProxy.modify(); orderServiceProxy.generate(); }}
以上第二步创建代理对象需要大家理解:
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
这行代码做了两件事:
●第一件事:代理字节码在内存中生成
●第二件事:创建代理对象:
2.3.1newproxyinstance翻译为新代理对象96也就是说,代理对象可以通过调用这种方法来创建。
本质上,这个Proxy.newProxyInstance()执行方法,做了两件事:
第一件事:代理字节码class在内存中动态生成。
第二件事:new对象。代理对象是通过内存中生成的代理代码实例化的。
2.3.2关于newproxyinstance()方法的三个重要参数,每个参数的含义和用途是什么?96第一个参数:ClassLoaderloader
类加载器。这种类型的加载器有什么用?96
内存中生成的字节码也是class文件,必须先加载到内存中才能执行。加载类需要类加载器。因此,这里需要指定类加载器。
而且JDK要求目标类加载器必须与代理类加载器相同。
第二个参数:Class[]interfaces96
代理类和目标类应实现相同的接口或相同的接口。
当代理类在内存中生成时,这个代理类需要你告诉它实现了什么接口。
第三个参数:InvocationHandlerh97
InvocationHandler被翻译成:调用处理器。这是一个接口。
编写在调用处理器接口中的是:增强代码。
JDK动态代理技术是猜不到的,因为要增强什么代码。没那么神圣。
既然是接口,就要写接口的实现类。
可能有问题吗?
你必须手写调用处理器接口的实现类。这不会爆炸吗?不会的。
因为这个调用处理器可以写一次。
注:代理对象与目标对象实现的界面相同,因此可以向下转换。
2.4InvocationHandler接口实现类97所以接下来我们要写java.lang.reflect.InvocationHandler接口的实现类,以及接口中的实现方法,代码如下:
package com.powernode.proxy.service;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 一个专门负责计时的调用处理器对象。 97 * 在此调用处理器中编写与计时相关的增强代码。 * 这个调用处理器只需要写一个。 **/public class TimerInvocationHandler implements InvocationHandler { // 目标对象 private Object target; public TimerInvocationHandler(Object target) { // 赋值给成员变量。 this.target = target; } /* 1. 为什么要求你实现InvocationHandler接口? this.target = target; } /* 1. 为什么要求你实现InvocationHandler接口? 98 因为一个类实现接口必须实现接口中的方法。 以下方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。 注:invoke方法不是我们程序员调用的,而是JDK调用的。 2. invoke方法什么时候调用? 当代理对象调用代理方法时,在InvocationHandler调用处理器中注册的invoke()方法被调用。 3. invoke方法的三个参数: 99 JDK负责调用invoke方法,因此JDK调用此方法时会自动将这三个参数传输给我们。 我们可以直接使用invoke方法的大括号。 第一个参数:Object proxy 引用代理对象。该参数使用较少。 第二个参数:Method method 目标对象上的目标方法。(要实现的目标方法就是它。) 第三个参数:Object[] args 实参的目标方法。 在invoke执行过程中,使用method调用目标对象的目标方法。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这个接口的目的是给你一个写增强代码的地方。 99-100 //System.out.println(增强1); long begin = System.currentTimeMillis(); // 调用目标对象上的目标方法 // 方法四要素:哪个对象,哪个方法,传输哪些参数,返回的价值是什么? Object retValue = method.invoke(target, args); //System.out.println(增强2); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); // 注意invoke方法的返回值,如果代理对象在调用代理方法后需要返回结果,invoke方法必须继续返回目标对象的目标方法执行结果。 return retValue; }}
2.4.为什么强行要求你实现InvocationHandler接口?98
因为一个类实现接口必须实现接口中的方法。
以下方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
注:invoke方法不是我们程序员调用的,而是JDK调用的。
2.4.什么时候调用invoke方法?98当代理对象调用代理方法时,在InvocationHandler调用处理器中注册的invoke()方法被调用。
2.4.三个参数3invoke方法:99JDK负责调用invoke方法,因此JDK调用此方法时会自动将这三个参数传输给我们。
我们可以直接使用invoke方法的大括号。
第一个参数:引用Objectproxy代理对象。该参数使用较少。
第二个参数:Methodmethod目标对象上的目标方法。
第三个参数:Object[]args目标方法的实参。
在invoke执行过程中,使用method调用目标对象的目标方法。
2.5客户端测试1000
package com.powernode.proxy.client;import com.powernode.proxy.service.OrderService;import com.powernode.proxy.service.OrderServiceImpl;import com.powernode.proxy.service.TimerInvocationHandler;import java.lang.reflect.Proxy;动态代理//jdk 客户端程序 96public class Client { public static void main(String[] args) { ///创建目标对象 OrderService target = new OrderServiceImpl(); ///创建代理对象 /* 1. newProxyInstance 翻译为:新代理对象: 96 也就是说,您可以通过调用此方法创建代理对象。 本质上,这个Proxy.newProxyInstance()执行方法,做了两件事: 第一件事:代理字节码class在内存中动态生成。 本质上,这个Proxy.newProxyInstance()执行方法,做了两件事: 第一件事:代理字节码class在内存中动态生成。 第二件事:new对象。代理对象是通过内存中生成的代理代码实例化的。 2. newproxyinstance()方法的三个重要参数是什么意思和用途? 96 第一个参数:ClassLoader loader 类加载器。这种类型的加载器有什么用? 96 在内存中生成的字节码也是class文件,必须先加载到内存中才能执行。加载类需要类加载器。因此,这里需要指定类加载器。 而且JDK要求目标类加载器必须与代理类加载器相同。 第二个参数:Class[] interfaces 96 代理类和目标类应实现相同的接口或相同的接口。 当代理类在内存中生成时,这个代理类需要你告诉它实现了什么接口。 第三个参数:InvocationHandler h 97 InvocationHandler 翻译成:调用处理器。它是一个接口。 编写在调用处理器接口中的是:增强代码。 JDK动态代理技术是猜不到的,因为需要增强哪些代码。没那么神。 既然是接口,就要写接口的实现类。 可能有问题吗? 你必须手写调用处理器接口的实现类。这不会爆炸吗?不会。 因为这个调用处理器可以写一次。 注:代理对象与目标对象实现的界面相同,因此可以向下转换。 */ OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target)); ////调用代理对象的代理方法 // 注:调用代理对象的代理方法时,如果要加强,必须保证目标对象的目标方法的实施。 proxyObj.generate(); proxyObj.modify(); proxyObj.detail(); String name = proxyObj.getName(); System.out.println(name); }}
在这里学习可能会感到有点困惑,辗转反侧很长一段时间,最后这不是写一个接口实现类吗?没有节省精力啊?
这样想你就错了!!!!
我们可以看到,无论你有多少Service接口和业务类别,这个TimerinvocationHandler接口是否只需要写一次,代码是否被重用!!
最重要的是,在未来,程序员只需要关注核心业务的编写,而不需要关注这样的统计时间代码。因为这个统计时间的代码只需要在调用处理器中编写一次。
在这里,JDK动态代理的原理就结束了。
但是,我们看到以下代码确实有点繁琐,对于客户端来说,使用起来并不方便:
