首先了解下动态代理技术出现的背景
在Java中若是需要为一个类扩充功能,大概有这几种方式
1、继承
优点:方便、可以从父类中继承一些成员变量还有方法,若不满意某个方法的实现可以重写这个方法
缺点:由于Java语言只能从一个类,因此不够灵活,并且会使类与类之间的关系耦合度强
2、装饰模式
Java的io中就大量的使用了这个设计模式,把底层的InputStream和OutputStream通过一层层的装饰包装,不断的扩充功能(例如:可以读取字符流,加上缓存等等)
优点:可以动态的给一个对象添加一些职责,可以比继承使用较少的类
缺点:使用装饰模式会产生比使用继承关系更多的对象,出现错误的话不好查,回想一样,刚开始学习java的 io时,是不是感觉有点乱,new出来这个Stream放进那个对象的,一大堆对象,看着就晕,我是这样感觉 的,不知道你们刚学时神马情况,呵呵
3、组合方式:
个人觉得组合方式和装饰模式在语言层面上都差不多(设计模式也是如此,有好几个感觉差不多,主要是解决的问题不同思想不同而已,),内部都有一个被扩展类的引用,在装饰模式中这个引用被传递过来,组合是直接在类中new出来,
组合方式有诸多好处,但是在实际的项目中,需要在不改变原来类的基础上扩展它的功能,需求是多种多样的,可能今天要在dao层上加上统计方法的执行时间,以便于数据库的优化,明天又想加入个日志功能等等,时间一长类的数量就会爆炸性的增长。
正是有这些需求,动态代理技术出现了,它可以动态的生成一段内部有一个被扩展类引用的代码,生成的类类似于用组合这种方式,并把它动态的编译成class文件,然后装载进jvm中执行。著名的框架Spring的核心技术之一aop就是使用的就是动态代理技术。
动态代理初级:
Java中为动态代理提供了支持,用到了类Proxy用于产生代理对理,还有一个接口InvocationHandler,用于在被代理的类的方法前后加入处理逻辑,这个接口内部有一个invoke方法,invoke接受三个参数,
摘自Java api
-------------------------------------------------------------
proxy -在其上调用方法的代理实例
method -对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args -包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
-------------------------------------------------------------
生成代理类的方法Proxy.newProxyInstance,它接受三个参数,第一个参数为被代理对象的类加载器,第二个参数为需要代理的接口,这些接口的所有方法都会被代理,第三个参数为建立的InvocationHandler的实例,现在就举个例子说明吧
由于Jdk提供的代理只能大力接口,所以首先我们建议一个被代理的接口还有他的实现,就用项目中的dao层演示
现在有个用户访问数据库的Userdao,里面有个保存用户的方法,接受一个名字
public interface UserDao {
void save(String username);
}
UserDao的实现类UserDaoImpl
public class UserDaoImpl implements UserDao {
@Override
public void save(String username){
System.out.println(username+ "存进了数据库");
}
}
现在想对UserDaoImpl记录日志,写一个处理类LogHandler实现InvocationHandler接口
public class LogHandler implements InvocationHandler {
//被代理的对象
private Object target;
@Override
public Object invoke(Object proxy,Method method, Object[] args)
throws Throwable{
//对被代理对象target的对应方法进行调用,把代理类接受的参数传递进去
Object result = method.invoke(target, args);
System.out.println(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate()) + "用户" + args[0]+ "保存进数据库");
return result;
}
//接收被代理的对象
public LogHandler(Object target){
super();
this.target = target;
}
}
用于测试的有main的类 ProxyTest
public class ProxyTest {
public static void main(String[] args){
//被代理的UserDaoImpl对象
UserDao ud = new UserDaoImpl();
//生成代理对象,转成UserDao引用使用,注意这里不能转为UserDaoImpl的引用,只能使用对应接口,
//这是由于接口可以规范方法
UserDao proxy = (UserDao)Proxy.newProxyInstance(
ud.getClass().getClassLoader()//加载生成代理类的加载器
, ud.getClass().getInterfaces()//需要代理的接口,这里为UserDao
, newLogHandler(ud));//日志处理类
//对代理对象调用save方法
proxy.save("黑马程序员");
}
}
控制台打印:
黑马程序员存进了数据库
2012-09-06 14:12:23用户黑马程序员保存进数据库
怎么样,这样就为UserDaoImpl动态的增加了功能,呵呵
动态代理中级:
为例使生成代理的过程简单,对jdk的动态代理功能可以做下封装,下面这个类ProxyFactory,可以代理任何的接口,并能在方法的前后都加上逻辑,在出现异常时还能进行处理,此类模仿了Spring对动态代理的支持
处理逻辑的接口Advice
public interface Advice {
//在被代理的方法执行前执行
void before(Object proxy,Method method, Object[] args);
//在被代理的方法执行后执行
void after(Object proxy,Method method, Object[] args);
//出现异常时执行
void exception(Object proxy,Method method, Object[] args);
//在被代理的方法执行前后都执行
void beforeAndAfter(Object proxy,Method method, Object[] args);
}
代理生成工厂ProxyFactory
public class ProxyFactory {
//被代理的对象
private Object target;
//在被代理的对象方法上加入的建议
private Advice advice;
//产生代理的方法
public Object createProxy(){
//和上面那个例子一样,使用jdk的动态代理api
Object proxyObj =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//最主要就是这个地方了,请仔细看里面的逻辑
new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method, Object[] args)
throws Throwable{
Object result= null;
try {
//在被代理方法前执行
advice.beforeAndAfter(proxy, method, args);
advice.before(proxy, method, args);
//调用目标方法
result = method.invoke(target, args);
//在被代理方法执行后执行
advice.after(proxy, method, args);
//呵呵,看到没,beforeAndAfter在前后都执行一次,达到了目的
advice.beforeAndAfter(proxy, method, args);
} catch(Exception e) {
//当出现异常时,就会调用建议中的对异常进行处理的exception方法
advice.exception(proxy, method, args);
}
return result;
}
});
return proxyObj;
}
public ProxyFactory(Object target,Advice advice){
this.target = target;
this.advice = advice;
}
}
测试类AdviceImpl,ProxyFactoryTest
public class AdviceImpl implements Advice {
@Override
public void before(Object proxy,Method method, Object[] args) {
System.out.println("before");
}
@Override
public void after(Object proxy,Method method, Object[] args) {
System.out.println("after");
//为了测试处理异常的方法是否执行,自己搞出来一个异常
Integer.parseInt("黑马程序员");
}
@Override
public void exception(Object proxy,Method method, Object[] args) {
System.out.println("exception");
}
@Override
public void beforeAndAfter(Object proxy,Method method, Object[] args) {
System.out.println("beforeAndAfter");
}
}
public class ProxyFactoryTest {
public staticvoid main(String[] args){
ProxyFactory proxyFactory= new ProxyFactory(new UserDaoImpl(),new AdviceImpl());
UserDao ud =(UserDao)proxyFactory.createProxy();
ud.save("黑马");
}
}
运行结果:
beforeAndAfter
before
黑马存进了数据库
after
exception
怎么样,经过了这层封装,客户端中获得一个代理对象,只需要两行代码而已,并且功能强大,如果愿意的话,还可以继续把代理对象和一个新的Advice扔进去,在获得一个新的代理对象,这样就可以不断的扩展功能了,spring的面向切面编程和声明实的异常处理都是这个原理。
动态代理高级:
动态代理在高级点,就要自己写代理程序了,jdk提供的只能代理接口,并且必须代理接口里的所有方法,自己写的话就试试写个即可以代理接口还可以代理类,还可以代理到具体以一个方法上面,暂时还没研究出来啊。
关于动态代理的知识可以去看下张孝祥老师的Java高新技术,张老师讲的特别好,特别详细,视频下载地址: