代理模式是开发中常用的一种设计模式,每一种设计模式的出现都会极大的解决某方面的问题,代理模式也是一样,本文将会用通俗的语言来解释什么是代理模式?代理模式的种类、代码示例、每种代理模式的优缺点和代理模式适用的场景。
首先我们用一个小故事来描述下什么是代理模式,这会让你更快的理解代理模式的相关角色,为后面的各种代理打下基础。
假如,你是一个大明星,人气很旺,粉丝也特别多。因为人气高,所以很多商家想找你代言广告,但是想要找你代言的人特别多,每个商家你都需要进行商务洽谈,如果聊得不错决定合作,后续还需要签署很多合同文件、记录、备案等。这么多商家找你代言,其中你只能选择其中几个代言,即便只选择几个,你也忙不过来。于是你就想了一个办法,给自己找了一个经纪人,给经纪人制定标准让他去对接各商家,经纪人做事很认真负责,不仅剔除了很多不良的商家还对有资格的商家做了详细的记录,记录商家的代言费、商家详细信息、商家合同等信息。于是在商务代言这件事情上你只需要专心代言拍广告,其他的事情交由经纪人一并处理。
分析下整个事件,可以知道,经纪人就是代理人,明星就是被代理人。在明星的广告代言中,经纪人处理的商务洽谈和签约环节相当于代理,这就是代理模式在实际生活中的简单案例。
其实不止经纪人和明星,生活中还有很多行为本质就是代理模式,比如:某些大牌的饮料三级代理销售、酒水的省市县的代理人、三国时曹操挟天子以令诸侯等等。
说了这么多案例,都是关于代理模式的,那既然这么多人都在用代理模式,那代理模式一定解决了生活中的某些棘手的问题,那究竟是什么问题呢?
在明星和经纪人这个案例中,因为把代言这个商业行为做了细分,让明星团队中每个人负责代言的一部分,使每人只需要专注于自己的事,提高每个人的专业度的同时,也提高了效率,这就叫专业,专人专事。
因为经纪人专注广告代言的代理行为,商业经验丰富,所以经纪人也可以用他的专业知识为其他明星做广告代言的代理,这就叫能力复用。
那么,如何使用代码展示经纪人代理明星的广告行为呢?这其中有是如何运用代理模式的呢?
类比上面的明星和经纪人的例子:
假如有个明星类,我们想在调用明星类的代言方法之前做一些其他操作比如权限控制、记录等,那么就需要一个中间层,先执行中间层,在执行明星类的代言方法。
那讲到这里,想必又有人问,直接在明星类上加一个权限控制、记录等方法不就行了么,为什么非要用代理呢?
这就是本文最重要的一个核心知识,程序设计中的一个原则:类的单一性原则。这个原则很简单,就是每个类的功能尽可能单一,在这个案例中让明星类保持功能单一,就是对代理模式的通俗解释。
那为什么要保持类的功能单一呢?
因为只有功能单一,这个类被改动的可能性才会最小,其他的操作交给其他类去办。在这个例子中,如果在明星类里加上权限控制功能,那么明星类就不再是单一的明星类了,是明星加经纪人两者功能的合并类。
如果我们只想用权限控制功能,使用经纪人的功能给其他明星筛选广告商家,如果两者合并,就要创建这个合并类,但是我们只使用权限功能,这就导致功能不单一,长期功能的累加会使得代码极为混乱,难以复用。
所以类的单一性原则和功能复用在代码设计上很重要,这也是使用代理模式的核心。
而这整个过程所涉及到的角色可以分为四类:
在Java语言的发展中,出现了很多种代理方式,这些代理方式可以分类为两类:静态代理和动态代理,下面我们就结合代码实例解释下,各类代理的几种实现方式,其中的优缺点和适用的场景。
主题接口
package com.shuai.proxy;
public interface IDBQuery {
String request();
}
真实主题
package com.shuai.proxy.staticproxy;
import com.shuai.proxy.IDBQuery;
public class DBQuery implements IDBQuery {
public DBQuery() {
try {
Thread.sleep(1000);//假设数据库连接等耗时操作
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Override
public String request() {
return "request string";
}
}
代理类
package com.shuai.proxy.staticproxy;
import com.shuai.proxy.IDBQuery;
public class DBQueryProxy implements IDBQuery {
private DBQuery real = null;
@Override
public String request() {
// TODO Auto-generated method stub
System.out.println("在此之前,记录下什么东西吧.....");
//在真正需要的时候才能创建真实对象,创建过程可能很慢
if (real == null) {
real = new DBQuery();
}//在多线程环境下,这里返回一个虚假类,类似于 Future 模式
String result = real.request();
System.out.println("在此之后,记录下什么东西吧.....");
return result;
}
}
Main客户端
package com.shuai.proxy.staticproxy;
import com.shuai.proxy.IDBQuery;
public class Test {
public static void main(String[] args) {
IDBQuery q = new DBQueryProxy(); //使用代里
q.request(); //在真正使用时才创建真实对象
}
}
可以看到,主题接口是IDBQuery,真实主题是DBQuery 实现了IDBQuery接口,代理类是DBQueryProxy,在代理类的方法里实现了DBQuery类,并且在代码里写死了代理前后的操作,这就是静态代理的简单实现,可以看到静态代理的实现优缺点十分明显。
优点:
使得真实主题处理的业务更加纯粹,不再去关注一些公共的事情,公共的业务由代理来完成,实现业务的分工,公共业务发生扩展时变得更加集中和方便。
缺点:
这种实现方式很直观也很简单,但其缺点是代理类必须提前写好,如果主题接口发生了变化,代理类的代码也要随着变化,有着高昂的维护成本。
针对静态代理的缺点,是否有一种方式弥补?能够不需要为每一个接口写上一个代理方法,那就动态代理。
动态代理,在java代码里动态代理类使用字节码动态生成加载技术,在运行时生成加载类。
生成动态代理类的方法很多,比如:JDK 自带的动态处理、CGLIB、Javassist、ASM 库。
这里介绍两种非常常用的动态代理技术,面试时也会常常用到的技术:JDK 自带的动态处理、CGLIB 两种。
Java提供了一个Proxy类,使用Proxy类的newInstance方法可以生成某个对象的代理对象,该方法需要三个参数:
初次看见会有些不理解,没关系,下面用一个实例来详细展示JDK动态代理的实现:
代理类的实现
package com.shuai.proxy.jdkproxy;
import com.shuai.proxy.staticproxy.DBQuery;
import com.shuai.proxy.IDBQuery;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DBQueryHandler implements InvocationHandler {
private IDBQuery realQuery = null;//定义主题接口
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果第一次调用,生成真实主题
if (realQuery == null) {
realQuery = new DBQuery();
}
if ("request".equalsIgnoreCase(method.getName())) {
System.out.println("调用前做点啥,助助兴.....");
Object result = method.invoke(realQuery, args);
System.out.println("调用后做点啥,助助兴.....");
return result;
} else {
// 如果不是调用request方法,返回真实主题完成实际的操作
return method.invoke(realQuery, args);
}
}
static IDBQuery createProxy() {
IDBQuery proxy = (IDBQuery) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), //当前类的类加载器
new Class[]{IDBQuery.class}, //被代理的主题接口
new DBQueryHandler() // 代理对象,这里是当前的对象
);
return proxy;
}
}
Main客户端
package com.shuai.proxy.jdkproxy;
import com.shuai.proxy.IDBQuery;
public class Test {
// 客户端测试方法
public static void main(String[] args) {
IDBQuery idbQuery = DBQueryHandler.createProxy();
idbQuery.request();
}
}
用debug的方式启动,可以看到方法被代理到代理类中实现,在代理类中执行真实主题的方法前后可以进行很多操作。
虽然这种方法实现看起来很方便,但是细心的同学应该也已经观察到了,JDK动态代理技术的实现是必须要一个接口才行的,所以JDK动态代理的优缺点也非常明显:
优点:
缺点:
由于必须要有接口才能使用JDK的动态代理,那是否有一种方式可以没有接口只有真实主题实现类也可以使用动态代理呢?这就是第二种动态代理:CGLIB;
使用 CGLIB 生成动态代理,首先需要生成 Enhancer 类实例,并指定用于处理代理业务的回调类。在 Enhancer.create() 方法中,会使用 DefaultGeneratorStrategy.Generate() 方法生成动态代理类的字节码,并保存在 byte 数组中。接着使用 ReflectUtils.defineClass() 方法,通过反射,调用 ClassLoader.defineClass() 方法,将字节码装载到 ClassLoader 中,完成类的加载。最后使用 ReflectUtils.newInstance() 方法,通过反射,生成动态类的实例,并返回该实例。基本流程是根据指定的回调类生成 Class 字节码—通过 defineClass() 将字节码定义为类—使用反射机制生成该类的实例。
真实主题
package com.shuai.proxy.cglibproxy;
class BookImpl {
void addBook() {
System.out.println("增加图书的普通方法...");
}
}
代理类
package com.shuai.proxy.cglibproxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class BookImplProxyLib implements MethodInterceptor {
/**
* 创建代理对象
*
* @return
*/
Object getBookProxyImplInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(BookImpl.class);
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
// 回调方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始...");
proxy.invokeSuper(obj, args);
System.out.println("结束...");
return null;
}
}
Main客户端
package com.shuai.proxy.cglibproxy;
public class Test {
public static void main(String[] args) {
BookImplProxyLib cglib = new BookImplProxyLib();
BookImpl bookCglib = (BookImpl) cglib.getBookProxyImplInstance();
bookCglib.addBook();
}
}
优点:
CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,弥补了JDK动态代理的缺陷。
缺点:
代理模式有多种应用场合,如下所述:
参考:《代理模式原理及实例讲解 》《为什么使用代理模式》
欢迎关注我的公众号:java之旅
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved