本节要点:
- Java静态代理
- Jdk动态代理
1 面向对象设计思想遇到的问题
在传统OOP编程里以对象为核心,并通过对象之间的协作来形成一个完整的软件功能,由于对象可以继承,因此我们可以把具有相同功能或相同特征的属性抽象到一个层次分明的类结构体系中。随着软件规范的不断扩大,专业化分工越来越系列,以及OOP应用实践的不断增多,随之也暴露了一些OOP无法很好解决的问题。
现在假设系统中有三段完全相似的代码,这些代码通常会采用“复制”、“粘贴”方式来完成,通过这种方式开发出来的软件如图所示:
可能读者已经发现了这种做法的不足之处,如果有一天,蓝色背景的代码需要修改,
那是不是要同时修改三个地方?如果不仅仅是这三个地方包含这段代码,而是100个
,甚至是1000个地方,那会是什么后果?
记录日志在代码中无处不在---先来看一个例子:
为了跟踪应用程序的运行过程,很多方法都需要记录日志信息。我们一般这样写:
/log4j的使用见文章“log4j介绍” import org.apache.log4j.Logger; public class Person { private Logger logger = Logger.getLogger(Person.class); public void sleep(){ logger.info(“开始执行时间:“ + new Date()); System.out.println("睡觉中"); logger.info(“执行结束时间:” + new Date()); } public void eating(){ logger.info("开始执行时间:“ + new Date()"); System.out.println("正在吃饭中"); logger.info("“执行结束时间:” + new Date()"); } }
问:弊端在哪里?
- l 混淆了业务方法本身的职责
- l 维护工作量巨大
2 解决方案1-静态代理
- l 目地是将业务代码与日志代码完全分离,实现松散耦合.
- l 代理对象与被代理对象必须实现同一接口,在代理对象中实现与日志记录的相关服务,并在需要的时候呼叫被代理对象,而被代理对象只保留业务代码.
静态代理的实现
1) 定义接口:
public interface IPerson { public abstract void sleep(); public abstract void eating(); }
2) 被代理类
public class Person implements IPerson { public void sleep(){ System.out.println("睡觉中"); } public void eating(){ System.out.println("正在吃饭中"); } }
import org.apache.log4j.Logger; public class PersonProxy implements IPerson { private IPerson person; private Logger logger = Logger.getLogger(PersonProxy.class); public PersonProxy(IPerson person) { this.person = person; } public void eating() { logger.info("开始执行时间:“ + new Date()"); person.eating(); logger.info("“执行结束时间:” + new Date()"); } public void sleep() { logger.info("开始执行时间:“ + new Date()"); person.sleep(); logger.info("“执行结束时间:” + new Date()"); } }
4) 测试类
package com.aptech.aop2; public class PersonTest { public static void main(String[] args) { IPerson proxy = new PersonProxy(new Person()); proxy.eating(); proxy.sleep(); } }
静态代理的弊端:
一个代理接口只能服务于一种类型的对象.对于稍大点的项目根本无法胜任.
3 解决方案2-动态代理
- 在JDK1.3之后加入了可协助开发的动态代理功能.不必为特定对象与方法编写特定的代理对象,使用动态代理,可以使得一个处理者(Handler)服务于各个对象.
- 一个处理者的类设计必须实现java.lang.reflect.InvocationHandler接口.
- 通过InvocationHandler接口实现的动态代理只能代理接口的实现类.
动态代理实现
1) 处理者(Handler)
public class DynaProxyHandler implements InvocationHandler { private Logger logger = Logger.getLogger(DynaProxyHandler.class); private Object target;//被代理对象 public void setTarget(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { logger.info("执行开始时间:" + new Date()); Object result = method.invoke(target, args); logger.info("执行结束时间:" + new Date()); return result;//返回method执行结果 } }
2) 生产代理对象的工厂
import java.lang.reflect.Proxy; public class DynaProxyFactory { //obj为被代理对象 public static Object getProxy(Object obj){ DynaProxyHandler handler = new DynaProxyHandler(); handler.setTarget(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler); } }
3) 测试类
public class PersonTest { public static void main(String[] args) { IPerson person = (IPerson) DynaProxyFactory.getProxy(new Person()); //返回代理类,代理类是JVM在内存中动态创建的,该类实现传入的接口数组的全部接口(的全部方法). person.eating(); person.sleep(); } }
4) 将2)和3)合并的写法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; public class PersonTest { public static void main(String[] args) {
//是我们要代理的真实对象
IPerson person1 = new Person();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynaProxyHandler(person1);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数person1.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
IPerson person = (IPerson) Proxy.newProxyInstance(handler.getClass().getClassLoader(), person1.getClass().getInterfaces(), handler); person.eating();
person.sleep();
}
}
我在学习 Java 的动态代理的时候,一直在使用 Proxy.newProxyInstance 方法生成代理的时候报错(如标题)。
我使用各种方法都无法解决这个问题,后来我直接用别人博客上的代码跑了一下,结果成功了。
于是我对比两个代码之间的差别,发现问题可能处在委托对象上面。
我写的代码委托对象是继承一个虚基类,而不是一个接口,于是我把我的代码改了一下,于是乎就跑起来了。。。
总结:要使用动态代理,用来生成代理的委托对象必须是一个接口。