代理模式
二十三种设计模式,我们先吃透代理模式,用通俗的图和最通俗的话 来解释什么是代理模式:
代理模式中的重要成员:制作人(代理对象)和 歌星(目标对象),这时候听众(调用服务方) 想听歌星唱一曲《花田里犯了错》,歌星说:最近嗓子不舒服,这首《花田里犯了错》,让制作人传唱给你听。于是制作人把歌星录好的这首《花田里犯了错》传唱给了听众(调用服务方)。
代理模式的核心点是:制作人(代理对象)与 歌星(目标对象).制作人(代理对象)是对歌星(目标对象)的扩展,并会调用 歌星(目标对象)并且使用 歌星(目标对象)里的方法。
搞清楚了代理模式,我们就来看下代理模式的三种实现方式
- 静态代理
- 动态代理
- Cglib代理 接下来,我们就用上述例子来比较这三种代理的区别,先看静态代理。
1、静态代理
接口:金碧辉煌的大房子
/**
* 唱片公司-接口
*/
public interface Dafangzi {
void song();
}
目标对象类:歌星
/**
*歌星
* 住在公司里
**/
public class Girl implements Dafangzi {
@Override
public void song() {
System.out.println("歌星唱歌:《花田里犯了错》");
}
}
代理对象类:制作人
/**
*制作人
*住在公司里
**/
public class ZhizuoProxy implements Dafangzi{
//接收保存目标对象
private Dafangzi dafangzi;
public ZhizuoProxy(Dafangzi dafangzi){
this.dafangzi=dafangzi;
}
@Override
public void song() {
System.out.println("制作人 开始播放 歌星 唱的《花田里犯了错》 ");
dafangzi.song();//执行目标对象(歌星)的方法,这里是歌星唱的歌曲
System.out.println("制作人 播放完毕");
}
}
测试类:客户
/**
*听众 测试听歌
* 静态代理
**/
public class UserTest {
public static void main(String[] args) {
//目标对象(歌星)
Girl star = new Girl();
//代理对象(制作人),把目标对象(歌星) 传给 代理对象(制作人),建立代理关系
ZhizuoProxy proxy = new ZhizuoProxy(star);
//此处执行的是 代理对象的方法 这里是代理对象(制作人)播放的《花田里犯了错》
//并不是歌星本人唱的
proxy.song();
}
}
执行完结果如下
我们总结一下静态代理的特点
1、(优点)听众可以不用见到歌星本人,就可以听到歌星唱的歌!由制作人来传唱歌星唱的歌!并且制作人可以通过技术手段给歌星的歌曲进行修音、配节奏!让听众听到的歌曲更嗨! (不修改目标对象的功能 前提下,就可以扩展、延伸目标对象的功能。)
2、(缺点)无论是歌星本人、还是制作人 都离不开唱片公司!(目标对象、代理对象 都必须实现同一个接口!!!!!)
3、(缺点)如果公司要推出新的业务,比如说唱,那么无论是歌星、制作人 都需要相应的进行录制歌唱歌曲。 (代理对象需要与目标对象实现一样的接口,代理类会很多。如果接口增加方法,目标对象与代理对象都要维护节后新增加的方法。)
知道了静态代理的优点和缺点,我们接下来看下动态代理的特点
2、动态代理
还是以歌星唱歌的demo为例子, 先看图:
超级制作人 SuperProxy(工厂)
import java.lang.reflect.Proxy;
/**
*超级制作人SuperProxy
*不用实现接口
*独立的类
**/
public class SuperProxy {
//维护一个目标对象
private Object obj;
public SuperProxy(Object obj){
this.obj=obj;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("制作人 开始播放 歌星 唱的《花田里犯了错》 ");
//执行目标对象(歌星)的方法,这里是歌星唱的歌曲
Object result = method.invoke(obj, args);
System.out.println("制作人 播放完毕");
return result;
}
);
}
}
测试类:听众
/**
*听众 测试听歌
* 静态代理
**/
public class UserTest1 {
public static void main(String[] args) {
// 目标对象
Dafangzi target = new Girl();
//class dailimoshi.jingtai.Girl
System.out.println(target.getClass());
// 给目标对象(歌星),创建代理对象(超级制作人SuperProxy)
//超级制作人SuperProxy不用局限在唱片公司里,不依赖唱片公司,他可以出差去各地播放歌星的歌曲
Dafangzi proxy = (Dafangzi) new SuperProxy(target).getProxyInstance();
//class com.sun.proxy.$Proxy0 在内存中 动态生成的 代理对象
System.out.println(proxy.getClass());
//此处执行的是 代理对象的方法 这里是代理对象(制作人)播放的《花田里犯了错》
//并不是歌星本人唱的
proxy.song();
}
}
运行后的结果:
可以看到 结果和静态代理一样,歌星(目标对象),没有任何变化,但是制作人(代理对象)变化巨大,和静态代理中的制作人不同,这次的超级制作人满足了以下几个特点:
- 不用待在唱片公司里 (代理对象不需要实现接口,目标对象一定要实现接口,否则动态代理无效)
- 超级制作人(代理对象)需要声明是哪家唱片公司的制作人,以及歌星唱的歌曲风格。(动态代理使用JDK的API,动态的在内存中构建代理对象,需要我们指定创建代理对象、以及目标对象实现的接口的类型。)
Dafangzi proxy = (Dafangzi) new SuperProxy(target).getProxyInstance(); - 此处的动态代理也被称为:接口代理、JDK代理
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现的代理只需要使用newProxyInstance方法,newProxyInstance方法中有三个入参
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader loader:指定当前目标对象(歌星)使用类加载器Class<?>[] interfaces:目标对象(歌星) 实现的接口的类型,使用泛型方式InvocationHandler h:事件处理,执行目标对象(歌星)的方法时,会触发事件处理器的方法,会把当前执行目标对象(歌星)的方法作为参数传入返回给代理对象(超级制作人)
接下来我们看代理的第三种实现方式:Cglib代理
3、Cglib代理
我们回顾下 刚才讲过的静态代理 和 动态代理
- 静态代理要求 :目标对象、代理对象都要实现相同的接口。
- 动态代理要求:目标对象需要实现接口、代理对象不需要。
问题来了,
- 动态代理 代理对象不需要实现接口,目标对象是不是也能不实现接口呢???
- 用歌星的例子来说,我是一个草根歌手 并没有签约任何唱片公司,我能否也被超级制作人代理呢?
- 答案是可以的!使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。 还是上面最初的唱歌例子: 需要引入spring-core的jar包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
目标对象:草根歌星
/**
*草根歌星
**/
public class Boy {
void shuangjg() {
System.out.println("草根明星 唱歌:《双节棍》");
}
}
cglib代理:超级制作人
/**
*Cglib子类代理
*对Boy在内存中 动态构建一个子类对象
**/
public class CgLibProxy implements MethodInterceptor {
//目标对象
private Object target;
public CgLibProxy(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance() {
//1.代理工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类(代理对象)
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("制作人 开始播放 歌曲 ");
//执行目标对象的方法(这里执行的就是草根歌手的自己唱的歌)
Object result = method.invoke(target, args);
System.out.println("制作人 播放完毕");
return result;
}
}
测试类:听众
/**
*听众 听歌
* 静态代理
**/
public class UserTest2 {
public static void main(String[] args) {
//目标对象(草根歌星)
Boy target = new Boy();
//代理对象(超级制作人)
Boy proxy = (Boy)new CgLibProxy(target).getProxyInstance();
//执行代理对象的方法
proxy.shuangjg();
}
}
Cglib是一个强大的高性能的代码生成包。
- 它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception拦截等;
- hibernate用Cglib来实现PO(Persistent Object 持久化对象)字节码的动态生成。
- CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
总结
我们在什么情况下使用代理模式:
- 设计模式的开闭原则,思想是:对修改关闭、对扩展开放,尤其是在代码重构的过程中,我们对其中一些底层代码逻辑无法迅速的下手修改,这时候就可以使用代理类对目标类进行一个扩展、增强。
- Spring的AOP机制就是采用动态代理的机制来实现切面编程。
每个代理方式适合的场景:
- 静态代理 :目标对象、代理对象都需要实现同一个接口 使用的场景:我们在代码编译时就确定了目标对象的类是哪一个,这种就可以使用静态代理。比如:架构解耦过程中的拆分对象,一些底层框架的设计;
- 动态代理 JDK代理:代理对象不需要实现接口、目标对象需要实现,应用的场景比如:RPC框架和Spring AOP机制。
- Cglib动态代理:目标对象、代理对象都不需要实现接口,使用的场景:不需要频繁创建代理对象的应用,比如Spring中默认的单例bean,只需要在容器启动时生成一次代理对象。