概述

定义

  • 代理模式顾名思义,作为某对象的代表,去做某些事情。例如海淘、转运公司,代收快递等,都是生活中的代理模式。
  • 代理模式的英文叫做Proxy或Surrogate。
  • 定义:代理(Proxy)是一种设计模式,为其他对象提供一个代理以控制对某个对象的访问,即通过代理对象访问目标对象。
    • 这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

        + 这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
  • 代理模式的关键点:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象

角色

  • 代理模式包含如下角色:

    • Subject: 抽象角色
    • Proxy: 代理角色
    • RealSubject: 真实角色

为什么会有代理模式?

  • 我们在写一个功能函数时,经常需要在其中写入与功能不是直接相关但很有必要的代 码,如日志记录,信息发送,安全和事务支持等,这些枝节性代码虽然是必要的,但它会带来以下麻烦:

    • 枝节性代码游离在功能性代码之外,它不是函数的目的,这是对OO是一种破坏
    • 枝节性代码会造成功能性代码对其它类的依赖,加深类之间的耦合,可重用性降低
    • 从法理上说,枝节性代码应该监视着功能性代码,然后采取行动,而不是功能性代码 通知枝节性代码采取行动,这好比吟游诗人应该是主动记录骑士的功绩而不是骑士主动要求诗人记录自己的功绩

应用场景

  • 常见的代理有:

    • 远程代理(Remote Proxy):对一个位于不同的地址空间对象提供一个局域代表对象,如RMI中的stub
    • 虚拟代理(Virtual Proxy):根据需要将一个资源消耗很大或者比较复杂的对象,延迟加载,在真正需要的时候才创建
    • 保护代理(Protect or Access Proxy):控制对一个对象的访问权限。
    • 智能引用(Smart Reference Proxy):提供比目标对象额外的服务和功能。

示例

静态代理

  • 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
  • 关键:在编译期确定代理对象,在程序运行前代理类的.class文件就已经存在了。
  • 比如:在代理对象中实例化被代理对象或者将被代理对象传入代理对象的构造方法

例子

  • 模拟保存动作,定义一个保存动作的接口:IUserDao.java,然后目标对象UserDao.java实现这个接口的方法,此时如果使用静态代理方式,就需要在代理对象(UserDaoProxy.java)中也实现IUserDao接口.调用的时候通过调用代理对象的方法来调用目标对象.
  • 需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
接口:IUserDao.java

public interface IUserDao {
void save();
}
目标对象类:UserDao.java public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
代理对象:UserDaoProxy.java public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
} public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
测试类:App.java public class App {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao(); //代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target); proxy.save();//执行的是代理的方法
}
}
  • 静态代理总结: 可以做到在不修改目标对象的功能前提下,对目标功能扩展.
  • 缺点:代理类和委托类实现相同的接口,同时要实现相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

  • 动态代理有以下特点:

    • 在运行期,通过反射机制创建一个实现了一组给定接口的新类
    • 在运行时生成的class,必须提供一组interface给它,然后该class就宣称它实现了这些 interface。该class的实 例可以当作这些interface中的任何一个来用。但是这个Dynamic Proxy其实就是一个Proxy, 它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工 作。
    • 动态代理也叫做:JDK代理,接口代理
    • 接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

JDK中生成代理对象的API

  代理类所在包:java.lang.reflect.Proxy

  JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
  注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
  • ClassLoader loader:指定当前目标对象使用类加载器,用null表示默认类加载器
  • Class [] interfaces:需要实现的接口数组
  • InvocationHandler handler:调用处理器,执行目标对象的方法时,会触发调用处理器的方法,从而把当前执行目标对象的方法作为参数传入

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。
Object invoke(Object proxy, Method method, Object[] args)

代码示例:

接口类IUserDao.java以及接口实现类UserDao是一样的.在这个基础上,增加一个代理工厂类(ProxyFactory.java),将代理类写在这个地方,然后在测试类中先建立目标对象和代理对象的联系,然后使用代理对象中的同名方法

代理工厂类:ProxyFactory.java

/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
测试类:App.java /**
* 测试类
*/
public class App {
public static void main(String[] args) {
// 目标对象
IUserDao target = new UserDao();
// 【原始的类型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass()); // 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass()); // 执行方法 【代理对象】
proxy.save();
}
}
  • 总结:

    • 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
    • 代理中的异常信息,需要特殊处理,抛出真实的异常信息
    @Override
    public Object invoke(Object instance, Method method, Object[] args) throws Throwable {
    String clazz = manager.getClass().getName();
    String methodName = method.getName();
    if (args==null)
    {
    try {
    return method.invoke(manager, args);
    } catch (InvocationTargetException e) {
    throw e.getCause();// 关键在于要抛出真实的异常,如果多层次代理调用,则需要循环处理
    }
    }
    ...
    }

代码示例2

  • 使用动态代理,增加接口的权限验证
  • PrivilegeValidationProxy代理类,代理调用时,增加权限验证valid
  • DatasetManager,真实对象
public class PrivilegeValidationProxy<T extends MetaBean> implements InvocationHandler {

	protected MetaManager<T> manager;
public PrivilegeValidationProxy(MetaManager<T> manager) {
super();
this.manager = manager;
} @Override
public Object invoke(Object instance, Method method, Object[] args) throws Throwable {
...
// 验证权限
valid();
method.invoke(manager, args); }
} public class DatasetManager extends MetaManager<DatasetBean> implements DatasetManagerI {
public static DatasetManagerI instance = newInstance();
public static DatasetManagerI getInstance() {
return instance;
}
public static DatasetManagerI newInstance() {
DatasetManager manager = new DatasetManager();
return (DatasetManagerI) Proxy.newProxyInstance(manager.getClass().getClassLoader(),
new Class[] { DatasetManagerI.class }, new PrivilegeValidationProxy<DatasetBean>(manager));
}
}

