虽然我对代理模式理解还是不够深入,但是我觉得这个知识点的学习,让我对技术的学习路线有了一个新的认知,有了一个新的学习方式,感觉终于入门了,希望以后自己在技术之路可以多多坚持与学习。如有不当之处,还望各位不吝赐教。
[TOC]
概述
定义:某个类被代理了,某个类能通过代理类调用其方法。A是代理类,B是被代理类,可以通过代理类B实现对对A方法的调用。
好处:如果既想用A类的方法,又觉得A类不够完美,所以代理类出现了,在不改变原始类的基础上,可以实现对类的增强。
如下几种代理模式,只是实现不同,场景都相同。
静态代理模式
- 场景:在咖啡厅写的博客,同时我也比较喜欢喝咖啡,所以用来当场景了,感觉一个好的场景比喻有利于理解。每个人喜欢喝的口味都不相同,目前咖啡厅的功能只有来一杯咖啡。但是我觉得这个咖啡苦呀,所有我需要加糖,代理类是在原有的基础上实现加糖的。
- 代码:
//咖啡实现类
public class CoffeeImpl implements Coffee {
@Override
public void needCoffee() {
System.out.println(" I need a cup of coffee ");
}
}
//静态代理类
public class ProxyStatic {
private Coffee coffee = new CoffeeImpl();
public void addSugar(){
coffee.needCoffee();
Util.before();
Util.after();
}
}
//test
ProxyStatic proxyStatic = new ProxyStatic();
proxyStatic.addSugar();
静态代理代码的耦合性还是比较高的,代理就是调用原来的方法,外加调用其他的方法,也就是方法的组合调用。
Java动态代理模式
- code:
public class ProxyDynamic implements InvocationHandler{
/**
* 代理的对象
*/
private Object target;
public <T> T getProxy(Object target){
/**
* newProxyInstance的三个参数
* ClassLoader loader:定义了由那个classLoader对象来加载指定的代理类
* Class<?>[] interfaces:读取代理类实现的接口的列表
* InvocationHandler:代理类对象在调用方法时会关联到那个InvocationHandler对象上
*/
this.target = target;
return (T)Proxy.newProxyInstance
(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/**
*
* @param proxy 代理类
* @param method 代理类真实对象所要调用的真实的方法
* @param args: 调用真实方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 利用反射代用被代理类的方法
*/
method.invoke(target,args);
//method.invoke(proxy,args);
Util.before();
Util.after();
return null;
}
}
//test
ProxyDynamic proxyDynamic = new ProxyDynamic();
Coffee coffee = proxyDynamic.getProxy(new CoffeeImpl());
coffee.needCoffee();
通过Proxy.newInstance返回动态代理类的对象。
java动态代理的前提是接口,针对接口进行代理,不能够针对类进行代理。注意代理类中的invoke方法中的proxy参数,是代理类的对象,不能够用来做反射调用,类型不匹配,而且还会造成死循环。
我理解的原理:
- 通过反射获取代理的.class对象,利用反射技术可以调用动态代理中被代理类的方法和代理类中增强的方法。普通的.java文件通过javac生成.class文件,JVM通过解析执行.class文件中的指令进行方法调用。代理类也是一样,只不过代理类的生成.class文件过程是动态的,对.class文件中的方法执行都是一样的。
- 多说几句吧,我也尝试了解了源码,代理类是存放在一个容器中的,如果容器中没有此代理类,那么会通过类加载器ClassLoader和interface去生成相应的代理类,存放到容器中,用的时候,从容器中取出(跟Spring容器中存放的bean方式类似)
Proxy.newInstance()
方法中的参数就是生成代理类的必要条件。个人还是觉得从宏观上理解框架的设计技术学习起来的难度会小一些,掌握了设计思想,对于理解源码其实就没有那么难了,也不用就刻意的去背一些源码了。
Cglib代理模式
- code
public class ProxyCglib implements MethodInterceptor {
private Object target;
/**
* 创建代理类的对象
* @param zlass 被代理类的.class对象
* @param <T>
* @return
*/
public <T> T getProxy(Class<T> zlass){
return (T)Enhancer.create(zlass,this);
}
/**
* 调用代理类的方法
* @param o 代理对象
* @param method 被代理类的方法对象
* @param objects 调用代理方法的参数
* @param methodProxy 代理类方法对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object obj = methodProxy.invokeSuper(o,objects);
Util.before();
Util.after();
return obj;
}
}
cglib代理模式可以代理除了final类以外所有类的方法,cglib相当于java动态代理的一个补充,它的代理是方法级别的,用起来比较灵活。
我理解的原理
public static Object create(Class type, Callback callback) {
Enhancer e = new Enhancer();
e.setSuperclass(type);
e.setCallback(callback);
return e.create();
}
- 上面代码是
Enhancer.create(Class type, Callback callback)
的底层实现,看到superle了吧,是超的意思,没错也就是继承时的父类。通过e.setSuperclass(type);
设置代理的父类是被代理类。在继承时,有一个硬性的条件,如果父类是final修饰符修饰的类,那么此类无法被继承,所以cglib代理无法为final修饰符的的进行代理。 - 继承还有一项特别好的技能,继承父类的一切,对于父类存在的方法,子类可以直接使用吗,所以cglib代理类在继承了被代理类中之后,通过super可以直接调用父类的方法。
Cglib代理模式和Java动态代理模式的区别
项目 | cglib | java |
---|---|---|
代理类型 | 无final修饰类 | 接口 |
方法调用方式 | 调用父类方法 | 反射调用方法 |
实现代理 | 通过反射 | 通过反射 |
优点 | 不需要硬编码接口,代码复用率高 | 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 |
代理模式的应用
- jpa:在jpa中直接定义了方法,然后就可以对数据库进行操作,底层的实现是通过代理完成对方法的实现和数据库的操作
- AOP:切面,在某个是时间,某个地点,动态的将代码嵌入到程序中。切面相当于在切点处对切面的代码进行了代用。
git 地址:https://github.com/jtracydy/SpringBootTestDemo/tree/master/src/main/java/com/demo/w/proxy/own
参考文章
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0