Java设计模式-代理模式

简介

在23中开发模式中,代理模式也是一种非常常见的开发模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。它可以在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能。

为了保持行为的一致性,一般情况下代理类和委托类会实现相同的接口,所以对于外部访问者而言几乎看不到差别,通过代理这一层能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

代理模式跟装饰器模式很相似,一般使用的时候装饰器模式是为了增强功能,但是代理模式是为了加以控制。

代理模式分为两种:静态代理和动态代理,常用动态代理又包含两种:JDK代理和cglib代理。

静态代理

静态代理就是说在运行之前代理类就已经编写完成,编译后存在字节码文件,也就是class文件,代理类和委托类的关系在运行前就确定了。

静态代理在实现过程中要求代理类和委托类需要有相同的父类或者接口,并且代理类中持有委托类的引用,然后调用相同的方法调用委托类的方法。也就是说代理类ProxySubject需要实现委托类RealSubject实现的Subject接口并且需要持有委托类Subject的引用,然后代理类ProxySubject通过调用所实现的接口方法request()调用委托类的request()方法。UML类图如下:

  • RealSubject 是委托类,ProxySubject 是代理类;
  • Subject 是委托类和代理类的接口;
  • request() 是委托类和代理类的共同方法;

示例代码如下:

public interface Subject {
	void request();
}

//委托类
public class RealSubject implements Subject {

	public void request() {
		System.out.println("request...");
	}

}

//代理类
public class ProxySubject implements Subject {
	
	private Subject subject;

	public ProxySubject(Subject subject) {
		this.subject = subject;
	}

	public void request() {
		System.out.println("request start");
		subject.request();
		System.out.println("request end");
	}
}

public static void main(String[] args) {
	Subject subject=new RealSubject();
	Subject proxySubject=new ProxySubject(subject);
	proxySubject.request();
}
//request start
//request...
//request end

静态代理的优缺点

优点:

  • 职责清晰:业务类只需要关注业务本身,保证了业务类的重用性。
  • 高扩展性:只要实现的接口方法名称不变,不管具体业务如何变化,代理类就可以不用做任何修改。

缺点:

  • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

静态代理虽然有优势,但是在开发过程总缺点也很突出,因为很多情况下需要在持续集成的过程中新增业务,就需要扩展接口中的方法,造成后期代码维护成本过高。因此鉴于代理模式在开发中使用的频率,JDK本身集成了有关代理模式的实现,在开发过程中我们只需要实现特定接口就可以很轻松的实现代理模式,而且代理对象不需要实现接口(静态代理中代理对象和委托对象都需要实现相同接口),代理对象的生成是JDK通过API直接在内存中生成的。

在讲解JDK动态代理之前先看一下几个相关类:

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
		
}

该接口在代理的实现过程中是作为一个入参传入的,在代理类Proxy中有一个生成代理对象的静态方法,接受三个入参:

public static Object newProxyInstance(ClassLoader loader,
									  Class[] interfaces,
									  InvocationHandler h);
  • ClassLoader loader:指定当前委托对象使用类加载器,获取加载器的方法是固定的;
  • Class[] interfaces:委托对象实现的接口的类型,使用泛型方式确认类型;
  • InvocationHandler h:事件处理,执行委托对象的方法时,会触发事件处理器的方法,会把当前执行委托对象的方法作为参数传入;

先自定义一个InvocationHandler,在InvocationHandler的invoke()方法中调用入参method的invoke()方法,这样就可以在方法调用的前后进行访问控制了。

public class LogHandler implements InvocationHandler {

	private Object target;

	public LogHandler(Object target) {
		this.target = target;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("request start");
		Object result = method.invoke(target, args);
		System.out.println("request end");
		return result;
	}

	public Object getProxyInstance() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

}
Subject subject=new RealSubject();
LogHandler handler=new LogHandler(subject);
Subject proxySubject=(Subject) handler.getProxyInstance();
proxySubject.request();

Proxy的newProxyInstance()方法就不深入探讨了,它最后会通过我们传入的参数在内存中生成一个代理类,一般情况下我们写的Java文件在编译完成后都会生成class字节码文件,但是动态代理并不会生成class文件,而是直接存入内存中,在内存中通过反射执行。

在InvocationHandler中invoke方法中我们可以看到有三个入参,后面两个一个是Method对象,一个就是Method的入参。通过copy静态代理的实现思路,我们可以自定义一个代理类,让代理类实现Subject接口,并且内部设置一个InvocationHandler的属性,然后在接口的实现方法中传入父接口中request()方法的Method对象。

public class ProxyCustom implements Subject {

	private InvocationHandler handler;

	public ProxyCustom(InvocationHandler handler) {
		this.handler = handler;
	}

	@Override
	public void request() {
		try {
			handler.invoke(this, Subject.class.getMethod("request", new Class[0]), null);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

}
Subject subject=new RealSubject();
LogHandler handler=new LogHandler(subject);
Subject proxySubject=new ProxyCustom(handler);
proxySubject.request();

这种逻辑跟静态代理的实现思路一样,静态代理只是在代理对象的方法中实行访问控制,而使用InvocationHandler中是通过方法回调实现的访问控制。事实上JDK在内存中生成的代理类中核心逻辑就是这种实现方式,只是在生成代理类的方式上面不同,但是所生成的代理类中的逻辑是一致的。

我们通过下面的方式可以查看一下动态代理生成的class字节码文件。

byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.MyProxy", subject.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.MyProxy.class");
out.write(classFile);
out.flush();

然后通过jd-gui反编译生成的字节码文件,内容如下:

public final class MyProxy extends Proxy implements Subject {
	private static Method m1;
	private static Method m2;
	private static Method m3;
	private static Method m0;
	
	static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m3 = Class.forName("com.yimi.demo.Subject").getMethod("request", new Class[0]);
			m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
			return;
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
		}
	}

	public MyProxy(InvocationHandler paramInvocationHandler) {
		super(paramInvocationHandler);
	}

	public final boolean equals(Object paramObject) {
		try {
			return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	public final String toString() {
		try {
			return (String) this.h.invoke(this, m2, null);
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	public final void request() {
		try {
			this.h.invoke(this, m3, null);
			return;
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	public final int hashCode() {
		try {
			return ((Integer) this.h.invoke(this, m0, null)).intValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

}

通过JDK提供的代理类Proxy和InvocationHandler回调接口实现的动态代理,虽然不会再让开发人员去实现每一个代理类,但是它要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理。

cglib动态代理

cglib(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。上面介绍了JDK生成的代理需要实现接口,如果不实现接口仍然需要设计代理类,这时候cglib就是我们的最佳选择了。cglib实现代理类的模式如下:

  • 查找A上的所有非final 的public类型的方法定义;
  • 将这些方法的定义转换成字节码;
  • 将组成的字节码转换成相应的代理的class对象;
  • 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)。
public class MySubject {
	
	public void request(){
		System.out.println("request...");
	}

}
public class MyInterceptor implements MethodInterceptor {
	
	private Object target;
	
	public MyInterceptor(Object target) {
		this.target = target;
	}

	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println(proxy.getClass());
		System.out.println("request start");
		Object result = method.invoke(target, args);
		System.out.println("request end");
		return result;
	}
	
	//给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

}
MySubject subject=new MySubject();
MySubject proxySubject=(MySubject) new MyInterceptor(subject).getProxyInstance();
proxySubject.request();

参考资料

设计模式(十一)代理模式Proxy(结构型)

java静态代理和动态代理

Java的三种代理模式

设计模式-代理模式

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

评论

您确定要删除吗?删除之后不可恢复