设计模式之代理模式 原创
春节不停更,此文正在参加「星光计划-春节更帖活动」
日积月累,水滴石穿 😄
什么是代理模式
代理模式,给某对象提供一个代理对象,并由代理对象控制对原对象的引用。讲的直白一点,就是找个人来帮我干原本由我来干的事情,而且找来的这个人干的可能比我还好。
代理模式的组成
-
抽象角色:通过接口或抽象类声明真实角色需要实现的业务方法。也就是要干的事情,比如结婚、找房。
-
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。也就是找过来的人,比如婚庆公司、房产中介。
-
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。真实角色也就是我,我要结婚、我要找房。
代理模式的好处
- 功能增强
- 保护目标对象
- 一定程度上降低了系统的耦合度
实现代理模式的方式
- 静态代理:由我们程序员创建一个 Java 类,作为代理类。需要代理的真实对象是已经确认了的。
- 动态代理
- JDK动态代理:利用反射机制动态地生成代理对象。要求目标类与代理类实现相同的接口,若目标类没有实现接口,则无法使用 JDK 动态代理。
- Cglib动态代理:
Cglib
既可代理接口又可以代理类。通过继承目标类,生成子类。在生成的子类中重写父类的方法,也就是重写目标类的方法,实现对其功能的增强。既然是继承,那就要求目标类不能是final
的,方法也不能是final
的。
静态代理
就拿结婚来举例吧!
- 结婚 属于抽象角色,也就是要干的事情。
- 婚庆公司属于代理角色,帮忙布置婚礼现场、录制、主持婚礼。
- 结婚人,属于真实角色。结婚人就想结婚然后洞房而已,为什么还要额外做这么多事呢,所以就找一家婚庆公司。
实例
- 提供抽象角色,里面有业务方法 marry。
/**
* 结婚
*/
public interface Marry {
String marry(String marryPerson);
}
- 代理角色,实现抽象角色。
/**
* 婚庆公司
*/
public class Wedding implements Marry{
/**
* 代理对象中包含真实对象,而且这真实对象是确认的
*/
private MarryPerson target = new MarryPerson();
public String marry(String marryPerson) {
marryBefore();
String marry = target.marry(marryPerson);
marryAfter();
return marry;
}
/**
* 结婚之前
*/
private void marryBefore(){
System.out.println("正式结婚前:婚庆公司布置现场、主持现场");
}
/**
* 结婚之后
*/
private void marryAfter(){
System.out.println("结婚后:婚庆公司打扫卫生、视频剪辑");
}
}
- 真实角色,也实现抽象角色。
public class MarryPerson implements Marry{
public String marry(String marryPerson) {
System.out.println("我" + marryPerson + "结婚啦");
return marryPerson;
}
}
- 测试
public class MarryMain {
public static void main(String[] args) {
Wedding wedding = new Wedding();
wedding.marry("程序员小杰");
}
}
结果:
正式结婚前:婚庆公司布置现场、主持现场
我程序员小杰结婚啦
结婚后:婚庆公司打扫卫生、视频剪辑
你在准备选择婚庆公司的时候,发现了一家更好的婚庆公司 B,婚庆公司 B 正好在搞周年庆,价格便宜,套餐选项更多,你就选择了 婚庆公司 B 主办你的婚礼。
增加代理类
public class WeddingB implements Marry{
/**
* 代理对象中包含真实对象
*/
private MarryPerson target = new MarryPerson();
public String marry(String marryPerson) {
marryBefore();
String marry = target.marry(marryPerson);
marryAfter();
return marry;
}
/**
* 结婚之前
*/
private void marryBefore(){
System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
}
/**
* 结婚之后
*/
private void marryAfter(){
System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
}
}
- 测试
public static void main(String[] args) {
WeddingB wedding = new WeddingB();
wedding.marry("程序员小杰");
}
结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
优缺点
优点
-
简单,便于理解
-
符合开闭原则的情况下对目标对象进行功能扩展。
缺点
- 需要为每一个目标对象都创建一个或多个代理类,增加了维护成本以及开发成本。
- 抽象角色有所改动会影响到代理角色。
动态代理
JDK代理
使用 JDK 动态代理就不需要手动创建代理类,利用反射机制动态地生成代理对象。要求目标类与代理类实现相同的接口,若目标类没有实现接口,则无法使用 JDK 动态代理。
小杰还是使用结婚来举例吧!进行改造。
- MarryProxy
创建类 MarryProxy
并实现InvocationHandler
接口。
public class MarryProxy implements InvocationHandler {
//内部还是要包含 目标对象,由外部进行传入
private Object target;
public MarryProxy(Object target) {
this.target = target;
}
//重写 invoke 方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//使用反射调用方法
//传入 目标对象 与 方法参数
Object invoke = method.invoke(target, args);
System.out.println("invoke 方法返回值:" + invoke);
after();
return invoke;
}
/**
* 结婚之前
*/
private void before(){
System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
}
/**
* 结婚之后
*/
private void after(){
System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
}
}
- 测试类
public static void main(String[] args) {
//1、创建目标对象 MarryPerson
Marry person = new MarryPerson();
//2、创建 InvocationHandler 对象 并传入目标对象
MarryProxy marryInvocationHandler = new MarryProxy(person);
//3、创建代理对象 ,并将代理对象强转为 Marry 接口,不能转为 MarryPerson 类
//MarryPerson 与 代理对象都是实现 Marry 接口,两者是并列关系
Marry proxy = (Marry) Proxy.newProxyInstance(Marry.class.getClassLoader(),
new Class[] {Marry.class}, marryInvocationHandler);
//4、调用 marry 方法并传入参数 最终会调用 InvocationHandler 接口的 invoke 方法
String s = proxy.marry("程序员小杰");
System.out.println("调用 marry 返回值 = " + s);
}
结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
invoke 方法返回值:程序员小杰
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
调用 marry 返回值 = 程序员小杰
可以发现代理类对象由反射进行产生,并不需要再由程序员手动进行创建了。
在上面我们需要注意几个方法。小杰分别介绍一下:
- newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
newProxyInstance
方法属于Proxy
类的静态方法,Proxy
类位于 java.lang.reflect
包,该方法有三个参数,含义分别如下:
- loader:类加载器,将类加载到 JVM 中。可以使用反射得到,比如:
MarryPerson.class.getClassLoader()
- interfaces:目标类实现的接口,可以传入多个。可以通过反射获取,比如:
MarryPerson.class.getInterfaces()
或者new Class[]{Marry.class}
。这里一定传入接口哦!否则会出Xxx is not an interface
异常。 - h:代理类需要完成的逻辑。
newProxyInstance
方法返回值为:返回代理对象。
- invoke
public Object invoke(Object proxy, Method method, Object[] args)
InvocationHandler
接口位于java.lang.reflect
包中,接口中就一个 invoke
方法,该方法有三个参数,含义分别如下:
- proxy:JDK 动态创建的代理对象。
- method:代理类执行的方法。
- args:需要传给方法的参数。
invoke
方法返回值的含义为:目标方法的返回值。
JDK动态代理
的要求还是挺严格的,必须基于接口创建代理类。如果我就是不想写接口怎么办,有什么方式可以产生代理类吗?那这时候就需要使用 Cglib
动态代理方式了,接下来小杰就来介绍介绍它。
Cglib代理
Cglib
是第三方工具库,Cglib
既可代理接口又可以代理类。通过继承目标类,生成子类。在生成的子类中重写父类的方法,也就是重写目标类的方法,实现对其功能的增强。既然是继承,那就要求目标类不能是 final
的,方法也不能是final
的,如果是 final
那也没办法进行继承。
- 需要加入第三方依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.1</version>
</dependency>
- 创建
MarryCglibProxy
类并继承MethodInterceptor
public class MarryCglibProxy implements MethodInterceptor {
private Object target;
public MarryCglibProxy(Object target) {
this.target = target;
}
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object invoke = method.invoke(target, args);
System.out.println("intercept 方法返回值:" + invoke);
after();
return invoke;
}
/**
* 结婚之前
*/
private void before(){
System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
}
/**
* 结婚之后
*/
private void after(){
System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
}
}
代理接口
public static void main(String[] args) {
//会在 D:\gongj\demo1 目录下生成 Cglib的源码,如果可以反编译工具打开
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"D:\\gongj\\demo1");
//1、创建目标对象 MarryPerson
Marry person = new MarryPerson();
//2、创建 MethodInterceptor 对象 并传入目标对象
MarryCglibProxy marryCglibProxy = new MarryCglibProxy(person);
//3、创建代理对象 ,并将代理对象强转为 Marry 对象
//传入 Marry 接口
Marry proxy = (Marry) Enhancer.create(Marry.class, marryCglibProxy);
//4、调用 marry 方法并传入参数 最终会调用 MethodInterceptor 接口的 intercept 方法
String s = proxy.marry("程序员小杰");
System.out.println("调用 marry 返回值 = " + s);
}
结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
intercept 方法返回值:程序员小杰
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
调用 marry 返回值 = 程序员小杰
- 源码
代理实现类
public static void main(String[] args) {
//会在 D:\gongj\demo1 目录下生成 Cglib 的源码,如果可以反编译工具打开
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"D:\\gongj\\demo1");
//1、创建目标对象 MarryPerson
Marry person = new MarryPerson();
//2、创建 MethodInterceptor 对象 并传入目标对象
MarryCglibProxy marryCglibProxy = new MarryCglibProxy(person);
//3、创建代理对象 ,并将代理对象强转为 Marry 对象,也可以强转为 MarryPerson 对象
// person.getClass():传入 MarryPerson 实现类
Marry proxy = (Marry) Enhancer.create(person.getClass(), marryCglibProxy);
//4、调用 marry 方法并传入参数 最终会调用 MethodInterceptor 接口的 intercept 方法
String s = proxy.marry("程序员小杰");
System.out.println("调用 marry 返回值 = " + s);
}
结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
intercept 方法返回值:程序员小杰
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
调用 marry 返回值 = 程序员小杰
-
源码
Cglib代理
和 JDK 代理
是很相似的,都要实现代理器接口完成。还是需要注意几个方法。小杰分别介绍一下:
- create
public static Object create(Class type, Callback callback)
create
方法属于Enhancer
类的静态方法,Enhancer
类位于 net.sf.cglib.proxy
包,该方法有两个参数,含义分别如下:
- type:指定目标类的类型。可以传入接口或者类,如果传入的是接口,代理类的源码是实现了该接口和
Factory
接口,如果传入的是类,源码是继承了类并实现了Factory
接口。 - callback:回调,意思是我们提供一个方法,它会在合适的时候调用我们提供的方法。
Callback
是一个接口,并不包含方法的声明,我们通常使用它的子接口MethodInterceptor
。
create
方法返回值的为:返回代理对象。
- MethodInterceptor
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
可以发现该方法的参数跟 JDK动态代理
的参数差不多,就是多了一个参数 methodProxy
,其余参数的意义是一致的。
- methodProxy:方法代理,不需要过于关心。
其实不论是使用 JDK动态代理
实现 InvocationHandler
还是使用Cglib动态代理
实现MethodInterceptor
,我们都只需要关心 method
与args
两个参数。