黑马程序员:代理模式(Proxy)

来源:互联网 发布:呼和浩特乾宇网络 编辑:程序博客网 时间:2024/05/21 22:27

------- android培训java培训、期待与您交流! ----------


程序中的代理
在编程的时候,要为已存在的多个具有相同接口的目标类的各个方法增加一些
系统功能。例如,异常处理、日志、计算方法的运行时间、事物管理等等。这种编程就可以理解为程序中的代理。

下面,通过具体的例子了解程序中的代理。
如:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码


注:采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中修改使用的目标类、还是代理类,这样以后很容易切换。

AOP(Aspect onented program)
面向方面的编程

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一
个方面。


如图,有管理学生的类,有管理课程的类,有管理Misc的类这些类都有安全、事物、日志的方法。
安全、事物、日志等功能要贯穿到好多个模块中,所以它们就是交叉业务。

交叉业务的编程问题即为面向方面的编程(AOP)
AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动
到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

动态代理技术
要为系统中的各个接口的类增加代理功能,那将需要太多的代
理类,全部采用静态代理方式,将是一件非常麻烦的事情。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即,动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用做具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类的生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1、在调用目标方法之前
2、在调用目标方法之后
3、在调用目标方法前后
4、在处理目标方法异常的catch块中

分析JVM动态生成的类

例子1:
创建Collection接口的动态类和查看其名称,分析
Proxy.getProxyClass方法的各个参数。
编写列出动态类中的所有构造方法和参数签名
编写列出动态类中的所有方法和参数签名

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);System.out.println(clazzProxy1.getName());System.out.println("----  constructor method list  ----");Constructor[] constructors = clazzProxy1.getConstructors();for (Constructor constructor : constructors) {    String name = constructor.getName();    StringBuilder sBuilder = new StringBuilder(name);    // 单线程使用StringBuilder效率高,多线程使用StringBuffer。StringBuffer在使用时,会考虑线程安全,谨慎。    sBuilder.append('(');    Class[] clazzParams = constructor.getParameterTypes();    for (Class clazzParam : clazzParams) {sBuilder.append(clazzParam.getName()).append(',');    }    if (clazzParams != null && clazzParams.length != 0) {sBuilder.deleteCharAt(sBuilder.length() - 1);    }    sBuilder.append(')');    System.out.println(sBuilder.toString());}System.out.println("----  method list  ----");Method[] methods = clazzProxy1.getMethods();for (Method method : methods) {    String name = method.getName();    StringBuilder sBuilder = new StringBuilder(name);    sBuilder.append('(');    Class[] clazzParams = method.getParameterTypes();    for (Class clazzParam : clazzParams) {sBuilder.append(clazzParam.getName()).append(',');    }    if (clazzParams != null && clazzParams.length != 0) {sBuilder.deleteCharAt(sBuilder.length() - 1);    }    sBuilder.append(')');    System.out.println(sBuilder.toString());}


例子2:
创建动态类的实例对象,用反射获得构造方法,编写一个最简单的InvocationHandler类。调用构造方法创建动态类的实例对象,并将编写的
InvocationHander类的实例对象传进去。

// Object obj = clazzProxy1.newInstance();// newInstance()调用的是无参的构造方法,而生成的类没有无参的构造方法Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);class MyInvocationHandler1 implements InvocationHandler {    @Override    public Object invoke(Object proxy, Method method, Object[] args)    throws Throwable {// TODO Auto-generated method stubreturn null;    }}Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler1());System.out.println(proxy1);

我们可以发现,当打印创建的对象和调用对象的没有返回值的方法和getClass方法时正常。调用其他有返回值的方法报告了异常。

可以将创建动态类的实例对象的代理改成匿名内部类的形式编
写。

总结:让JVM创建动态类及其实例对象,需要给它提供了三个方面信息:
1、生成的类的实现接口
2、产生的类类加载器对象。
3、生成的类中的方法的代码是怎样的,也得由我们提供,把我们的代码写在一个约定好了的接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

把创建动态类和创建实例对象,放在一起写的代码为:
Collection proxy3 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[] { Collection.class }, new InvocationHandler() {    @Override    public Object invoke(Object proxy, Method method,    Object[] args) throws Throwable {return null;    }});

Collection proxy3 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[] { Collection.class }, new InvocationHandler() {    ArrayList target = new ArrayList();    //target是成员变量,每次调用invoke时,操作的target是同一个变量    @Override    public Object invoke(Object proxy, Method method,    Object[] args) throws Throwable {long beginTime = System.currentTimeMillis();Object retVal =  method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println(endTime - beginTime);return retVal;    }});proxy3.add("a");proxy3.add("b");System.out.println("size : " + proxy3.size());

猜想分析动态生成类的内部代码

动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接收InvocationHandler参数的构造方法。
构造方法接收一个InvocationHandler对象,接收对象了要干什么?方法内部的代码会是怎样的?
InvocationHandler接口中定义的invoke方法接收的三个参数是什么意思?
程序中调用的objProxy.add("abc")方法时,设计三要素:objProxy对象、abb方法、“abc”参数
所以
Class Proxy${
add(Object object){
return hander.invoke(Object proxy, Method method, Object[] args);
}
}

所以
上面调用有返回值的方法会报告异常是因为inovke 方法的返回值默认是null,把null转换长整数会报告异常返回void的不报错


面向切面编程就是
就是把切面的代码用对象的方式进行封装,