声明:文章仅是为了记录自己的学习成果和分享给大家,参考了这篇文章http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 思路大致一样。不过自己又重新写了一遍,一是增强一下印象,二是写的过程中也有一些新的思考。
代理类与被代理类
被代理类,简单说来就是 需要被增强 的类,增强的理解,简单而言就是在调用前后增加了一些其他操作。代理可以分为静态代理和动态代理。
代理类,假如我们称被代理类为真实的对象,那么代理类就是一个代理对象。我们在调用代理对象的方法的时候,代理对象会自动调用真实对象的相应方法,并对其做出特定的增强。
静态代理,类似于装饰者模式,包装类和被包装类实现了同一个接口,用于统一类型。这样就可以针对于接口中的某一个方法做增强了。但是,如果接口暴露的方法太多,而我们又需要对多个接口做强化,静态代理就显得太麻烦了。
动态代理,在运行时,通过反射的方法,动态的进行方法的增强。动态代理又分为好几种呢!这里只介绍jdk自带的!
注:这只是静态代理和动态代理的小区别,其他的区别在本篇目暂不做深究。
InvoCationHander和Proxy
*** Proxy***,即代理类,它继承自Object类。
InvoCationHander 代理小助手(这个词是我乱翻译的 - - ),他有一个重要的方法,叫做invoke;
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
}
文档对它的描述:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
大意是:InvocationHandler是一个,它被被proxy实例实现。每一个proxy实例都与一个handler相关联。当一个proxy实例所代理方法被调用的时候,将对此次调用进行编码(我的理解就是,对原方法的实现过程重新进行梳理),并将其分发到InvocationHandler中的invoke方法中去(只要调用一次proxy的方法,就会调用一次InvocationHandler的invoke方法)。
抓重点:
- 一个proxy与一个InvocationHandler关联;
- 调用proxy的方法(自然也包括proxy的父类Object的方法如toString啦),会转到InvocationHandler 的 invoke方法上去;
- 2017/05/23补充:代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。(参考于https://www.ibm.com/developerworks/cn/java/j-lo-proxy2/ 这篇文章写的比我不知高到哪里去了..)
一个简单的实现过程###
先定义一个接口IAnimal,暴露一个cry方法。
为什么需要一个接口?自然是统一类型呀。
统一类型以后,proxy和被代理类继承了接口的暴露方法。这样,当我们调用的proxy的方法时,可以代理被代理类中同名方法了。否则,如果要调用被代理类的cry方法,你的proxy难道还要写一个新方法,而且这个新方法又要叫做什么名字呢?
但是,强制要求被代理类也要实现同样的接口。对于未实现该接口的类,代理类将无能为力。
public interface IAnimal {
void cry();
}
再来一个实现类,也就是我们的被代理类,或者说真实对象,即被增强的类。
public class Pig implements IAnimal {
@Override
public void cry() {
System.out.println("喵~喵~喵~~~");
}
}
下面该轮到代理小助手上场了。
注意,bind是自己新加的一个方法,这是为了便于保存被代理对象的引用和生成代理类实例;
invoke方法中的第一个参数proxy指的是代理对象,其实也就是bind方法生成的对象啦,而并不是AnimalProxy本身,否则为嘛要多给出来一个参数,用this就可以了嘛!
public class AnimalProxy implements InvocationHandler {
//被代理对象的引用
private Object realObj = null;
public Object bind(Object target) {
this.realObj = target;
//返回一个代理对象实例
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("-------鉴定假猪过程开始,请听猪叫-------");
Object invoke = method.invoke(realObj, args);
System.out.println("-------鉴定过程结束-------");
return invoke;
}
}
来测试一下
public static void main(String[] args) {
AnimalProxy animalProxy =new AnimalProxy();
IAnimal animal = (IAnimal) animalProxy.bind(new Pig());
animal.cry();
}
控制台打印结果
-------鉴定假猪过程开始,请听猪叫-------
喵~喵~喵~~~
-------鉴定过程结束-------
假如,修改一下代理小助手的实现类中的invoke,多加了一行代码,打印proxy对象
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("-------鉴定假猪过程开始,请听猪叫-------");
Object invoke = method.invoke(realObj, args);
System.out.println( proxy);//多加了这行代码
System.out.println("-------鉴定过程结束-------");
return invoke;
}
看看打印结果
-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
...
Exception in thread "main" java.lang.StackOverflowError
at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:52)
at java.nio.CharBuffer.wrap(CharBuffer.java:350)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:246)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:106)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
at java.io.PrintStream.write(PrintStream.java:476)
at java.io.PrintStream.print(PrintStream.java:619)
at java.io.PrintStream.println(PrintStream.java:756)
at AnimalProxy.invoke(AnimalProxy.java:34)
at com.sun.proxy.$Proxy0.toString(Unknown Source)
at java.lang.String.valueOf(String.java:2826)
...
嗯哼?!堆栈溢出了?为啥??
原因是因为在代理小助手那里,打印了proxy对象,proxy是啥?是代理类的实例啊,还记得么?只要调用一次proxy实例的方法,就又要进入invoke方法一次。what?没调用呀?非也,打印的时候,自动调用了对象的toString()了。so,卡在这死循环了。
以上。