文章目录
1 代理模式
代理模式是Java的常用设计模式。它的要素有如下要求:
- 公共接口:代理类和委托类(被代理类)要有相同的接口;
- 代理类增强服务:代理类主要负责为委托类(被代理类)预处理消息、过滤消息、把消息转发给委托类(被代理类),以及事后处理消息等;
- 两个对象关联:代理类与委托类(被代理类)之间通常存在关联关系,一个代理类的对象与一个委托类(被代理类)的对象相关联,代理类的对象本身并不真正实现服务,而是通过调用委托类(被代理类)的对象的相关方法,来提供特定的服务。
简单来说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
可以举个不恰当的生活中例子,加以理解。原告张三因为家暴想向法院申请离婚,为了提高胜率,原告张三找了个代理律师全权负责,律师在原告张三陈述的事实基础上,还添加了法律条文及离婚后财产分配等内容。
针对上面的例子,其中:
- 委托类(被代理类):原告张三
- 代理类:张三的律师
- 他俩的公共接口:可以认为是想打赢这个官司
- 委托类的方法:离婚
- 代理类增强的服务:法律条文及离婚后财产分配等内容
2 静态代理
2.1 静态代理介绍
静态代理:有程序员创建或特定工具自动生成源码,也就是在编译时就已经将接口、被代理类、代理类等确定下来了。在程序运行之前,代理类的.class文件就已经生成。
2.2 静态代理简单实现
写一个简单的静态代理的例子:假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长代理学生上交班费,班长就是学生的代理。
- 创建公共接口具体行为
首先,我们创建一个Person公共接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/**
* 创建Person公共接口
*/
public interface Person {
//上交班费
void giveMoney();
}
- 被代理类对象实现接口,完成具体的业务逻辑
被代理类——Student类实现Person接口。Student可以具体实施上交班费的动作:
/**
* 被代理类实现公共接口
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
- 代理类实现公共接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
StudentsProxy类,这个类也实现了Person公共接口,同时还持有一个学生类对象。由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/**
* 代理类实现Person公共接口,同时保存一个被代理类的对象——学生,这样就可以通过代理类产生学生行为
*/
public class StudentsProxy implements Person{
//被代理类的对象——学生
Student stu;
// 通过构造器传入具体的代理类对象
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
// 可以附加多种用途
System.out.println("张三最近学习有进步!");
stu.giveMoney();
}
}
- 客户端使用操作与分析
public class StaticProxyTest {
public static void main(String[] args) {
//被代理类(学生张三),他的班费上交由代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将被代理对象(张三)传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}
代理模式最重要的要素:
- 一个公共接口(Person)
- 一个实现了公共接口的被代理类(Student)
- 一个实现了公共接口的代理类(StudentProxy)
- 代理类持有的被代理类的对象,代为执行被代理类的实例方法。
只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。
最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。其实也是切面思想的主要思路。
3 动态代理
3.1 动态代理介绍
代理类在程序运行时创建的代理方式被称为动态代理。
上面的静态代理例子中,代理类(StudentProxy)是自己定义好的,在程序运行之前就已经编译完成的。
然而对于动态代理来说,代理类并不是Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成。相比于静态代理,动态代理的优势在于可以很方便的对代理类的所有方法进行统一的处理,而不用修改代理类中的每个方法。
3.2 JDK动态代理实现
在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
- 创建公共接口的具体行为
跟上面静态代理一样:首先,我们创建一个Person公共接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/**
* 创建Person公共接口
*/
public interface Person {
//上交班费
void giveMoney();
}
- 被代理类对象实现接口,完成具体的业务逻辑
跟上面静态代理一样:被代理类——Student类实现Person接口。Student可以具体实施上交班费的动作:
/**
* 被代理类实现公共接口
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
- 代理类实现InvocationHandler接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
这里是跟静态代理不一样的地方,实现的是InvocationHandler接口,而不是公共接口。
创建StudentInvocationHandler类,实现InvocationHandler接口,在这个类中有一个被代理对象obj。
InvocationHandler接口中有一个invoke方法,所有执行被代理对象的方法都会被替换成执行invoke方法。在invoke方法中执行被代理对象obj的相应方法。
在代理过程中,我们真正执行被代理对象的方法前可以加入自己其他的处理,来增强被代理对象的方法。这也是Spring中的AOP的实现主要原理,这里还涉及到一个很重要的关于Java反射方面的基础知识。
public class StudentInvocationHandler implements InvocationHandler {
// invocationHandler持有的被代理对象
private Object obj;
// 通过构造器传入具体的被代理对象
public StudentInvocationHandler(Object obj) {
this.obj = obj;
}
/**
* @param proxy 代表动态代理对象
* @param method 代表被代理对象的方法
* @param args 代表被代理对象的方法传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被代理对象的方法执行之前的操作
System.out.println("执行之前的操作....\t 被代理对象的方法是:"+ method.getName() + "\t 方法传入的参数是:" + Arrays.toString(args));
// 被代理对象的方法执行
// method.invoke() 表示执行被代理对象的方法,其中第一个参数是被代理对象,第二个参数是传入方法中的实参
Object result = method.invoke(obj, args);
// 被代理对象的方法执行之前的操作
System.out.println("执行之后的操作....\t 被代理对象的方法执行后的结果是:" + result);
return result;
}
}
- 客户端使用操作与分析
/**
* JDK动态代理
* 1、需要有一个接口,即接口的实现类
* 2、创建接口实现类的代理对象
*
* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 1、loader: 用哪个类加载器去加载代理对象
* 2、interfaces:动态代理类需要实现的接口
* 3、h:动态代理方法在执行时,会调用h里面的invoke方法去执行
*/
public class JDKProxyTest {
public static void main(String[] args) {
// 创建被代理对象
Person zhangsan = new Student("张三");
// 创建一个与被代理对象相关联的InvocationHandler
StudentInvocationHandler studentInvocationHandler = new StudentInvocationHandler(zhangsan);
// 利用Proxy类创建代理对象studentProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
ClassLoader loader = JDKProxyTest.class.getClassLoader();
Class<?>[] interfaces = {Person.class};
InvocationHandler h = studentInvocationHandler;
Person studentProxy = (Person) Proxy.newProxyInstance(loader, interfaces, h);
// 代理执行上交班费的方法
studentProxy.giveMoney();
}
}
运行结果:

我们前面说到,动态代理的优势在于可以很方便的对代理类的所有方法进行统一的处理,而不用修改代理类中每个的方法。是因为被代理类的所有方法,都是通过在InvocationHandler中的invoke方法被调用的,所以我们只要在invoke方法中统一处理,就可以对代理类的素有方法进行相同的操作了。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
JDK动态代理运行错误
-
java反射错误:object is not an instance of declaring class
解决方法:一般是接口类型不对,检查下
Class<?>[] interfaces = {UserDaoImpl.class};
-
被代理类没有实现接口:com.jie.aop.UserDaoImpl is not an interface
如下,被代理类没有实现接口:
如下,JDK动态代理代码:
报错:
修改为:
3.3 JDK动态代理源码分析
/**
* 源码注释:Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation handler.
* 注释翻译:返回指定接口的代理类的实例,该接口将方法调用分派到指定的调用处理程序
*/
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);
}
}
最重要的是下面这四个位置的语句:
1.final Class<?>[] intfs = interfaces.clone();
2.Class<?> cl = getProxyClass0(loader, intfs);
3.final Constructor<?> cons = cl.getConstructor(constructorParams);
4.return cons.newInstance(new Object[]{h});
其中最应该关注的是:Class<?> cl = getProxyClass0(loader, intfs);
,因为在这里产生了代理类。后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件是缓存在java虚拟机中的。
接下来我们对class文件进行反编译,看看JDK为我们生成了什么内容:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
* 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,
* 看到这,是不是就有点明白,为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
* 被代理对象的实例。
*
* super(paramInvocationHandler),是调用父类Proxy的构造方法。
* 父类持有:protected InvocationHandler h;
* Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//这个静态块本来是在最后的,我把它拿到前面来,方便描述
static
{
try
{
//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
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("proxy.Person").getMethod("giveMoney", 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());
}
}
/**
* 这里调用被代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
* this.h.invoke(this, m3, null); 这里简单明了。
* 代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理对象,再联系到InvacationHandler中的invoke方法。嗯,就是这样。
*/
public final void giveMoney()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
}
由上面的反编译文件,可以看出,JDK为我们生成了一个叫做$Proxy0(这个名字后面的0是编号,有多个代理类会依次递增)的代理类,这个代理类文件是放在内存中的,我们在创建代理对象时,就是通过反射获得这个代理类的构造方法,然后创建的代理对象。
我们可以将InvocationHandler看成是一个中介类,该中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。然后通过聚合的方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
总结:
-
生成的代理类
$Proxy0
继承了Proxy
类,并且实现了公共接口Person
- 代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
public final class $Proxy0 extends Proxy implements Person{}
-
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
3.4 InvocationHandler接口和Proxy类详解
InvocationHandler接口是代理对象的调用处理程序实现的一个接口,每一个代理对象都有一个关联的调用程序。在代理对象调用方法时,方法调用被编码分派到调用程序的invoke方法。
当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler
接口类的invoke
方法来调用,看如下invoke
方法:
/**
* proxy:代理对象:com.sun.proxy.$Proxy0
* method:被代理对象的方法的Method对象
* args:代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Proxy
类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance
方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:
loader
:一个ClassLoader
对象,定义了由哪个ClassLoader
对象对生成的代理类进行加载。interfaces
:一个interface
对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类要实现哪些接口,代理类就可以调用接口声明的所有方法。h
:一个InvocationHandler
对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler
对象上,并最终由其调用。
3.5 JDK动态代理和CGLIB动态代理代码示例比较
- 定义创建用户管理接口
/**
* 创建公共接口
*/
public interface UserDao {
public int add(int a, int b);
public String update(String id);
}
- 用户管理实现类,实现用户管理接口(被代理的实现类)
① JDK动态代理写法:被代理类实现公共接口
/**
* JDK动态代理写法:被代理类实现公共接口,完成具体的业务逻辑
*/
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public String update(String id) {
return id;
}
}
② CGLIB动态代理写法:被代理类
/**
* CGLIB动态代理写法:被代理类,完成具体的业务逻辑
*/
public class UserDaoImpl{
public int add(int a, int b) {
return a + b;
}
public String update(String id) {
return id;
}
}
- JDK动态代理实现
① 创建与动态代理对象相关联的InvocationHandler
/**
* 创建UserDaoInvocationHandler,实现InvocationHandler接口
* 作用:动态代理对象相关联的InvocationHandler
* 1、持有一个被代理对象
* 2、通过有参构造器的方式传递被代理对象
* 3、使用invoke()方法来增强代理对象的方法
*/
public class UserDaoInvocationHandler implements InvocationHandler {
// invocationHandler持有的被代理对象
private Object obj;
// 通过有参构造器的方式传递被代理对象
public UserDaoInvocationHandler(Object obj) {
this.obj = obj;
}
/**
* 增强的逻辑
* @param proxy 动态代理对象
* @param method 被代理对象中的方法
* @param args 被代理对象中的方法传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被代理对象的方法执行之前的操作
System.out.println("方法之前执行...\t 传入的方法是:" + method.getName() + ",该方法传入的参数为" + Arrays.toString(args));
// 被代理对象中的方法执行
Object res = method.invoke(obj, args);
// 被代理对象的方法执行之前的操作
System.out.println("方法之后执行...\t 代理的对象是:" + obj);
return res;
}
}
② 创建动态代理对象
/**
* JDK动态代理
* JDK代理的接口
*
* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 1、loader: 用哪个类加载器去加载代理对象
* 2、interfaces:动态代理类需要实现的接口
* 3、h:动态代理方法在执行时,会调用h里面的invoke方法去执行
*/
public class JDKProxy {
public static void main(String[] args) {
// 1、创建被代理对象
UserDao userDaoImpl = new UserDaoImpl();
// 2、创建一个与被代理对象相关联的InvocationHandler
UserDaoInvocationHandler userDaoInvocationHandler = new UserDaoInvocationHandler(userDaoImpl);
// 3、利用Proxy类创建代理对象userDaoProxy来代理userDaoImpl,
// 代理对象的每个执行方法都会替换执行Invocation中的invoke方法
ClassLoader loader = JDKProxy.class.getClassLoader();
Class<?>[] interfaces = {UserDao.class};
InvocationHandler h = userDaoInvocationHandler;
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(loader, interfaces, h);
// 4、代理对象执行被代理对象的方法
int result = userDaoProxy.add(2, 3);
System.out.println("最终运行结果:" + result);;
}
}
运行结果:

- CGLIB代理
① 需要先导入 asm-3.3.1.jar 和 cglib-2.2.2.jar 两个包
② 创建与动态代理对象相关联的MethodInterceptor
/**
* 创建UserDaoMethodInterceptor,实现MethodInterceptor接口
* 作用:动态代理对象相关联的MethodInterceptor
* 1、持有一个被代理对象
* 2、通过有参构造器的方式传递被代理对象
* 3、使用invoke()方法来增强代理对象的方法
*/
public class UserDaoMethodInterceptor implements MethodInterceptor {
// 被代理对象
private Object obj;
// 通过有参构造器的方式传递被代理对象
public UserDaoMethodInterceptor(Object obj) {
this.obj = obj;
}
/**
*
* @param proxyInstance 代理子类实例
* @param method 被代理对象的方法
* @param args 被代理对象的方法传入的参数
* @param methodProxy 用来调用代理子类中的基类方法副本,或委托基类中的原方法。
*/
@Override
public Object intercept(Object proxyInstance, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 被代理对象的方法执行之前的操作
System.out.println("方法之前执行...\t 传入的方法是:" + method.getName() + ",该方法传入的参数为" + Arrays.toString(args));
System.out.println("方法之前执行...\t 代理子类的类名是:" + proxyInstance.getClass().getCanonicalName());
// 被代理对象中的方法执行
// 第一种方法:使用 method.invoke(obj, args)
Object result = method.invoke(obj, args);
// 第二种方法:使用 methodProxy.invokeSuper(proxyInstance, args)
// Object result = methodProxy.invokeSuper(proxyInstance, args);
// 被代理对象的方法执行之前的操作
System.out.println("方法之后执行...\t 代理的对象是:" + obj);
return result;
}
}
③ 创建动态代理对象
/**
* CGLIB动态代理
* 代理的是类
*/
public class CGLIBProxy {
public static void main(String[] args) {
// 1、创建被代理对象
UserDaoImpl userDaoImpl = new UserDaoImpl();
// 2、创建动态代理对象相关联的MethodInterceptor
UserDaoMethodInterceptor userDaoMethodInterceptor = new UserDaoMethodInterceptor(userDaoImpl);
// 3、创建动态代理对象
Enhancer enhancer = new Enhancer();
//设置父类(即被代理类),因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(userDaoImpl.getClass());
//设置回调
enhancer.setCallback(userDaoMethodInterceptor);
//创建并返回代理对象
UserDaoImpl userDaoProxy = (UserDaoImpl) enhancer.create();
// 4、利用动态代理对象调用原方法
int result = userDaoProxy.add(3, 3);
System.out.println("最终运行结果:" + result);
}
}
运行结果:

3.6 JDK和CGLIB动态代理总结
-
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
-
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final
-
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现
InvocationHandler
+ 使用Proxy.newProxyInstance
产生代理对象 + 被代理的对象必须要实现接口 -
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承。
当针对接口编程的环境下推荐使用JDK的代理。
参考资料: