JAVA设计模式之代理模式

Posted 孔子-说

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA设计模式之代理模式相关的知识,希望对你有一定的参考价值。

转自 JAVA设计模式之代理模式

代理模式(Proxy)的定义

为其他对象提供一个代理以控制对某个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。即代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类与委托类通常会存在关联关系,通常需要实现同一个接口。一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

代理模式(Proxy)优缺点

代理模式是一种结构型设计模式。其主要优点如下:

  1. 代理模式实现使用者与真实处理者的分离,降低系统的耦合度;
  2. 调用接口时,便于扩展一些业务无关的其他操作,不影响原系统。

缺点:

  1. 增加类代理角色,性能上比直接使用低。

适用环境:

1、spring框架中的aop技术用到了动态代理模式。

  • 调用者Bean尝试调用目标方法,但是被生成的代理截了胡
  • 代理根据Advice的种类(本例中是@Before Advice),对Advice首先进行调用
  • 代理调用目标方法
  • 返回调用结果给调用者Bean(由代理返回,没有体现在图中)

2、延迟加载场景

延迟加载的思想:如果当前没有使用这个组件时,则不需要真正地去初始化它,而是用一个代理对象去替代它的原有位置。当真正需要使用的时候,才对它进行加载。使用代理模式实现延迟加载是很有意义的,首先从时间轴上分散系统的压力,尤其在系统启动时,不必完成所有的初始化工作,从而加速启动时间;其次,对于很多真事主题而言,在软件启动到系统关闭的整个过程,可能都不会被调用,初始化这些数据无疑是一种资源的浪费。

假设某客户端软件,根据用户请求,去数据库查询数据的功能。在数据库查询前需要获得数据库连接。在系统启动时,初始化系统所有的类,此时尝试获得数据库连接。当系统存在大量类似的操作时(XML解析等),所有这些初始化操作都必须叠加,使得系统很慢。为此,使用代理模式,使用代理类,封装对数据库的查询操作。当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理类什么都不做。当真正开始查询的时候,才初始化查询对象。

3、WebService也是应用到了代理模式

4、远程代理:为一个对象在不同地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实,例如:老阮(MrRuan)在地点A,老李在地点B,餐厅柜台也在地点B,那么老李和老软住在一起(都在地点A住),那么老李就是餐厅(地点B)在老软与老李住处(地点A)的代表。

5、虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真是对象,例如:老阮(MrRuan)在地点A,到餐厅柜台(地点B),因为距离远却是很费劲,而老李刚好在这里(地点B)上班,所以让老李去办是很可行的办法。(不太恰当)

6、安全代理:用来控制真是对象访问时的权限,例如:老阮跟餐厅的柜台MM刚分手不方便去办理,所以需要借助老李去完成事项的办理。

7、智能代理:是指当调用真是的对象时,代理去处理另外一些事情,例如:老李帮助老阮办理卡片激活时,顺便说说老阮的好话,让她俩能够和好。

代理模式(Proxy)的结构

代理模式包含如下角色:

    Subject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口。

    RealSubject:真实主题角色,是实现抽象主题接口的类。

    Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

代理模式包括三种:静态代理、动态代理(也叫JDK代理)、Cglib代理。

代理模式(Proxy)的应用实例

1.静态代理

public interface ISinger 
    void sing();


/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger
    public void sing()
        System.out.println("唱一首歌");
      


/**
 *  代理对象和目标对象实现相同的接口
 */
public class SingerProxy implements ISinger
    // 接收目标对象,以便调用sing方法
    private ISinger target;
    public UserDaoProxy(ISinger target)
        this.target=target;
    
    // 对目标对象的sing方法进行功能扩展
    public void sing() 
        System.out.println("向观众问好");
        target.sing();
        System.out.println("谢谢大家");
    


/**
 * 测试类
 */
public class Test 
    public static void main(String[] args) 
        //目标对象
        ISinger target = new Singer();
        //代理对象
        ISinger proxy = new SingerProxy(target);
        //执行的是代理的方法
        proxy.sing();
    

总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。

  缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。

2.动态代理(也叫JDK代理)

 跟静态代理的前提一样,依然是对Singer对象进行扩展

public interface ISinger 
    void sing();


/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger
    public void sing()
        System.out.println("唱一首歌");
      

这回直接上测试,由于java底层封装了实现细节(之后会详细讲),所以代码非常简单,格式也基本上固定。

调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,写法固定
  • Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
  • InvocationHandler h:事件处理接口,需传入一个实现类,一般直接使用匿名内部类

测试代码

public class SingerJdkProxyTest 
	public static void main(String[] args) 
		Singer target = new Singer();
		ISinger proxy = (ISinger) 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("向观众问好");
						// 执行目标对象方法
						Object returnValue = method.invoke(target, args);
						System.out.println("谢谢大家");
						return returnValue;
					
				);
		proxy.sing();
	

总结:以上代码只有3、10、13、17行是需要自己写出,其余部分全都是固定代码。由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便,具体的底层原理将会在下一小节说明。

缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如没有,则可以使用Cglib代理。

3.Cglib代理

前提条件:

  • 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
  • 目标类不能为final
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/**
 * 目标对象,没有实现任何接口
 */
public class Singer

    public void sing() 
        System.out.println("唱一首歌");
    
public class SingerProxyFactory implements MethodInterceptor 
	// 维护目标对象
	private Object target;

	public SingerProxyFactory(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 static void main(String[] args)
        //目标对象
        Singer target = new Singer();
        //代理对象
        Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.sing();
    

三种代理模式的区别

三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。在Spring的AOP编程中:如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理。

1.静态代理

目标对象要实现接口,而且代理对象需要实现接口。这样的代理类只能代理特定接口的对象,灵活度不够。

2.JDK动态代理

代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

3.CGLib动态代理

代理对象不需要实现接口,目标对象也可以是单纯的一个对象。

以上是关于JAVA设计模式之代理模式的主要内容,如果未能解决你的问题,请参考以下文章

java设计模式之代理模式:

Java 之 设计模式——代理模式

Java设计模式之代理模式

java代码实现设计模式之代理模式

设计模式之代理模式详解(java)

设计模式之代理模式详解(java)