代理模式

2018年8月8日16:01:54

代理模式

使用场景

代理模式,为其他对象提供一种代理以控制对这个对象的访问。 ------《设计模式:可复用面向对象软件的基础》

1、远程代理:对一个位于不同地址空间的对象提供一个本地代表,隐藏这个对象存在于不同地址空间的事实,例如RMI的stub(RMI也是比较重要的概念)。这个不同的地址空间可以是在同一主机中,也可以是在不同主机中。

2、虚拟代理:如果创建一个开销比较大的对象,可以先创建开销小的代理对象,真实对象只在需要时才被真正创建,延迟加载。

3、安全代理(保护代理):用来控制真实对象访问时的权限。

4、智能引用代理:当调用真实对象时,代理提供一下额外的操作,例如把对象被调用的次数记录下来(即引用计数)

5、缓冲代理:为某一目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

角色

抽象角色(Subject):声明真实对象和代理对象的共用接口,这样使得在任何使用真实对象(RealSubject)的地方都可以使用代理对象(Proxy)。

代理角色(Proxy):代理对象角色内部包含对真实对象的引用,从而可以操作真实对象,同时代理对象提供域真实对象相同的接口以便任何时刻都能代替真实对象。代理对象可以实现上面使用场景所述的功能。

真实角色(RealSubject):代理模式所代表的真实对象。

静态代理

-- 图示

静态代理

-- 代码示例

抽象接口(Subject.java):

package com.mingmingcome.designpattern.proxy.staticproxy;

/** 
 * @ClassName: Subject
 * @Description: 抽象接口:代理对象和真实对象的共用接口
 * @author: luhaoming
 * @date: 2018年8月12日 下午2:56:43
 */
public interface Subject {
	void request();
}

代理类(Proxy.java):

package com.mingmingcome.designpattern.proxy.staticproxy;

/** 
 * @ClassName: Proxy
 * @Description: 代理对象:代理对象角色内部包含对真实对象的引用,从而可以操作真实对象,
 * 同时代理对象提供域真实对象相同的接口以便任何时刻都能代替真实对象。
 * @author: luhaoming
 * @date: 2018年8月12日 下午3:00:10
 */
public class Proxy implements Subject {
	// 真实主题对象的引用
	private Subject subject;
	
	public Proxy(Subject subject) {
		this.subject = subject;
	}
	
	@Override
	public void request() {
		preRequest();
		// 执行真实对象的方法
		subject.request();
		afterRequest();
	}
	
	private void preRequest() {
		System.out.println("这是在调用真实对象之前做的事情。。。");
	}
	
	private void afterRequest() {
		System.out.println("这是在调用真实对象之后做的事情。。。");
	}

}

真实主题类(RealSubject):

package com.mingmingcome.designpattern.proxy.staticproxy;

/** 
 * @ClassName: RealSubject
 * @Description: 真实对象
 * @author: luhaoming
 * @date: 2018年8月12日 下午3:10:59
 */
public class RealSubject implements Subject {
	
	@Override
	public void request() {
		System.out.println("真实的请求");
	}

}

测试类(TestStaticProxy.java):

package com.mingmingcome.designpattern.proxy.staticproxy;

/** 
 * @ClassName: TestStaticProxy
 * @Description: 静态代理测试类
 * @author: luhaoming
 * @date: 2018年8月12日 下午3:22:19
 */
public class TestStaticProxy {

	public static void main(String[] args) {
		Subject subject = new RealSubject();
		Proxy proxy = new Proxy(subject);
		proxy.request();
	}

}

执行结果:

这是在调用真实对象之前做的事情。。。
真实的请求
这是在调用真实对象之后做的事情。。。

总结:

1、可以做到在不修改真实对象的功能下,对目标功能扩展

2、缺点:因为代理对象要与真实对象实现同一个接口,这样就会有很多与真实对象类相对应的代理类。另外,如果接口增删方法,都要同时维护真实对象类和代理对象类。

3、在编译期就决定了代理类的功能,决定为哪个真实对象代理,以后不可修改

讨论

上面我们用静态代理的方式实现了代理功能,那为什么还需要动态代理呢?讨论一下两种情况:

1、在抽象接口中增加100个方法,需要为每个方法都添加日志

2、有100个不同抽象接口的实现类,需要为每个类中的其中一个方法添加日志

第一种情况,使用静态代理的代理类:

public class Proxy implements Subject {
	// 真实主题对象的引用
	private Subject subject;
	
	public Proxy(Subject subject) {
		this.subject = subject;
	}
	
	@Override
	public void request0() {
		System.our.println("调用Subject的request0方法");
		subject.request0();
	}

	@Override
	public void request1() {
		System.our.println("调用Subject的request1方法");
		subject.request1();
	}

	// 为每个接口的方法提供日志
	// ...

}

第二种情况,要为每个实现类创建一个相对应的代理类,多了100个类。

