设计模式学习之代理模式
最近忙比赛,又学习框架做项目忙的不亦乐乎,但是收获不少,下面几天有必要把最近几周的问题总结一下,一来方便自己以后复习,二来也和大家一起分享一些解决问题的小技巧。下面进入正题,代理模式的学习。
代理模式很好的将两个直接关联的类进行解耦,可以在代理类中加入额外的代码,进行特殊的处理。比如,我们经常能遇到和具体业务无关的代码,如添加日志等,那么,我们就可以在代理中统一的编写添加日志的代码,在被代理类中集中精力编写业务代码。另外,在网络高负载的情况下,可以将一些重要的业务放在被代理中先执行,而次要的业务放在代理类中之后执行,可以给用户良好用户体验。
代理模式分为动态代理和静态代替,静态代理比较好理解,直接看代码。
public interface CustomerDao {
public void buy();
}
//事件:代理模式中要完成的最终事件
public class Customer implements CustomerDao{
@Override
public void buy() {
System.out.println("买东西");
}
}
public class StaticProxy implements CustomerDao{
CustomerDao customerDao = new Customer();
@Override
public void buy() {
System.out.println("买东西之前");
customerDao.buy();
System.out.println("买东西之后");
}
public static void main(String[] args) {//这里为了方便,直接写main方法测试
CustomerDao customerDao = new StaticProxy();
customerDao.buy();
}
}
这段代码很好理解,我随意举了个客户买东西的例子,主要的买的业务在被代理类中书写,代理类中书写买前后有可能的处理,这样一来两个类很好的进行了分离,如果买的业务需要修改,只在自己负责的类中修改就可以了,这样很好的进行了解耦。
这种思想正是大家所熟知的面向对象六大原则的很好的体现,我觉得死记硬背那种概念没有什么意义,理解核心思想就好,比如,单一职责原则,每个类只负责自己的功能;开闭原则,类可以扩展代码,不能修改代码;依赖注入原则,一个类调用其他类应该调用该类的接口或抽象类,这个在spring中有很好的体现;里氏替换原则;迪米特原则,各个类之间要解耦;接口分离原则,一个接口对外只提供一中功能。看来,开发大神们为了降低各个类之间的耦合性可谓是煞费苦心,当然这是在各位前辈在开发中深受修改代码,维护代码等众多问题折磨中总结出来的。那么,回过来看我们的代理模式,正是这种解耦思想的重要体现。
静态代理很好理解,但是实际开发中用的非常少,原因在于当项目变得非常庞大时,代理类就会变得非常多,整个项目就会变得臃肿,这不是我们想要的,那么动态代理就来了,他是在运行中动态的生成代理对象,减少了实际存在的代理类的数量。最近学习SSM框架,像mybatis,spring等框架许多组件的底层的实现就是用的动态代理,他们具体的底层的动态代理的实现我会后面的博客与大家一起分析。下面直接上代码
public interface Customer { //定义被代理类接口,两个方法
public void buyBed();
public void buyPhone();
}
public class CustomerImpl implements Customer{
//该接口的实现类
@Override
public void buyBed() {
System.out.println("buyBed");
}
@Override
public void buyPhone() {
System.out.println("buyPhone");
}
}
JDK自带的可以实现JDK动态代理
private Object target = null;//被代理类对象
public Object bind(Object target){//绑定代理类与被代理类
this.target = 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("before doing");
Object object = method.invoke(target, args);
System.out.println("after doing");
return object;
}
public static void main(String[] args) {//测试方法
DynamicProxy dynamicProxy = new DynamicProxy();
Customer proxy = (Customer) dynamicProxy.bind(new CustomerImpl());
proxy.buyBed();
proxy.buyPhone();
}
}
控制台输出:
before doing
buyBed
after doing
before doing
buyPhone
after doing
实现动态代理,在我看来需要两步:
1.建立代理对象和真实对象的关系;这里是使用bind方法完成,首先用类的属性target保存真实的对象,然后通过Proxy类的newProxyInstance方法生成代理对象:Proxy.newProxyInstance(target.getClass().getgetClassLoader(),target.getClass().getInterfaces(),this);
这里,newProxyInstance方法包含三个参数:第一,传进target类的类加载器,二,确定把生成的代理对象下挂到target实现的接口下,三,传进实现方法逻辑的代理类,这里this表示实现InvocationHandler接口,重写invoke方法的类。
2.实现代理逻辑方法;这里通过重写invoke方法实现的,后面我们会发现,调用代理类的业务方法实际底层就是调用代理类的这个invoke方法。invoke方法有三个参数,一,bind方法生成的代理对象,二当前调度的方法,调度方法的参数。
其中有两个特别重要的必须记住类或接口,InvocationHandler 和 Proxy
InvocationHandler主要作用是将target和代理类绑定和提供invoke方法来书写要加入的逻辑,Proxy类中如果查阅API文档会发现,他提供了多个实现代理类的方法,这里我们采用常用的
Proxy.newProxyInstance(target.getClass().getgetClassLoader(),target.getClass().getInterfaces(),this);
使用时需要注意,代理类生成返回的是Object类型,使用时需要向下转型。
可以看到在整个过程中,我们并没有像静态代理一样显式地创建一个代理类,而是通过实现java提供的实现JDK动态代理的InvocationHandler接口来创建一个handler,那么所有需要生成代理类的类都可以通过这一个handler在运行时动态的生成代理类,这样,代理类的数量就会大大减少。
刚开始看到这么个东西,反正我是感到很奇怪,琢磨了好久,我一直好奇他是怎么就能生成代理类呢,那么下面就分享下我的研究心得。
看了很多博客和书,发现大部分说的也不是很明白,大部分博客也都是抄来抄去,其实,最好的方法还是看!源!码!
大家会发现,动态代理底层实现用了大量的反射技术,而反射可以实现运行时动态的修改对象等操作,呢么动态代理可以实现动态生成代理类也是可以理解的。下面看细节。
动态代理中最重要的莫过于这个方法了,我的是JDK1.7,我们跟进去看看
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {//如果handler参数空,抛出异常
throw new NullPointerException();
}
//获取传进来的所有接口
final Class<?>[] intfs = interfaces.clone();
//判断该代理安全性问题,以及是否对装载器可见等
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//获取代理类的class对象
Class<?> cl = getProxyClass0(loader, intfs);
try {
//获取该class对象的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//判断权限问题以及是否需要实现需要特殊权限的非公共接口,
Proxy类中好多地方都需要判断权限及安全性问题
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
//否则,根据构造方法,以及invacationhandler参数通过反射创建代理类实例
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
那么,现在我们就明白了,在动态创建代理类过程中,是通过接口获取到代理类的class对象,然后通过该class对象的构造方法反射生成的代理类,而这个构造方法需要一个参数invocationhandler参数,这里,如果一路跟着源码点进去,会发现,经过许多的判断各种可能出现的权限及异常问题,最后会到Constructor的这么一个方法 public T newInstance(Object ... initargs)
参数就是传过来的invocationhandler参数,只不过在创建的过程中,将参数改成以Object数组的形式传进去。可能好多人还是和我有着一样的好奇心,这个代理对象时如何通过构造函数细节的生成的呢,看constructorAccessor源码我是没看懂,但看大神的博客说是按照jvm生成class的规则生成的,可能有小伙伴听过大名鼎鼎的ASM框架,这些都需要对虚拟机底层字节码文件规范特别熟悉才会理解的比较好吧,等有机会再研究研究。
那么,还有一个问题,Class<?> cl = getProxyClass0(loader, intfs);
这行代码也是很有意思,他是怎么获取到的class对象呢,看代码
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//如果类数量过大,抛出异常
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//否则根据这个方法返回
return proxyClassCache.get(loader, interfaces);
}
这个proxyClassCache点过去,会发现他是一个静态对象,他表示了我们的类加载器和代理类之间的映射。所以proxyClassCache的get方法用于根据类加载器来获取Proxy类,如果已经存在则直接从cache中返回,如果没有则创建一个映射并更新cache表。
这样,我们就明白了整个动态代理的过,其实也没什么神奇的,整个的实现过程也得益于反射的大量使用。我们的这个newProxyInsatance方法实际是对整个过程的封装,让我们总结一下,生成动态代理对象分成这样几个步骤:
1.创建自己实现InvocationHandler接口类的实例
2.通过Proxy类的getProxyClass方法创建动态代理的class对象,这个过程要通过传进去的被代理类classLoader和实现的接口来创建
3.通过反射获取class对象的constructor,
4.向构造函数中传入invocationHandler参数,通过构造函数反射构造代理类实例
整个过程清洗明了,我看网上有些大神还用反编译插件反编译了代理类的class,可以学学,另外我觉得系统的学完jvm会更好的理解反射,那么动态代理的过程应该会更加清楚。
现在流行的框架底层大量使用了动态代理,关于其源码的分析,我会后续更新,错误之处多多指教。