目录
下文通过案例以及代码,从底层实现上剖析了动态代理的执行过程以及代理类的拦截作用、方法参数等等,文章较长,耐心看完一定会有收获;
什么是代理?
顾名思义,代替某个对象去处理一些问题,谓之代理,那么何为动态?即让JVM虚拟机去完成而非程序员去完成(与静态对比),连起来就是让虚拟机去动态的创建一个对象去代替另一个对象完成某些业务需求;
呢么其中就涉及到了两个对象,代理类和目标类;代理类又被前辈归纳成代理模式,下面看代理模式;
代理模式
代理模式是GoF23种设计模式之一。属于结构型设计模式。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
代理模式中的角色:
- 代理类(代理主题)
- 目标类(真实主题)
- 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
为什么要有个公共接口呢?
呢你代理类要代替目标类完成某些操作,你是不是就需要拥有目标类该有的功能,如何拥有这个类拥有的功能呢?一、代理类继承目标类(如果你自己编写继承类,耦合度太高,而且当需求量上去后,你是不是要写n个子类,不明智!!!)二、通过实现共同接口,实现必要方法,nice!!!,完美解决;
知道基本概念后,我们完善一下,用更加官方的话来表示。。。
动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
Java中常用的代理模式
- jdk动态代理;
- cglib动态代理;
- Javassist动态代理技术;
分析JDK代理编码逻辑
JDK动态代理(理解):
就是一个可以通过一个接口和一个实现类,能够给你动态的生成一个代理类,并且这个代理类还实现了该接口;
使用到的技术:
用到了java.lang.reflect.Proxy,以及invocationHandler类;
代码逻辑或者说使用过程:
- 创建目标类对象(用于得到类加载器和接口数组)
-
orderServiceImpl orderService = new orderServiceImpl();
- 调用Proxy类的newProxyInstance(classLoader,intefaces,invocationHandler)方法返回代理类对象;
-
orderService o = (orderService)Proxy.newProxyInstance( orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new myProxy(orderService));
- 使用代理类调用目标类方法(没错,代理类底层调用的还是目标类的方法,代理类只是实现了接口,并没有重写方法,所以调用的依旧是目标类的方法)
-
o.getOrder();
然后我们接下来展开说具体细节.......
| 下面我们通过一个实际业务需求来分析动态代理 |
假设某项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。你坑定不能直接在已经上线并且运行很好的项目的源代码上操作啊,于是乎我们采用静态代理方法,代码如下:
// 目标接口
public interface OrderService {
void add();
void update();
void delete();
}
// 目标类
public class OrderServiceImpl implements OrderService {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
}
// 代理类
public class OrderServiceProxy implements OrderService {
@Override
public void add() {
long begin = System.currentTimeMillis();
OrderService.add();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void update() {
long begin = System.currentTimeMillis();
OrderService.update();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void delete() {
long begin = System.currentTimeMillis();
OrderService.delete();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}
在上面的静态代理方法中,代理类和目标类都实现同一个接口,在代理类中维护目标类对象,并完成对目标类对象方法的增强,这种方式虽然遵循开闭原则,但是代理类和目标类至少是“一对一”的绑定关系,如果需要被代理的目标类个数越多,代理类就会越多,会产生大量重复的代码,也不利于后期的维护。
于是乎我们使用动态代理技术:目前代码是这样的,需要我们动态生成一个代理类:
// 目标接口
public interfaceOrderService {
void add();
void update();
void delete();
}
// 目标类
public class OrderServiceImpl implements OrderService {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
}
问题来了,如何动态生成代理类?
现在我们思考一个问题:我们目前有一个目标类、一个公共接口;我们如何通过二者返回一个代理类对象呢?
我们知道创建一个对象就先要有这个类才能new对象,那现在代理类都还不存在,该怎么去构造代理对象呢?
于是乎我们就想有没有什么技术?可以通过接口生成一个接口的实现类呢,呢当我们拿到实现类后如何产生对象呢?这就让我们想到了反射技术,我们可以通过反射技术来获取对象;(这里有个前提是在内存中生成,而并不是显式生成实现类,所以使用的是反射技术)
而我们目前讨论的JDK动态代理,的作用就是通过接口来生成代理类以及代理类的对象,那么JDK动态代理是怎么创建出代理类以及代理类对象的呢?
invocationHandler接口和Proxy类介绍
Jdk提供了invocationHandler接口和Proxy类,借助这两个工具可以达到我们想要的效果,接下来我们分开介绍:
invocationHandler(调用处理器):
InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
- 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
- 第二个参数:Method method。目标方法。
- 第三个参数:Object[] args。目标方法调用时要传的参数。
Proxy(代理类):
它里面有一个很重要的方法 newProxyInstance:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
调用Proxy的newProxyInstance方法可以生成代理对象,我们可以看到其中newProxyInstance()方法有三个参数,下面分析三个参数:
- 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
- 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
- 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
完整动态代理代码实现:
接下来我们看一段代码,我们是创建了一个proxyhandler类实现了invocationHandler类,并重写了invoke方法,为什么必须要这个方法呢?(这个问题我们最后说),在类中我们定义了一个targetObject属性,用于接受目标类,为什么需要目标类呢?因为我们需要通过目标类获取proxy类中newproxyInstance方法的两个重要参数,ClassLoader loader,Class<?>[] interfaces;
targetObject.getClass().getClassLoader():目标类的类加载器
targetObject.getClass().getInterfaces():目标类的实现接口,实现接口有时候也不止一个;
public class ProxyHandler implements InvocationHandler {
private Object targetObject;//被代理的对象
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
}
//该方法在代理对象调用方法时调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("记录日志");
return method.invoke(targetObject,args);
}
}
调用代码:
ProxyHandler proxyHandler=new ProxyHandler();
代理类=proxyHandler.newProxyInstance(目标类);
代理类调用方法...
Invoke方法
这个方法从头到尾都没有显式调用,呢它有没有起到作用呢?
起到了,我们在开头就说过,动态代理的作用是通过现有的目标类和公共接口,来动态生成一个代理类去代替目标类完成一些操作,或者在目标类的基础上扩展一些功能去完成目标类完成不了的任务,呢么这个扩展的功能我们该写在哪里,JVM虽然可以动态生成代理类,但他还不至于强大到连扩展功能都给你写进代理类中吧,所以扩展功能还的我们自己写,呢我们写到哪里呢?写到Invoke方法体中;
例如:在原有的业务执行前添加日志:
最重要的来了(代理类的作用)
我们在获取到代理类之后,这个代理类实际上只实现了接口,并没有实现其中的方法,我们通过代理类来调用的方法,实际上底层是调用的还是目标类的方法(上面这串代码中targetObject就是目标类对象),这样一来是不是就通了,就是我们通过代理类看似在调用代理类的方法实际上是调用的Invoke方法,造成这个的原因是,我们继承了一个叫invocationhandler的接口,也就是调用处理器,顾名思义就是调用方法前后添加一些处理,这个处理就是我们的扩展功能代码;
黄背景这段字这里我再次看的时候,产生了这样一个疑问?既然代理类底层还是调用的目标类方法,呢代理类的作用是什么?它方法也没重写就只实现了个接口,呢他的作用是什么?最后回答;
总结一下上面这句话,就是动态代理生成的代理类实际上只是起到了一个拦截作用,因为它实现了
InvocationHandler
调用处理器接口,并在invoke
方法中对目标方法进行拦截和处理。就是说通过代理类调用方法时,实际上调用的是InvocationHandler
的Invoke方法,然后在Invoke方法中调用目标方法,然后在Invoke方法的前后我们可以添加一些代码逻辑(也就是扩展功能),此时这个代理类就起到一个拦截方法调用的作用,就是拦住先别调用,让我先添加一些操作之后你再调用;
当代理类调用方法时,底层实际上调用的是 InvocationHandler
接口的 invoke
方法。下面通过一个简单的例子来说明这个过程:
假设有一个接口 MyInterface
:
public interface MyInterface { void someMethod(); }
以及一个实现了 InvocationHandler
接口的代理处理类 MyInvocationHandler
:
public class MyInvocationHandler implements InvocationHandler {
private final MyInterface targetObject;
public MyInvocationHandler(MyInterface targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理逻辑,可以在调用目标方法前后执行一些操作
System.out.println("Before invoking method: " + method.getName());
// 调用目标方法
Object result = method.invoke(targetObject, args);
// 在这里可以对目标方法的返回值进行处理
System.out.println("After invoking method: " + method.getName());
return result;
}
}
接着,我们创建一个实现了 MyInterface
接口的类 MyClass
:
public class MyClass implements MyInterface {
@Override
public void someMethod() {
System.out.println("Executing someMethod");
}
}
现在,我们使用动态代理创建代理对象并调用方法:
public static void main(String[] args) {
// 创建目标对象
MyInterface myClass = new MyClass();
// 创建代理处理器
MyInvocationHandler handler = new MyInvocationHandler(myClass);
// 创建代理对象
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[]{MyInterface.class},
handler
);
// 调用代理对象的方法
proxyInstance.someMethod();
}
在上述代码中,Proxy.newProxyInstance
方法创建了一个代理对象,并在代理对象的 someMethod
方法调用时,实际上会调用 MyInvocationHandler
中的 invoke
方法。这个方法中包含了代理逻辑(也就是扩展的功能代码)和对目标方法的调用。
运行这段代码,输出将是:
Before invoking method: someMethod
Executing someMethod
After invoking method: someMethod
在创建代理对象时,使用了 Proxy.newProxyInstance
方法,并指定了一个接口数组 new Class[]{MyInterface.class}
。这意味着代理对象 proxyInstance
实现了 MyInterface
接口。
又由于代理对象实现了接口,并且在创建代理对象时使用了 MyInvocationHandler
作为代理处理器,因此在代理对象在这段代码中,proxyInstance.someMethod();
调用的是代理对象 proxyInstance
的 someMethod
方法。实际上,这个调用是通过动态代理机制实现的,底层衔接到了 InvocationHandler
接口的 invoke
方法。
让我们一步步解释这个过程:
-
代理对象创建:
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class[]{MyInterface.class}, handler );
在这一步,通过
Proxy.newProxyInstance
方法创建了一个代理对象proxyInstance
。这个代理对象实现了MyInterface
接口,并且在其方法调用时将会调用MyInvocationHandler
中的invoke
方法。 -
代理对象方法调用:
proxyInstance.someMethod();
在这一步,调用了代理对象的
someMethod
方法。由于代理对象实现了MyInterface
接口,但实际上并没有提供具体的实现,因此这个调用会被转发到MyInvocationHandler
中的invoke
方法。 -
调用 MyInvocationHandler 的 invoke 方法:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理逻辑,可以在调用目标方法前后执行一些操作 System.out.println("Before invoking method: " + method.getName()); // 调用目标方法 Object result = method.invoke(targetObject, args); // 在这里可以对目标方法的返回值进行处理 System.out.println("After invoking method: " + method.getName()); return result; }
总体而言,动态代理通过生成一个实现了指定接口的代理类,并在代理对象的方法调用时调用 InvocationHandler
接口的 invoke
方法,实现了对方法调用的拦截和处理。这种机制使得我们可以在调用目标方法前后执行自定义的逻辑,从而实现横切关注点。
Object result = method.invoke(targetObject, args);
如果你看不懂上面这行代码就返回去看Invoke方法参数的介绍;
动态代理的应用
动态代理在代码界可是有非常重要的意义,我们开发用到的许多框架都使用到了这个概念。
Spring AOP。Spring 最重要的一个特性是 AOP(Aspect Oriented Programming 面向切面编程),利用 Spring AOP 可以快速地实现权限校验、安全校验等公用操作。而 Spring AOP 的原理则是通过动态代理实现的,默认情况下 Spring AOP 会采用 Java 动态代理实现,而当该类没有对应接口时才会使用 CGLib 动态代理实现。
回答中间黄色背景的产生的问题:
在JDK动态代理中,代理类的作用是在不改变目标类原有功能的情况下,对目标类的方法进行增强或者添加一些额外的逻辑。代理类通过实现目标类所实现的接口,并在代理类中持有目标类的引用,从而在代理类中可以调用目标类的方法。而不直接通过目标类调用的原因是,通过代理类调用目标类的方法可以在方法执行前后添加一些额外的逻辑,比如日志记录、性能统计等,从而实现对目标类方法的增强。此外,代理类还可以对目标类的方法进行过滤、拦截等操作,从而实现对目标类方法的控制。
到此为止JDK动态代理知识点记录完毕,各位看官请指正。