目录
什么是代理
代理在生活中指的是什么?
“有什么事请找我的律师谈”,这里的律师就充当着代理的作用。为了更好达表的我的观点,帮助我处理纠纷,我需要聘请更专业的律师来替我打官司。
在程序原有功能的基础上,现在需要添加新的功能。在不改变原有类的情况下,我们可以创建一个代理类,来代替原有类出面,完成重要的任务。
Spring框架中的AOP切面编程,正是用了代理技术。
很多好用方便的框架,通过动态代理、反射等技术,在不影响业务代码的情况下(保证最小的入侵),替我们完成许多复杂的工作。
代理应用广泛,但别把它想的那么复杂,基本的原理和思想也是来源于生活。
常见的代理方式有:静态代理、jdk动态代理、cglib动态代理。今天暂且整理前两种的实现方式。
静态代理
小明来到了一家管理严格的公司,这个公司有很多的摄像头,当员工操作公司文件的时候,都会很智能的拍摄下来,防止泄密。我们来实现这一功能:
对文件的操作通常有增删改查,先定义一个接口,来描述“操作”。
public interface Operation {
void select();
void add();
void update();
void delete();
}
公司的文件需要实现这一接口,因为它可以被增删改查
public class OfficeFile implements Operation{
@Override
public void select() {
System.out.println("查询公司文件");
}
@Override
public void add() {
System.out.println("添加公司文件");
}
@Override
public void update() {
System.out.println("更换公司文件");
}
@Override
public void delete() {
System.out.println("删除公司文件");
}
}
现在的需求是,在查看和拿走(删除)文件的时候,记录下操作人的行为。
public class OfficeFileProxy implements Operation{
private Operation operation;
public OfficeFileProxy(Operation operation) {
this.operation = operation;
}
@Override
public void select() {
System.out.println("监控发现一个人正在查看公司文件");
operation.select();
System.out.println("此人已离开,拍摄完毕");
}
@Override
public void add() {
operation.add();
}
@Override
public void update() {
operation.update();
}
@Override
public void delete() {
System.out.println("监控发现一个人正在删除公司文件");
operation.delete();
System.out.println("此人已离开,拍摄完毕");
}
}
现在有人来操作文件了,摄像头记录了这一切
public class StaticProxyTest {
public static void main(String[] args) {
OfficeFileProxy officeFileProxy = new OfficeFileProxy(new OfficeFile());
officeFileProxy.delete();
System.out.println("--------------分割线---------------");
officeFileProxy.add();
System.out.println("--------------分割线---------------");
officeFileProxy.select();
}
}
运行结果
此时的OfficeFileProxy就是我们请过来的代理,它可以实现OfficeFile的所有功能,并提供了监控服务。
这就是静态代理的实现方式。
jdk动态代理
动态代理相对静态代理来说,更为高级。它可以动态(运行时)的创建代理类,实现上述功能。
在使用静态代理的过程中,可能会出现多个处理器(例如上述的select、delete),使用动态代理可以将所有的调用重定向到单一处理器上。
入门
先来简化一下难度,假如现在只有一个操作,就是查询。
接口
public interface Operation {
void select();
}
实现类
public class OfficeFile implements Operation{
@Override
public void select() {
System.out.println("查询公司文件");
}
}
创建代理类并测试
(好长的一串,先这么写吧,后面再分解)
public class DynamicProxy {
public static void main(String[] args) {
Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
OfficeFile officeFile = new OfficeFile();
System.out.println("有人要开始查询文件了-----------------");
Object result = method.invoke(officeFile, args);
System.out.println("这个人已经查看完了-----------------");
return result;
}
});
operationProxy.select();
}
}
结果
功能基本达到了,但是有些地方还有问题。
第一点,匿名类这玩意自己用起来感觉很爽,但是看别人用就感觉提高了代码的复杂度,不好理解,那我们先把匿名类分解出来。
主程序简化了:
public class DynamicProxy {
public static void main(String[] args) {
Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
operationProxy.select();
}
}
下面我们来分析一下这几行代码。
1、 Proxy类
jdk提供的代理工具类
2、newProxyInstance()
产生一个代理类的实例,也就是OfficeFile的实例
3、Operation
用Operation来接收这个实例,是因为OfficeFile继承了Operation接口,多态。
4、参数
第一个参数:被代理类的类加载器
第二个参数:被代理类的所有接口的字节码的对象
第三个参数:处理器,在这个对象里加工被代理的类的方法
处理器:
public class OfficeFileInvocationHandler implements InvocationHandler {
private Object target;
public OfficeFileInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("有人要开始查询文件了-----------------");
Object result = method.invoke(target, args);
System.out.println("这个人已经查看完了-----------------");
return result;
}
}
通过构造注入的方式,更加灵活一点。
InvocationHandler也是jdk提供的代理工具类,听名字就知道是个处理器,当代理的的方法被调用时,会自动调用invoke方法。
再看一下Proxy类里面有哪些方法:
分别是:
1、创建一个代理类实例(常用)
2、获取代理类的字节码对象
3、获取处理器
4、判断是否是一个代理类的字节码对象
我们只用了第一个方法。现在我们来走点弯路,用第二个方法创建代理类实例,分解一下过程。
public class DynamicProxy {
public static void main(String[] args) throws Exception {
// Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
// OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
// operationProxy.select();
//获取代理类的字节码对象
Class<?> proxyClass = Proxy.getProxyClass(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces());
//获得代理类的字节码文件
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
//创建代理对象(实例化)
Operation operationProxy = (Operation) constructor.newInstance(new OfficeFileInvocationHandler(new OfficeFile()));
operationProxy.select();
}
}
其实就是通过反射api创建实例的不同方法而已,重点还是反射。
提升
现在来加大难度,完善我们的程序。
首先,将操作拓展为增删改查,并添加参数。
接口
public interface Operation {
void select(String fileName);
void update();
void delete();
void add();
}
处理类
通过方法名判断是否需要进一步加工
public class OfficeFileInvocationHandler implements InvocationHandler {
private Object target;
public OfficeFileInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
if ("select".equals(method.getName())) {
System.out.println("有人要开始查询文件了: " + args[0] + "-----------------");
result = method.invoke(target, args);
System.out.println("这个人已经查看完了-----------------");
} else if ("delete".equals(method.getName())) {
System.out.println("删除了一个文件-------------------");
result = method.invoke(target, args);
System.out.println("这个人已经删除完了-----------------");
} else {
//add、update忽略
result = method.invoke(target, args);
}
return result;
}
}
主程序
public class DynamicProxy {
public static void main(String[] args) throws Exception {
// Operation operationProxy = (Operation) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
// OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
// operationProxy.select();
//获取代理类的字节码对象
Class<?> proxyClass = Proxy.getProxyClass(OfficeFile.class.getClassLoader(), OfficeFile.class.getInterfaces());
//获得代理类的字节码文件
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
//创建代理对象(实例化)
Operation operationProxy = (Operation) constructor.newInstance(new OfficeFileInvocationHandler(new OfficeFile()));
operationProxy.select("企业文化");
operationProxy.add();
operationProxy.update();
operationProxy.delete();
}
}
运行结果:
有人要开始查询文件了: 企业文化-----------------
查询公司文件___企业文化
这个人已经查看完了-----------------
添加公司文件
更新公司文件
删除了一个文件-------------------
删除公司文件
这个人已经删除完了-----------------
为什么被代理类必须实现接口?
为什么这个Operation一直存在,我们不能直接用OfficeFile这个类吗?
从程序本身来看,确实将操作抽象出来比较好,符合面向对象程序设计的规范,也方便运用各种设计模式。
但如果我们就不想用这个接口,直接用实现类试试
public class DynamicProxy {
public static void main(String[] args) throws Exception {
OfficeFile officeFileProxy = (OfficeFile) Proxy.newProxyInstance(OfficeFile.class.getClassLoader(),
OfficeFile.class.getInterfaces(), new OfficeFileInvocationHandler(new OfficeFile()));
officeFileProxy.select("企业文化");
}
}
程序会报错
代理对象不能被转化成OfficeFile对象。
我们点进proxy的源码看看,
获取代理类的字节码对象,再点进去
找到了工厂类ProxyClassFactory,点进去
终于找到了生成代理类字节码文件的地方,可惜返回值是byte数组,我们看不到。
但是如果把字节流写进.class文件,不就可以得到代理类的字节码文件了吗。
仿造上面的方法,填好参数,设置好文件存放位置:
public class GenerateProxyClass {
public static void main(String[] args) {
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
"OfficeFile", com.dayrain.demo.proxy.verson3.OfficeFile.class.getInterfaces(), Modifier.PUBLIC);
File file = new File("E:\\personcode\\base-demo\\class\\OfficeFileProxy.class");
try {
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(proxyClassFile);
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
随便下个反编译工具,打开看看。
代理类已经继承了Proxy,Java中是不允许多继承的,所以只能通过实现接口的方式,来与被代理类产生联系。
问题
jdk的动态代理虽然可以在运行中创建代理对象,但是很依赖接口,没有继承接口,就无法实现代理。
市面上的框架基本都是用的cglib,不需要实现接口,这个日后再做总结。
如果文章有任何问题,欢迎批评指正!