前言
日常生活中,我们想买房或者租房又没有房源的时候,我们通常会找中介,由中介负责帮我们联系房主,我们只需要最后签合同就行了,而不用去关心中介怎么联系房东的,怎么谈价格的。
什么是代理模式
代理模式(Proxy Pattern): 给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
所谓的代理模式,是指客户端不想或者不能直接访问或引用一个对象,这时候可以通过代理对象在客户端和目标对象之间起到一个中介作用。
代理(Proxy)模式提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
代理模式的三个角色
Subject(抽象角色):
声明代理角色和真实角色的公共接口,也是代理角色代理真实角色的方法。
RealSubject(真实角色):
定义代理对象需要代理的真实对象,真正实现业务逻辑的类;
Proxy(代理角色):
用来代理和封装真实主题,来控制对真实对象的访问,代理对象持有真实对象的应用,从而可以随时控制客户端对真实对象的访问。
代理模式UML图
静态代理
抽象主题角色
package com.java.design.proxy;
/**
* 抽象主题角色:明星
* @author liyongfu
*/
public interface Star {
/**
* 唱歌
*/
void sing();
}
真实主题角色
package com.java.design.proxy;
/**
* 真实主题角色:具体的明星
* @author liyongfu
*/
public class RealStar implements Star{
/**
* 唱歌
*/
@Override
public void sing() {
System.out.println("do----真正明星唱歌");
}
}
代理主题角色
package com.java.design.proxy.statics;
/**
*
* 代理主题角色
* 静态代理:
* 被代理类含有接口和具体实现类;
* 代理类实现被代理接口,并含有被代理接口属性、相应属性构造器
* @author liyongfu
*/
public class ProxyStar implements Star{
/**被代理的对象*/
private Star star;
public ProxyStar(Star star) {
this.star = star;
}
@Override
public void sing() {
doBeforeSing();
star.sing();
doAfterSing();
}
/**
* 明星唱歌前:经纪人卖票
*/
private void doBeforeSing() {
System.out.println("before---经纪人卖票");
}
/**
* 明星唱歌后:经纪人收钱
*/
private void doAfterSing() {
System.out.println("after---经纪人收钱");
}
public static void main(String[] args){
ProxyStar proxy= new ProxyStar(new RealStar());
proxy.sing();
}
}
控制台打印
before---经纪人卖票
do----真正明星唱歌
after---经纪人收钱
观察静态代理运行的整个流程,可以看出来:
- 代理对象和目标对象都实现了同样的接口。
- 可以在不修改目标对象的前提下增强额外的功能操作,即扩展目标对象的功能。
- 在明星唱歌之前,真实主题角色和代理主题角色就被确定下来了,也就是说在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类。
JDK动态代理
在上面的示例中,一个代理只能代理一种类型,而且是在编译期间就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。
由于静态代理的局限性,为了解决上面的问题,动态代理模式就横空出世了,我们最常见的就是Spring AOP了,看过源码的都知道spring aop有两种动态代理模式,JDK动态代理和CGLIB动态代理,那么这两者之间又有什么区别呢?
在Java中要想实现JDK动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。我们先来看一下他们的源代码。
package java.lang.reflect;
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
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.
*
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
*
* @return the value to return from the method invocation on the
* proxy instance. If the declared return type of the interface
* method is a primitive type, then the value returned by
* this method must be an instance of the corresponding primitive
* wrapper class; otherwise, it must be a type assignable to the
* declared return type. If the value returned by this method is
* {@code null} and the interface method's return type is
* primitive, then a {@code NullPointerException} will be
* thrown by the method invocation on the proxy instance. If the
* value returned by this method is otherwise not compatible with
* the interface method's declared return type as described above,
* a {@code ClassCastException} will be thrown by the method
* invocation on the proxy instance.
*
* @throws Throwable the exception to throw from the method
* invocation on the proxy instance. The exception's type must be
* assignable either to any of the exception types declared in the
* {@code throws} clause of the interface method or to the
* unchecked exception types {@code java.lang.RuntimeException}
* or {@code java.lang.Error}. If a checked exception is
* thrown by this method that is not assignable to any of the
* exception types declared in the {@code throws} clause of
* the interface method, then an
* {@link UndeclaredThrowableException} containing the
* exception that was thrown by this method will be thrown by the
* method invocation on the proxy instance.
*
* @see UndeclaredThrowableException
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
* {@code Proxy.getProxyClass} does.
*
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
* @throws SecurityException if a security manager, <em>s</em>, is present
* and any of the following conditions is met:
* <ul>
* <li> the given {@code loader} is {@code null} and
* the caller's class loader is not {@code null} and the
* invocation of {@link SecurityManager#checkPermission
* s.checkPermission} with
* {@code RuntimePermission("getClassLoader")} permission
* denies access;</li>
* <li> for each proxy interface, {@code intf},
* the caller's class loader is not the same as or an
* ancestor of the class loader for {@code intf} and
* invocation of {@link SecurityManager#checkPackageAccess
* s.checkPackageAccess()} denies access to {@code intf};</li>
* <li> any of the given proxy interfaces is non-public and the
* caller class is not in the same {@linkplain Package runtime package}
* as the non-public interface and the invocation of
* {@link SecurityManager#checkPermission s.checkPermission} with
* {@code ReflectPermission("newProxyInPackage.{package name}")}
* permission denies access.</li>
* </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
* {@code null}
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
抽象主题角色(同上,略)
真实主题角色(同上,略)
代理主题角色
package com.java.design.proxy.dynamic;
import com.java.design.proxy.RealStar;
import com.java.design.proxy.Star;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理
* @author liyongfu
*/
public class DynamicProxy implements InvocationHandler {
/**被代理的对象**/
private Star star;
public DynamicProxy(Star star) {
super();
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk动态代理");
doBeforeSing();
method.invoke(star, args);
doAfterSing();
return null;
}
/**
* 明星唱歌前:经纪人卖票
*/
private void doBeforeSing() {
System.out.println("before---经纪人卖票");
}
/**
* 明星唱歌后:经纪人收钱
*/
private void doAfterSing() {
System.out.println("after---经纪人收钱");
}
public static void main(String[] args) {
Star star = new RealStar();
Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, new DynamicProxy(star));
proxy.sing();
}
}
比较: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
源码实例
// TODO 待处理
项目实战
// TODO 待处理