1.简介
在某些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如:购买火车票不一定要去火车站买,可以通过12306网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如:要访问的远程对象比较大,其下载需要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
2.定义
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
3.优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的扩展性。
4.缺点
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
5.结构
代理模式的主要角色如下:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
在代码中,一般代理会被理解为代码增强,实际上就是在源代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理:
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
- 动态:在程序运行时,运用反射机制动态创建而成。
6.应用场景
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要目的就是保护目标对象和增强目标对象。
主要应用场景:
- 远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如:用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理:这种方式通常用于要创建的目标对象开销很大时。例如:下载一副很大的图像需要很长时间,因某种计算比较复杂而段时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理:这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引:主要用于调用目标对象时,代理附加一些额外的处理功能。例如:增加计算真实对象的引用次数的功能,这样当对象没有被引用时,就可以自动释放它。
- 延迟加载:指为了提高系统的性能,延迟对目标的加载。例如:Hibernate中就存在属性的延迟加载和关联表的延迟加载。
7.使用样例
1.静态代理和动态代理
静态代理就是按照代理模式书写的代码,其特点是代理类和目标类在代码中是确定的。因此称为静态。
动态代理也叫JDK代理或接口代理,有以下特点:
- 代理对象不需要实现接口。
- 代理对象的生成是利用JDK的API动态的在内存中构建代理对象。
- 能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为、
一般情况下,动态代理的底层不用我们亲自去实现,可以使用线程提供的API。目前普遍使用的是JDK自带的代理和CGLib提供的类库。
JDK实现代理只需要使用newProxyInstance方法
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, invocationHandler h)
该方法在Proxy类中是静态方法,且接收的三个参数说明依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
- Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
- InvocationHandler h :事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入。
静态代理和动态代理主要有以下几点区别:
- 静态代理只能通过手动完成代理模式,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
- 动态代理采用运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增加逻辑进行扩展,结合策略模式,只需要新增策略类便可完成。无须修改代理类的代码。
下面是代码示例:
使用静态代理实现:张三父亲替张三找辅导老师
class ZhangSan{
public void findTeacher() {
System.out.println("找到符合张三要求的老师");
}
}
class ZhangSanFather{
private ZhangSan zhangSan = new ZhangSan();
public void findTeacher() {
System.out.println("张三的老父亲开始找老师");
zhangSan.findTeacher();
System.out.println("开始学习");
}
}
public class ZhangSanProxyTest {
public static void main(String[] args){
ZhangSanFather zhangSanFather = new ZhangSanFather();
zhangSanFather.findTeacher();
}
}
上面场景有个弊端,就是张三的父亲只会帮张三去挑选辅导老师,别人家的孩子是不会管的。于是社会上这样业务发展成了一个产业,出现了答疑、辅导班、培训机构等,还有各种各样的定制套餐。
这样如果还使用静态代理成本就太高了,需要一个更加通用的解决方案,满足任何人想要找老师的需求。这就由静态代理升级到了动态代理。采用动态代理基本要求只要是人就可以提供找老师服务。
接下来使用JDK动态代理实现:辅导班替李四找辅导老师
interface IPerson{
void findTeacher();
}
class LiSi implements IPerson{
@Override
public void findTeacher() {
System.out.println("找到符合李四要求的老师");
}
}
class JDKCoach implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target){
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target, args);
after();
return result;
}
private void before(){ System.out.println("提供挑选老师服务"); }
private void after(){ System.out.println("开始学习"); }
}
public class CoachProxyTest {
public static void main(String[] args){
JDKCoach proxy = new JDKCoach();
IPerson liSi = proxy.getInstance(new LiSi());
liSi.findTeacher();
}
}
2.使用代理模式切换数据源
在分布式业务场景中,通常会对数据库进行分库分表,这样使用Java操作时就可能需要配置多个数据源,可以通过设计数据源路由来动态切换数据源。
@Getter
@Setter
@ToString
class Order{
private Object orderInfo;
private Long createTime;
private String id;
}
class OrderDao{
public int insert(Order order){
System.out.println("OrderDao创建Order成功");
return 1;
}
}
interface IOrderService{
int createOrder(Order order);
}
class OrderService implements IOrderService{
private OrderDao orderDao;
public OrderService(){
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService调用OrderDao创建订单");
return orderDao.insert(order);
}
}
class DynamicDataSourceEntry{
public final static String DEFAULT_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DynamicDataSourceEntry(){}
public static void clear(){
local.remove();
}
public static String get(){
return local.get();
}
public static void restore(){
local.set(DEFAULT_SOURCE);
}
public static void set(String source){
local.set(source);
}
public static void set(int year){
local.set("DB_" + year);
}
}
public class DataSourceProxy implements InvocationHandler {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private Object target;
private IOrderService orderService;
public IOrderService getInstance(IOrderService orderService) throws NoSuchMethodException {
target = orderService;
Class<?> clazz = target.getClass();
return (IOrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before(args[0]);
Object object = method.invoke(target, args);
after();
return object;
}
private void before(Object target){
System.out.println("代理模式之前的方法");
Long time = null;
try{
time = (Long) target
.getClass()
.getMethod("getCreateTime")
.invoke(target);
}catch (Exception e){
e.printStackTrace();
}
Integer dbRouter = Integer.valueOf(year