Cglib代理

  • 适用于被代理的对象未实现任何接口;
  • 上面的静态代理和动态代理模式都是要求目标对象实现一个接口或者多个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用构建目标对象子类的方式实现代理,这种方法就叫做:Cglib代理
  • Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring + AOP和synaop,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的子类.
    • 代理的类不能为final,否则报错;目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

代码示例

目标对象类:UserDao.java

/**
* 目标对象,没有实现任何接口
*/
public class UserDao { public void save() {
System.out.println("----已经保存数据!----");
}
}
Cglib代理工厂:ProxyFactory.java /**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target; public ProxyFactory(Object target) {
this.target = target;
} //给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
} @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
测试类: /**
* 测试类
*/
public class App { @Test
public void test(){
//目标对象
UserDao target = new UserDao(); //代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法
proxy.save();
}
}

AOP(AspectOrientedProgramming):

  将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码---解耦。

**在Spring的AOP编程中:

  • 如果加入容器的目标对象有实现接口,用JDK代理
  • 如果目标对象没有实现接口,用Cglib代理**

参考

设计模式——proxy代理模式的更多相关文章

  1. C++设计模式-Proxy代理模式

    Proxy代理模式 作用:为其他对象提供一种代理以控制对这个对象的访问. 代理的种类: 如果按照使用目的来划分,代理有以下几种: 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代 ...

  2. php设计模式 Proxy (代理模式)

    代理,指的就是一个角色代表另一个角色采取行动,就象生活中,一个红酒厂商,是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务.而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行 ...

  3. [C++设计模式] proxy 代理模式

    代理模式:为其它对象提供一种代理以控制对这个对象的訪问. Proxy: 保存一个引用使得代理能够訪问实体.若RealSubject和Subject的接口同样,Proxy会引用Subject,就相当于在 ...

  4. C#设计模式(13)——代理模式(Proxy Pattern)

    一.引言 在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代 ...

  5. php设计模式之Proxy(代理模式)和Facade(外观)设计模式

    Proxy(代理模式)和Facade(外观)设计模式它们均为更复杂的功能提供抽象化的概念,但这两种实现抽象化的过程大不相同 Proxy案例中,所有的方法和成员变量都来自于目标对象,必要时,该代理能够对 ...

  6. java设计模式之Proxy(代理模式)

    java设计模式之Proxy(代理模式) 2008-03-25 20:30 227人阅读 评论(0) 收藏 举报 设计模式javaauthorizationpermissionsstringclass ...

  7. 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)

    原文:乐在其中设计模式(C#) - 代理模式(Proxy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 代理模式(Proxy Pattern) 作者:webabcd 介绍 为 ...

  8. 设计模式之代理模式之二(Proxy)

    from://http://www.cnblogs.com/xwdreamer/archive/2012/05/23/2515306.html 设计模式之代理模式之二(Proxy)   0.前言 在前 ...

  9. 设计模式12: Proxy 代理模式(结构型模式)

    Proxy 代理模式(结构型模式) 直接与间接 人们对于复杂的软件系统常常有一种处理手法,即增加一层间接层,从而对系统获得一种更为灵活.满足特定需求的解决方案.如下图,开始时,A需要和B进行3次通信, ...

随机推荐

  1. 1099 Lottery

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1099 英文水平太差了,题目实在是不知道是什么意思,然后看了其他高手写的思路,才看明白. 题意,收集n张彩票 ...

  2. 流畅的Python读书笔记(二)

    2.1 可变序列与不可变序列 可变序列 list. bytearray. array.array. collections.deque 和 memoryview. 不可变序列 tuple. str 和 ...

  3. [P3613]睡觉困难综合征

    Description: 给定一个n个点的树,每个点有一个操作符号 "&" "|" "^" ,以及一个权值 要求支持以下操作: 1. ...

  4. unity游戏设计与实现 --读书笔记(一)

    1,  游戏入门的一些知识点,游戏对象GameObject(角色), 组件Compoent(角色的功能),资源Asset(美术素材呵呵音频等的数据),场景Scene(用以放置各个角色,负责展示画面), ...

  5. python 计算程序运行耗时的好用的代码

    python 计算程序运行耗时的好用的代码: import time start=time.clock() sum=0 for i in range(50): sum=sum+i print(sum) ...

  6. python之列表和生成器表达式篇

    一.协程函数的应用 写一个装饰器用于让协程函数不需要输入再执行一次next()函数 分析: 在装饰器中生成该协程函数的生成器, 并且执行一次next()函数 def firstNext(func): ...

  7. Vmware下mint os的安装

    在vmware 11中,mint倒是很好安装.直接读取iso文件,会自动进入一个类似WindowsPE的安装系统,直接双击开始安装就好,比较傻瓜化. 但是安装好了,字体太小,看着很不舒服,于是就倒腾V ...

  8. javascript——10章 DOM

    1.节点关系 (1)childNodes:返回节点的子节点集合 返回值:NodeList 对象,表示节点集合. 注:childNodes只能取到子级,不能取到子级的子级. childNodes所有节点 ...

  9. Spring Boot @Autowired 没法自动注入的问题

    Application 启动类: @SpringBootApplication @EnableConfigurationProperties @ComponentScan(basePackages = ...

  10. SpringBoot(十):读取application.yml下配置参数信息,java -jar启动时项目修改参数

    读取application.yml下配置参数信息 在application.yml文件内容 my: remote-address: 192.168.1.1 yarn: weburl: http://1 ...