背景
在前几篇文章中,我们介绍了AOP的使用方法,辣么AOP为何会如此神奇呢?通过查阅资料,我们了解到AOP的实现是通过代理去实现。
在分析问题之前我们应该有如下几点疑问:
1. 什么是代理。
2. AOP实现代理方式有几种,这几种方式各有什么优点。
3. 如何去实现AOP的代理方式。
介绍
- 动态代理 : 在程序运行期间由Java反射等机制动态生成,也就是在将class加载到jvm时期完成的工作。这种方式是不会修改字节码。
- 静态代理: 在程序运行前已经存在代理类的字节码的文件,也就是在编译时期已经完成代理工作。
Spring实现AOP的方式是通过动态代理。也就是在目标类的基础上增加切面逻辑,生成目标类的增强的代理类。
Spring AOP的动态代理方式有两种方式:
1. 基于JDK实现的JDK动态代理。
2. 基于ASM实现的CGLIB动态代理。
JDK动态代理
JDK动态代理通过反射来处理被代理的类。JDK动态代理的核心是InvocationHandler
接口和Proxy
类。
1. InvocationHandler
中仅一个方法public Object invoke(Object proxy, Method method, Object[] args)
。
这三个参数 分别是:代理实例、调用的方法、方法的参数列表。
2. Proxy
类是真正创建代理实例的类,其中主要是使用 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
来创建。这三个参数分别是:目标的ClassLoader对象、提供给目标类的接口对象、InvocationHandler 对象。
下面 我们举个例子来说明。:
接口:
package com.perf.Test.aop.jdk;
/** * @author cj34920 * Date: 2018/04/21. */
public interface IDayWork {
void breakfast();
void lunch();
void dinner();
}
实现类
package com.perf.Test.aop.jdk;
/** * @author cj34920 * Date: 2018/04/21 */
public class DayWorkImpl implements IDayWork {
@Override
public void breakfast() {
System.out.println("吃早饭");
}
@Override
public void lunch() {
System.out.println("吃午饭");
}
@Override
public void dinner() {
System.out.println("吃晚饭");
}
}
TimeHandler类
package com.perf.Test.aop.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/** * @author cj34920 * Date: 2018/04/21 */
public class TimeHandler implements InvocationHandler{
private Object target;
public TimeHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("吃饭之前要洗手");
//调用真正的方法
Object retVal = method.invoke(target, args);
System.out.println("吃饭之后要洗碗");
return retVal;
}
}
主函数:
public static void main(String[] args) {
DayWorkImpl dayWork = new DayWorkImpl();
TimeHandler timeHandler = new TimeHandler(dayWork);
IDayWork work = (IDayWork) Proxy.newProxyInstance(dayWork.getClass().getClassLoader(), dayWork.getClass().getInterfaces(), timeHandler);
work.breakfast();
work.lunch();
work.dinner();
System.out.println(work.getClass());
}
运行结果:
吃饭之前要洗手
吃早饭
吃饭之后要洗碗
吃饭之前要洗手
吃午饭
吃饭之后要洗碗
吃饭之前要洗手
吃晚饭
吃饭之后要洗碗
class com.sun.proxy.$Proxy0
- 这边我们需要注意的是
Proxy.newProxyInstance
返回的对象是代理类,但是这个代理类不是我们这边DayWorkImpl
这个类,所以这边需要注意不要写成 DayWorkImpl。 - 那么 为什么我们能够将这个代理类转化成 IDayWork ?其原因就在
newProxyInstance
的第二个参数上面,我们回想一下第二个参数是什么?是接口,是我们提供给代理对象的接口,那么我们这个代理对象就会去实现这个接口,这种情况下,我们当然可以将这个代理对象强制转化成提供的接口类型。 Proxy.newProxyInstance
创建的代理对象是在JVM运行时动态生成的一个对象,这个对象不是我们知道的任何一个对象,而是运行时动态生成的,并且命名方式都是以$Proxy
这种类型的。看运行结果也看出来 $Proxy0就是实际代理类。
总结
JDK动态代理的特点:JDK动态代理的方法都必须有接口的实现(newProxyInstance
第二个参数看出来)。