静态代理适合:被代理的对象固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候。但是像上面讨论的两种情况,我们可以使用动态代理优雅解决要写很多方法或创建很多类的问题。

动态代理

JDK动态代理

图示

下面是JDK的动态代理:
动态代理

代码示例

首先,来了解一下java.lang.reflect包下的InvocationHandler接口和Proxy类的newProxyInstance方法。

InvocationHandler里面只有一个方法,就是invoke。

package java.lang.reflect;

public interface InvocationHandler {
	/**
	 * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     * 处理代理实例的方法调用,返回结果。当一个方法在其相关联的代理实例上调用,
     * 这个方法会在调用处理器上被调用。
     * (代理实例的方法——>InvocationHandler的方法——>真正对象的方法)
     */
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}

在代理类调用的方法,都会通过invoke方法,转发给真正对象执行。有图如下:

invocation-handler

图中Proxy是动态生成的代理类,InvocationHandler是实现了InvocationHandler接口的实现类们,target是真实对象Subject。

Proxy类中的newProxyInstance方法:

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

Proxy类中都是static方法,可以知道这个应该是一个工具类,newProxyInstance方法用来动态生成代理类。

三个参数:

  • `ClassLoader loader`:the class loader to define the proxy class定义代理类的类加载器
  • `Class[] interfaces`:the list of interfaces for the proxy class to implement代理类要实现的接口列表
  • `InvocationHandler h`:the invocation handler to dispatch method invocations to分发方法调用的调用处理器

相对于静态代理来说:

1、动态代理的接口类和真实对象类与静态代理是一样的,即Subject类和RealSubject类。

2、有一个实现InvocationHandler接口的SubjectInvocationHandler类,用来处理代理类上调用的方法,转发给真实对象。

3、通过Proxy.newProxyInstance()动态生产代理类

InvocationHandler实现类(SubjectInvocationHandler.java):

package com.mingmingcome.designpattern.proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/** 
 * @ClassName: Proxy
 * @Description: InvocationHandler实现类:作为动态生成代理类的分发方法调用的调用处理器
 * @author: luhaoming
 * @date: 2018年8月12日 下午4:26:45
 */
public class SubjectInvocationHandler implements InvocationHandler {
	
	// 这里真实对象不再是具体接口,而是Object(实现了任意接口的所有类)
	private Object object;
	
	public SubjectInvocationHandler(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 模仿日志的功能
		System.out.println("调用前");
		method.invoke(object, args);
		// 模仿日志的功能
		System.out.println("调用后");
		return null;
	}

}

测试类(TestDynamicProxy.java):

package com.mingmingcome.designpattern.proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/** 
 * @ClassName: TestDynamicProxy
 * @Description: TODO
 * @author: luhaoming
 * @date: 2018年8月12日 下午6:59:23
 */
public class TestDynamicProxy {

	public static void main(String[] args) {
		// the class loader to define the proxy class
		// 定义代理类的类加载器
		// 测试类中Subjec、RealSuject、TestDynamicProxy的类加载器都是一样的,不知道是否需要明确哪个类加载器
		ClassLoader loader = Subject.class.getClassLoader();

		// the list of interfaces for the proxy class to implement
		// 代理类要实现的接口列表
		Class<?>[] interfaces = RealSubject.class.getInterfaces();
		
		// the invocation handler to dispatch method invocations to
		// 分发方法调用的调用处理器
		InvocationHandler handler = new SubjectInvocationHandler(new RealSubject());
		
		// 生产代理类
		Subject proxy = (Subject)Proxy.newProxyInstance(loader, interfaces, handler);
		
		// 调用真实对象方法
		proxy.request();
	}

}

总结:

1、完美解决这两个静态代理的缺点:1)修改接口类(Subject)需要修改代理类;2)增加被代理类(真实对象)需要增加代理类。无论你要怎么修改接口类或者增加被代理类(真实对象),只要你提供你的接口类和真实对象,动态代理可以动态生成你所需要的代理类。

2、缺点:被代理类(真实对象)总是需要接口。

思考:有没有不需要接口的且又是动态代理的代理方法?CGLIB代理来了。

CGLIB动态代理

图示

CGLIB动态代理

代码示例

CGLIB基于ASM实现,提供比反射更为强大的动态特性,使用CGLIB可以非常方便地实现动态代理。

CGLIB代理相关的类:

  • net.sf.cglib.proxy.Enhancer 主要的增强类。
  • net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
  • net.sf.cglib.proxy.MethodProxy JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。

cglib是通过动态的生成一个子类去覆盖所要代理类的非final方法,并设置好callback,则原有类的每个方法调用就会转变成调用用户定义的拦截方法(interceptors)。

下面了解一下MethodInterceptor类和Enhancer类。

1、MethodInterceptor接口

这个类中和InvocationHandler接口类似,只有一个方法,就是intercept();

package net.sf.cglib.proxy;

