本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
什么是代理模式
graph LR
访问者 --> 代理类--> 被代理类
代理模式是一种行为型模式,可以理解为创建一个代理类,将被代理类进行包装增强,使得可以不改变原来被代理类的情况下额外对其进行扩展。例:我(被代理类)喜欢一个女生,但是我不自信,没有勇气表白,所以我找了一个舍友(代理类),这个舍友很自信,让他代替我进行我向女生表白这个行为,最终都是执行了我向女生表白这个行为,但是舍友对我进行了增强。
代理模式的好处
正如上面所说,代理模式可以在不改变被代理类的情况下进行增强,可以使代码的业务解耦,例如Spring中的拦截器,我们可以使用代理模式在不改变原来业务代码的情况下,在其前后加入日志或校验操作,实现业务和基础操作的分离,使代码更加优雅。
Java中代理对象的创建方式
创建代理类的方式有两种,一种是静态代理和动态代理,其中Java中动态代理比较常用的有两种:基于接口的JDK动态代理和基于子类的Cglib动态代理,当然还有其他创建方式,但在日常开发中接触的比较少,此文不再展开。
静态代理
代码演示
静态代理是最简单的代理方式,首先创建如下结构:
Person.java:
// Person接口,有一个eat方法
interface Person {
void eat();
}
Developer.java:
// Developer实现类,也就是被代理类,实现Person接口和eat方法
public class Developer implements Person {
@Override
public void eat() {
System.out.println("吃饭了");
}
}
PersonProxy.java:
// 代理类,实现对Developer的代理
public class PersonProxy implements Person {
private Person person;
public PersonProxy(Person person) {
this.person = person;
}
@Override
public void eat() {
System.out.println("吃饭前要洗手");
person.eat();
}
}
启动测试类:ProxyTest.java:
public class ProxyTest {
public static void main(String[] args) {
Developer developer = new Developer();
PersonProxy personProxy = new PersonProxy(developer);
personProxy.eat();
}
}
输出如下:
我们看到了PersonProxy对Developer进行了增强,在eat()之前洗手。
缺点
缺点其实很明显,就是针对每一种需要被代理的类都需要创建代理类,如果都使用静态代理,将 会出现大量的代理类,这对于今后的维护是灾难性的。于是我们就想到用反射技术,将传入的需要被代理的类进行处理,自动生成代理类,这样就避免了上面的问题,这也就是动态代理。
动态代理
动态代理的本质其实就是在程序的执行过程中,使用反射机制创建代理对象。动态代理在Java中常见的技术分为JDK动态代理和Cglib动态代理,还有其他代理技术如javassist动态代理、ByteBuddy动态代理等。
JDK动态代理
JDK从1.3版本开始支持JDK动态代理。主要核心类为:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
代码演示
先看一下如何使用JDK动态代理: Developer.java和Person.java代码保持不变,我们主要修改PersonProxy.java,为了区分,我们新建JDK动态代理类JdkProxyHandler.java
public class JdkProxyHandler implements InvocationHandler {
// 不再静态声明被代理类,用Object接收所有被代理类
private Object proxyObject;
public JdkProxyHandler(Object proxyObject) {
this.proxyObject = proxyObject;
}
/**
* @param proxy JDK生成的代理对象
* @param method 被代理类中的方法
* @param args 被代理类中的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名,这里是指被调用的方法
String methodName = method.getName();
if (methodName.equals("eat")) {
System.out.println("吃饭前要洗手");
}
return method.invoke(proxyObject, args);
}
}
修改ProxyTest.java
public class ProxyTest {
public static void main(String[] args) {
JdkProxyHandler proxy = new JdkProxyHandler(new Developer());
Person developer = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
developer.eat();
}
}
运行结果如下:
我们可以看到同样实现了对Developer的增强。
代码解析
首先是声明代理类处理器:实现java.lang.reflect.InvocationHandler的invoke()方法,这样可以使用反射获取调用方法的信息,以后不管被代理类里有什么方法,我们都可以动态的获取。
然后就是创建代理对象:
Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
newProxyInstance传入的参数依次为:ClassLoader(代理类的类加载器),Class[](代理类要实现的接口列表),InvocationHandler(将方法调用分派到的处理程序)。 这三者的用处也很明显,ClassLoader加载代理类,Class[]用于实现接口对应的方法,InvocationHandler用于指定进行怎样的处理。
InvocationHandler和Proxy的源码不是很复杂,大体来说就是使用反射创建指定的类,强烈建议大体看一眼,可以学到很多。
缺点
JDK动态代理的缺点主要有两点,一个是需要被代理类实现接口,因为代理类是基于被代理类的接口生成的,所以要求被代理类必须实现接口。第二个是效率问题,不管怎么说JDK动态代理都是基于反射生成代理类的,一般情况下如果生成的代理类数量不是很多,对效率影响不是很高,但是如果生成的代理类数量很多,比如Spring项目启动时会创建大量的代理类,如果使用JDK动态代理则会导致启动速度变慢,占用系统资源等问题,这也是为什么Spring后来改成Cglib动态代理的原因。
Cglib动态代理
Cglib底层使用ASM框架,ASM框架可以直接操作字节码文件,绕过了反射这一层,游离于Java之外,所以提供了比JDK更为强大的动态代理,比如Cglib是通过生成被代理类的子类来实现代理的,不要求被代理类实现接口,比JDK动态代理更加灵活,速度也比JDK动态代理更快。
Cglib动态代理的核心类也有两个:org.springframework.cglib.proxy.Enhancer和 org.springframework.cglib.proxy.MethodInterceptor,这里以spring-core包中的为例,其他包中的使用方法也类似,底层都是ASM框架,只不过自己封装了一层而已。
代码演示
同样保持Person.java和Developer.java不动,我们创建CglibProxyHandler.java来定义生成代理类处理器
public class CglibProxyHandler implements MethodInterceptor {
// 原被代理对象
private Object proxyObject;
// Cglib创建代理类需要的enhancer对象
private Enhancer enhancer = new Enhancer();
public CglibProxyHandler(Object proxyObject) {
this.proxyObject = proxyObject;
}
/** 根据被代理类,创建enhancer被代理对象,用于之后的代理类创建 */
public Object getProxyObject() {
enhancer.setSuperclass(proxyObject.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
* @param obj 生成的代理对象
* @param method 被代理类中的方法
* @param args 被代理类中的参数
* @param proxy 父类的方法,也就是代理类覆盖了被代理类的那个方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 获取方法名,这里是指被调用的方法
String methodName = method.getName();
if (methodName.equals("eat")) {
System.out.println("吃饭前要洗手");
}
return method.invoke(proxyObject, args);
}
}
修改ProxyTest.java:
public class ProxyTest {
public static void main(String[] args) {
CglibProxyHandler proxyHandler = new CglibProxyHandler(new Developer());
Person developer = (Person) proxyHandler.getProxyObject();
developer.eat();
}
}
运行输出结果:
毫无疑问也实现了动态代理功能,并且可以看到【代理类 extends 被代理类】
代码解析
Cglib的MethodInterceptor也和JDK动态代理一样,用来获取调用方法的信息,同时因为Cglib是通过继承来实现的,也能获取继承的父类的方法信息。
Enhancer.create(),根据指定的被代理类,创建代理类,源码相对于jdk更加复杂,总而言之是通过继承来实现的,有兴趣的可以研究。
两者对比
| JDK动态代理 | Cglib动态代理 | 原因 | |
|---|---|---|---|
| 速度 | 相对慢 | 快 | JDK使用反射,Cglib直接操作字节码 |
| 代理类 | implements被代理类的接口 | extends被代理类 | 反射操作继承复杂,字节码操作较为简单 |
| 被代理类 | 需要实现接口 | 更加灵活,不需要实现接口 | 代理类的生成方式决定 |
| 应用场景 | 不依赖外部框架,简单生成代理对象时使用 | 需要生成大量代理对象时使用,如Spring初始化 |