假设有一个User类,里面有一个获取用户信息的方法,接口和实现如下:
//接口定义
public interface IUserService {
void getUserInfo();
}
//接口实现
public class UserService implements IUserService{
@Override
public void getUserInfo() {
//这个等待是后面测试用的,可忽略
try {
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("获取User信息");
}
}
当我们正常调用时,只需要实现这个类调用即可。这里并没有使用代理。
IUserService user=new UserService();
user.getUserInfo();
假如现在有一个新需求,需要统计一下这个方法的运行时间。最简单的办法当然是直接在getUserInfo这个方法中增加时间统计代码即可,但是如果是如下情况,该怎么办(不仅指这个小需求):
1、这个类被封在了一个jar包中(静态代理)
2、不允许修改源代码,保持原代码的既有功能不变(静态代理,装饰器模式)
3、旧版代码和新版代码结构差异太大(静态代理,适配器模式)
4、对所有代码增加时间统计(动态代理)
括号里已标注了对应的解决方案。其实在学习设计模式的时候,就感觉装饰器模式和适配器模式很类似,学完代理模式以后,感觉三个类似。。。
从网上找了代理模式的含义:其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
个人理解:在不改变源文件的情况下,通过对原有功能的重写(适配器)、修改(装饰器)、增强(AOP)等方式实现新的目的。
从上面列出的四点就可以看出,代理模式基本上就是要新写一个类来代替这个类执行新功能了。还是从代码看起。
public class UserServiceProxy implements IUserService{
private IUserService user;
public UserServiceProxy(IUserService user) {
this.user=user;
}
@Override
public void getUserInfo() {
System.out.println("begin");
long begin=System.currentTimeMillis();
user.getUserInfo();
System.out.println("end");
System.out.println("一共运行了:"+(System.currentTimeMillis()-begin)+"ms");
}
}
只需要新建一个代理类实现同一个接口,把原始类传进来作为成员变量即可,调用方法如下:
IUserService user=new UserServiceProxy(new UserService());
user.getUserInfo();
这样就实现了不改变源文件的情况下,增强了原有功能。
其实从这里就能看出来,为什么说代理模式和装饰器模式很像,JDK中一个很著名的装饰器模式,就是IO流的处理了,我们经常是这么用的,比如new BufferedReader(new FileReader(file));就是一层一层的封装,每次的封装都是支持前一层操作,然后再增加新的功能,这行代码就是对文件读取支持的基础上,增加了缓冲区。
但是这种方式很笨重,如果再来一个Company类,需要打印时间,那么我们就需要再写一个CompanyProxy类去实现,一定程度上,增加了代码冗余,并且效果还不如直接修改源代码的好。所以就有了动态代理的概念,即在运行时动态获取代理类对象,并对该对象进行增强。
JDK动态代理的实现很简单,实现InvocationHandler 接口,并重写invoke方法,里面的核心语句是 method.invoke(target, args);这句话就是在调用源代码的方法。然后我们就可以在这句话前后,增加我们想要的内容,比如计算代码执行时间等。
public class InvocationHandlerTest<T> implements InvocationHandler {
T target;
public InvocationHandlerTest(T target) {
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("begin");
long begin=System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("一共运行了:"+(System.currentTimeMillis()-begin)+"ms");
System.out.println("end");
return result;
}
}
调用方法如下:
IUserService user=new UserService();
InvocationHandlerTest<IUserService> userInvocationHandler = new InvocationHandlerTest<IUserService>(user);
IUserService userProxy=(IUserService)Proxy.newProxyInstance(IUserService.class.getClassLoader(), user.getClass().getInterfaces(), userInvocationHandler);
userProxy.getUserInfo();
核心是Proxy.newProxyInstance(IUserService.class.getClassLoader(), user.getClass().getInterfaces(), userInvocationHandler);三个参数分别是被代理对象的classloader,接口和代理对象的invocation。
这样就实现了一个动态代理,我们只需要修改这三个参数,就可以将所有的类进行动态代理了。这里要注意的是,jdk的动态代理必须要求有接口,因为它是基于接口实现的动态代理。如果想要不依赖接口实现动态代理,那么可以采用cglib技术实现。
至于Proxy.newProxyInstance是怎么实现动态代理的,容我读几天源码再写吧。