1. 动态代理 95
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
1.1 在内存当中动态生成类的技术常见的包括:95
● JDK动态代理技术:只能代理接口。
● CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
● Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
2. JDK动态代理 96
我们还是使用静态代理中的例子:一个接口和一个实现类。
2.1 OrderService接口
package com.powernode.proxy.service;
/**
* 订单业务接口 96
**/
public interface OrderService { // 代理对象和目标对象的公共接口。
String getName();
/**
* 生成订单
*/
void generate();
/**
* 修改订单信息
*/
void modify();
/**
* 查看订单详情
*/
void detail();
}
2.2 接口实现类OrderServiceImpl
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.3 Client客户端程序 96
我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类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动态代理 客户端程序 96
public 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.1 newProxyInstance 翻译为:新建代理对象 96
也就是说,通过调用这个方法可以创建代理对象。
本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
第一件事:在内存中动态的生成了一个代理类的字节码class。
第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
2.3.2 关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用? 96
第一个参数:ClassLoader loader
类加载器。这个类加载器有什么用呢? 96
在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。
并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
第二个参数:Class[] interfaces 96
代理类和目标类要实现同一个接口或同一些接口。
在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。
第三个参数:InvocationHandler h 97
InvocationHandler 被翻译为:调用处理器。是一个接口。
在调用处理器接口中编写的就是:增强代码。
因为具体要增强什么代码,JDK动态代理技术它是猜不到的。没有那么神。
既然是接口,就要写接口的实现类。
可能会有疑问?
自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。
因为这种调用处理器写一次就好。
注意:代理对象和目标对象实现的接口一样,所以可以向下转型。
2.4 InvocationHandler接口的实现类 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接口? 98
因为一个类实现接口就必须实现接口中的方法。
以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
2. invoke方法什么时候被调用呢?
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
3. invoke方法的三个参数: 99
invoke方法是JDK负责调用的,所以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.1 为什么强行要求你必须实现InvocationHandler接口? 98
因为一个类实现接口就必须实现接口中的方法。
以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
2.4.2 invoke方法什么时候被调用呢?98
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
2.4.3 invoke方法的三个参数: 99
invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。
我们可以在invoke方法的大括号中直接使用。
第一个参数:Object proxy 代理对象的引用。这个参数使用较少。
第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)
第三个参数:Object[] args 目标方法上的实参。
invoke方法执行过程中,使用method来调用目标对象的目标方法。
2.5 客户端测试 100
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动态代理 客户端程序 96
public class Client {
public static void main(String[] args) {
//创建目标对象
OrderService target = new OrderServiceImpl();
//创建代理对象
/*
1. newProxyInstance 翻译为:新建代理对象 96
也就是说,通过调用这个方法可以创建代理对象。
本质上,这个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动态代理的原理就结束了。
不过我们看以下这个代码确实有点繁琐,对于客户端来说,用起来不方便:
2.6 我们可以提供一个工具类:ProxyUtil,封装一个方法:101
package com.powernode.proxy.util;
import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
/**
* 工具类 101
**/
public class ProxyUtil {
/**
* 封装一个工具方法,可以通过这个方法获取代理对象。
* @param target
* @return
*/
public static Object newProxyInstance(Object target){
// 底层是调用的还是JDK的动态代理。
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
}
}
/*OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));*/
// 上面代码通过一个工具类的封装,就简洁了。
OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);
标签:调用,target,invoke,代理,接口,动态,方法
From: https://blog.51cto.com/u_15784725/6454476