public interface MethodInterceptor extends Callback
{
    /**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,
     * or by using the MethodProxy (faster).
     * 所有生成的代理方法调用这个方法代替原始方法。原始方法或者使用Method对象通过普通反射调用,
     * 或者使用MethodProxy调用(更快)。
     * @param obj "this", the enhanced object 增强的对象
     * @param method intercepted Method 被拦截的方法
     * @param args argument array; primitive types are wrapped 参数数组;原始类型会被装箱
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed 用来调用超类(不拦截方法);可能按需调用多次
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */    
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

intercept方法用来拦截调用真正对象的方法。如图:
method-interceptor

2、Enhancer类

代理用到的几个主要方法:

  • void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
  • void setCallback(Callback callback) 设置CallBack接口的实例,也就是实现了MethodInterceptor接口的回调函数,当拦截到方法时转发给真实对象
  • void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
  • void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
  • Object create() 使用默认无参数的构造函数创建目标对象。
  • Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。
3、代码

代码示例中真实对象还是使用RealSubject。

MethodInterceptor实现类SubjectMethodInterceptorImpl(SubjectMethodInterceptorImpl.java):

package com.mingmingcome.designpattern.proxy.cglibproxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/** 
 * @ClassName: SubjectMethodInterceptorImpl
 * @Description: 实现了MethodInterceptor的方法拦截器
 * @author: luhaoming
 * @date: 2018年8月13日 下午7:10:08
 */
public class SubjectMethodInterceptorImpl implements MethodInterceptor {

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("拦截前");
		// 在具体对象上调用原始类的方法
		Object ret = proxy.invokeSuper(obj, args);
		// Object ret1 = method.invoke(obj, args); // 用这种方式会发生死循环,因为方法会被拦截
		System.out.println("拦截后");
		return ret;
	}

}

实现了intercept方法。

update begin at 2018-12-19 08:45:26

关于method.invoke(obj, args)会发生死循环,如图:
死循环

为什么会死循环呢?首先,我们生成代理实例是继承我们调用setSuperclass()设置的超类的子类,重写了父类的方法。其次,当我们调用被代理对象的方法时,调用的是子类的方法即重写之后的方法,代码中就是Method对象。如果此时使用的method.invoke(obj, args),相当于调用了调用了它本身,类似代码:

public void request() {
	request();
	System.out.println("真实的请求");
}

这样就产生了死循环。

update end at 2018-12-19 09:10:11

动态生成代理实例的测试类(TestCglibProxy.java)

package com.mingmingcome.designpattern.proxy.cglibproxy;

import net.sf.cglib.proxy.Enhancer;

/** 
 * @ClassName: TestCglibProxy
 * @Description: 测试:动态生成代理实例,调用代理实例方法
 * @author: luhaoming
 * @date: 2018年8月13日 下午7:43:41
 */
public class TestCglibProxy {

	public static void main(String[] args) {
		// 动态代理增强器
		Enhancer enhancer = new Enhancer();
		// 设置超类,可以是没有实现任何接口的具体类,也可以是接口。
		// 如果是接口的话,setInterfaces会被调用。
		enhancer.setSuperclass(RealSubject.class);
		// 设置回调函数(MethodInterceptor接口是继承了Callback接口的)
		enhancer.setCallback(new SubjectMethodInterceptorImpl());
		// 动态生成的代理实例
		RealSubject proxy = (RealSubject)enhancer.create();
		// 调用代理实例的方法
		proxy.request();
		
	}

}

通过Enhancer的方法创建代理实例。

期间遇到的问题:

Exception in thread "main" java.lang.VerifyError: class net.sf.cglib.core.DebuggingClassWriter overrides final method visit.(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at net.sf.cglib.core.AbstractClassGenerator.<init>(AbstractClassGenerator.java:38)
	at net.sf.cglib.core.KeyFactory$Generator.<init>(KeyFactory.java:127)
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:112)
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108)
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104)
	at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
	at com.mingmingcome.designpattern.proxy.cglibproxy.TestCglibProxy.main(TestCglibProxy.java:15)

其实是cglib和ASM的版本不对应。我用的是cglib-2.2.2.jar,应该对应的是asm-3.3.1.jar,且没有用maven构建。

问题解决的方法:

java.lang.VerifyError: class net.sf.cglib.core.DebuggingClassWriter

使用 CGLib 报错 Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type

使用CGlib出现java.lang.NoClassDefFoundError: org/objectweb/asm/Type异常

总结:CGLIB代理比JDK动态代理更简单易用,真实对象连接口都不需要实现。

总结

本文介绍了代理模式的三种实现方式:静态代理、JDK动态代理、CGLIB动态代理。静态代理条条框框比较多,JDK动态代理需要实现接口,CGLIB代理连接口都不用实现。

参考:

CGLIB学习笔记

Java的三种代理模式

Java静态代理&动态代理笔记

2018年8月14日09:54:29

posted on 2018-08-14 14:44  mingmingcome  阅读(497)  评论(0编辑  收藏  举报

导航