鲁迅:好看的皮囊千篇一律,有趣的灵魂万里挑一
双11的晚上正在狂欢双11的节目,而我似乎没有什么可以要去败的,亲戚送的口红,至今没有用完,台湾搬回来的几盒面膜,至今没用几张。。。脑子里,突然闪过JAVA注解,反射,于是在双11节目吆喝叫卖中,跳转到了动态代理这里。
在知乎上,读过一篇关于注解原理的文章,注解元数据定义+ 反射 + Invoke注入完成了手写注解的整个过程。
在该文章中,延伸到了动态代理这个概念,引起了我这个小白的好奇,其实看了那么多的文章,觉得都没有很通俗的把代理这个概念解释好,一般的解释是一种设计模式。对我而言,最好懂的解释如下:
我们在用第三方库的时候,不方便去更改代码,那么我们可以在以第三方实例外面再包一层,在调用第三方方法之前可以做一些准备工作。比如,执行方法前,记录类状态,写入log,监控xx变量等等。
具体看一个网上的例子:
首先是两个接口:
public interface Fly {
public void fly();
}
public interface Run {
public void run();
}
真实类,这个是真正做工作的地方,实现Fly和Run接口:
public class Animal implements Fly, Run{
public static final String TAG = "ProxyTest";
@Override
public void fly() {
Log.i(TAG, "Animal fly");
}
@Override
public void run() {
Log.i(TAG, "Animal run");
}
}
现在如果要在fly()和run()方法调用之前做些准备工作该怎么办呢?我们可以实现一个代理类。其实真正做工作的还是animal,只不过用来静态的代理模式后,我们可以在调用方法或者之后做些其他的工作。
public class AnimalProxy implements Fly, Run{
private Animal animal;
public AnimalProxy(Animal animal) {
this.animal = animal;
}
@Override
public void fly() {
Log.i(Animal.TAG, "In AnimalProxy fly");
animal.fly();
}
@Override
public void run() {
Log.i(Animal.TAG, "In AnimalProxy run");
animal.run();
}
}
在静态代理中可以看到Proxy这个类做的工作无非就是在RealSubject的工作之前或之后插入其他的业务代码,如果每次都手动去实现Proxy类会比较繁琐,Java中引入了第三个类InvocationHandler,用来统一映射Proxy方法调用到RealSubject上。通过newProxyInstance可以帮助我们动态生成Proxy类实例。
于是,动态代理产生了
动态代理中,两个重要的方法: 一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class)。
还是以上面例子来做示范,首先看到的是直观的Proxy代理类实例,下面具体讲讲。
private static Object getAnimalBase(Aminal mAnimal){
//获取动态代理对象
return Proxy.newProxyInstance(mAnimal.getClass().getClassLoader(),
mAnimal.getClass().getInterfaces(),
new ProxyHandler(mAnimal));
}
这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
然后是添加ProxyHandler这个InvocationHandler的实现,其中,Fly接口和Run接口不变:
public class ProxyHandler implements InvocationHandler{
// 这个就是我们要代理的真实对象
private Object subject;
// 构造方法,给我们要代理的真实对象赋初值
public ProxyHandler(Object subject)
{
this.subject = subject;
}
/**
* 这个方法会自动调用,Java动态代理机制
* 会传入下面是个参数
* @param Object proxy 代理自身的一个对象,不同于被代理对象
* @param Method method 被调用方法
* @param Object[] args 方法参数
* 这种代理机制是面向接口,而不是面向类的
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object ret = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");
return ret ;
}
}
这里,你一定会跟我一样,注意到invoke方法里面,三个参数,并没有用到第一个参数Object proxy,那么它是什么意思呢?
可以尝试在上面的代码里,加入打印代码看看它的name是什么?
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass().getName());
return method.invoke(object,args);
}
你会看到它打印出:$Proxy0,其实它就是代理类的一个实例,具体作用,我也不清楚,网上有人说,它并没有什么卵用。。。
重点来了!!!
通过上面对动态代理两个重要方法的分析,可以看出Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
补充
那些听起来好像很高深的AOP编程,其实就是用到了动态代理。了解实现原理最重要。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
本文中提到的例子以及部分代码来自以下大神:
使用java原生动态代理实现AOP
动态代理
Java动态机制详解
其实,我已经把他们的精华重新书写了,感觉自己棒棒哒。。。o( ̄︶ ̄)o
网友评论