代理模式的定义:
- 给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问。
- 客户不直接操控原对象,而是通过代理对象间接地操控原对象。
代理模式UML图:
其中:
- RealSubject 委托对象,Proxy 是代理对象。
- Subject 是委托对象和代理对象都共同实现的接口。
- Request() 是委托对象和代理对象共同拥有的方法。
我们为什么要用代理模式呢?换句话说,代理模式的好处是什么?
- 好处一:我们可以隐藏委托类的实现;
- 好处二:可以使客户端与委托类实现解耦。即在不修改委托类的情况下实现额外的处理。
代理的实现分为:
- 静态代理:代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
- 动态代理:代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时通过Java的反射机制,动态生成类的字节码,并加载到JVM中。
静态代理
看懂了上面的UML图,其实静态代理不难实现。直接先给出代码:
- 公共接口:
public interface Subject{
void request();
}
- 委托类:
public class RealSubject implements Subject{
public void request(){
System.out.println("realSubject request.");
}
}
- 代理类:
public class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
public void request() {
System.out.println("BeforeProcess")
subject.request();
System.out.println("AfterProcess");
}
}
- 客户端:
public class Client{
public static void main(String args[]){
RealSubject realSubject = new RealSubject();
Proxy p = new Proxy(realSubject);
p.request();
}
}
- 输出:
BeforeProcess
realSubject request
AfterProcess
可以看出,静态代理通过聚合来实现,只要让代理类持有一个委托类的引用即可。
静态代理的特点:
- 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。当然了,这是所以代理模式的共有优点。
- 缺点:
- 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
(1)JDK动态代理:
Java实现JDK动态代理的大致步骤如下:
首先定义一个委托类和公共接口。
然后自定义一个调用处理器类(即实现 InvocationHandler 接口的类),这个类的目的是指定运行时将生成的代理类需要完成的具体任务(包括”before process”和”after process”),代理类调用任何方法都会经过这个调用处理器类。
最后生成代理对象(当然也会生成代理类),需要为他指定(1)委托对象、(2)实现的一系列接口、(3)调用处理器类的实例。因此可以看出一个代理对象对应一个委托对象,对应一个调用处理器实例。
先用一个简单的例子实现Java实现JDK动态代理的整个过程:
- 公共接口:
public interface Subject{
void request();
}
- 委托类:
public class RealSubject implements Subject{
public void request(){
System.out.println("realSubject request");
}
}
- 调用处理器类:
public class ProxyHandler implements InvocationHandler{
private Subject subject;
public ProxyHandler(Subject subject){
this.subject = subject;
}
@Override
public Object invoke(Object subject, Method method, Object[] args)
throws Throwable {
System.out.println("before process");
Object result = method.invoke(subject, args);
System.out.println("after process");
return result;
}
}
- 客户端:
public class Client{
public static void main(String[] args) {
//1.创建委托对象
RealSubject realSubject = new RealSubject();
//2.创建调用处理器对象
ProxyHandler handler = new ProxyHandler(realSubject);
//3.动态生成代理对象
Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(), handler);
//4.通过代理对象调用方法
proxySubject.request();
}
}
- 输出:
before process
realSubject request
after process
Jdk 的 java.lang.reflect 包下的 Proxy 类,正是构造代理类的入口。他内部的newProxyInstance 就是创建代理对象的方法,源码如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//如果h为空将抛出异常
Objects.requireNonNull(h);
//拷贝被代理类实现的一些接口,用于后面权限方面的一些检查
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//这里对某些安全权限进行检查,确保我们有权限对预期的被代理类进行代理
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//根据类加载器和接口创建代理类!
Class<?> cl = getProxyClass0(loader, intfs);
/* 使用指定的调用处理程序获取代理类的构造函数对象 */
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获得代理类的带参数的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 假如代理类的构造函数是非共有的,就使用反射来set accessible
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);
}
}
上述代码主要做了一下三件事:
- 1,根据类加载器和接口创建代理类;
- 2,获得代理类的带参数的构造函数;
- 3,根据代理类的构造函数来生成代理类的对象(调用处理器实例为参数传入),并返回。
InvocationHandler 接口中有方法:
invoke(Object proxy, Method method, Object[] args)
这个函数是在代理对象调用任何一个方法时都会调用的,方法不同会导致第二个参数method不同,第一个参数是代理对象(表示哪个代理对象调用了method方法),第二个参数是 Method 对象(表示哪个方法被调用了),第三个参数是指定调用方法的参数。
总结一下使用JDK动态生成的代理类的特点:
- 继承 Proxy 类,并实现了在Proxy.newProxyInstance()中提供的接口数组。
- 代理类是public final的。
- 命名方式为
$ProxyN
,其中N会慢慢增加,一开始是$Proxy1
,接下来是$Proxy2
。。。 - 有一个参数为 InvocationHandler 的构造函数。这个从 Proxy.newProxyInstance() 函数内部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出。
- 动态代理类相对于静态代理类,当代理类的实现是有很多共性的(重复代码),动态代理的好处在于避免了这些重复代码,只需要关注操作。
- Java 实现动态代理的缺点:因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),只能针对接口创建代理类,不能针对类创建代理类。
(2)CGLIB动态代理:
上文提到,JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理了。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
下面我们来实现简单的CGLIB动态代理。
首先,我们需要使用maven引入cglib的依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
<dependency>
下面开始实现cglib动态代理:
- 委托类:
public class RealSubject {
public void request() {
System.out.println("request.");
}
}
注意,该委托类没有实现接口,所以不能、或者说无法使用JDK动态代理。
- 实现MethodInterceptor接口的方法拦截器:
public class MyMethodInterceptor implements MethodInterceptor {
public Object intercept(Object objcet, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before...");
Object obj = methodProxy.invokeSuper(object, objects);
System.out.println("after...");
return obj;
}
}
- 客户端:
public class Client {
public static void main(String[] args) {
// 利用Enhancer类生成代理类
Enhancer enhancer = new Enhancer();
// 继承被代理类
enhancer.setSuperClass(RealSubject.class);
// 设置回调
enhancer.setCallBack(new MyMethodInterceptor());
// 生成代理对象
RealSubject real = (RealSubject)enhancer.create();
// 在调用代理类方法时,会被我们实现的拦截器拦截
real.request();
}
}
整个CGLIB动态代理的具体实现步骤大概如下:
- 1、通过Enhancer类生成代理类Class的二进制字节码,并通过Class.forName加载二进制字节码,生成Class对象;
- 2、通过反射机制获取实例构造,并初始化代理类对象。
- 3,代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。
- 4,在代理方法中判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行增强代理。
CGLIB动态代理的特点:
- 优点:可以在运行时对类或者是接口进行增强操作,委托类无需实现接口,这正好弥补了JDK动态代理的缺点。
- 缺点:不能对final类以及final方法进行代理。
最后,相信大家对什么时候用JDK动态代理,什么时候用CGLIB动态代理,以及怎么用这两种代理模式,都已经了然了。