前言:
Start:2020.11.1 End:2020.11.3 00:40
在学习mybatis源码和spring Aop源码时,JDK动态代理都是不能避过的!
内容:
代理与反射---->静态代理------>手写动态代理----->JDK动态代理+源码+总结
[根据目录选择你需要的内容查看]
动态代理-java反射机制的关系
-
动态代理是23种设计模式中的一种
-
常见的两种动态代理:jdk动态代理和cglib动态代理。区别在于其底层实现的方式不同
- jdk动态代理是由java内部的反射机制来实现的
- cglib动态代理底层则是借助asm来实现的,常用于框架中
- 总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。
- 还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
-
CGlib动态代理
cglib是第三方的工具类.创建代理对象
cglib的原理是继承.cglib通过继承目标类.创建它的子类,在子类中重写父类中同名的方法,实现功能的修改
因为cglib是继承,重写方法,所以要求目标类不能是final的.方法也不能是final的
cglib的要求比较宽松,只要能继承就可以了,cglib在很多框架中使用,比如mybatis和spring框架 对于无接口实现的类只能用cglib动态代理
代理模式的作用:在不改变由于目标类的目标方法原有代码情况下,对目标方法实现功能增强
代理类对象要完成的功能:
- 调用目标类的方法
- 功能增强,在目标方法被调用前后增加额外的功能
java反射机制
掌握掌握Class类的使用
-
获取Class实例
-
创建运行时类的对象
-
调用运行时类的指定结构
-
反射的应用:动态代理
public class TestReflect {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//clazz是Class类型的对象,这个对象封装了Person类的所有信息(属性,方法,构造器,参数,全类名等)
Class<Person> clazz = Person.class;
//通过clazz拿到Person类的构造器,创建一个Person类型的对象person
Constructor<Person> constructor = clazz.getConstructor(Integer.class, String.class);
Person person = constructor.newInstance(22, "xzq");
System.out.println(person);
//通过clazz直接创建一个对象,实际是利用了空参构造器
/**
* newInstance(){
* this.getConstructor().newInstance();
* }
*/
Person obj = clazz.newInstance();
System.out.println(obj);
//通过clazz拿到Person类的age属性,并给上面创建的person对象修改age属性
Field agefield = clazz.getField("age");
agefield.set(person,21);
System.out.println(person);
//通过clazz拿到Person类的show方法,并通过person对象调用show方法
Method showMethod = clazz.getMethod("publicshow");
showMethod.invoke(person);
//------------------------------------------------------------------------------------
// 在类的外部,不能访问类内部的私有属性和方法,也就不能通过person对象去访问属性name和方法privateshow()
// 但是通过反射可以
Field privatenamefield = clazz.getDeclaredField("name");
privatenamefield.setAccessible(true);
privatenamefield.set(person,"小明");
System.out.println(person);
Method privateshowMethod = clazz.getDeclaredMethod("privateshow");
privateshowMethod.setAccessible(true);
privateshowMethod.invoke(person);
}
}
静态代理
代码
想感受一下静态代理,再体会动态代理的魅力
package com.powernode.staticProxy;
public class StaticProxyTest {
public static void main(String[] args) {
//目标类对象
TargetClass targetObject = new TargetClass();
//静态代理对象
StaticProxy staticProxy = new StaticProxy(targetObject);
//调用代理对象的方法,实际是调用代理对象 代理的 目标对象的方法
staticProxy.targetMethod();
}
}
//3.静态代理类
class StaticProxy implements JustAInterface{
TargetClass targetObject;
public StaticProxy(TargetClass targetObject) {
this.targetObject = targetObject;
}
@Override
public void targetMethod() {
//可以再目标对象方法被调用前后对目标对象的方法进行增强
targetObject.targetMethod();
}
}
//2.目标类
class TargetClass implements JustAInterface{
//目标方法
@Override
public void targetMethod(){
System.out.println("调用了目标方法");
}
}
//1.接口
interface JustAInterface{
public void targetMethod();
}
静态代理的致命点
这也就是为何取名为静态代理的原因了.那么动态代理动态在哪里的原因我们就推出出来了
手写版动态代理
代码
package com.powernode.writebymyselfproxy;
//这是xzq手写版的动态代理,就静态代理的改进
//因为java反射机制中clazz.getMethod(methodName,paramterType)有如下局限:
// 1.如果我们通过反射查找的方法没有参数,不用指定参数类型
// 2.如果我们通过反射查找的方法有参数,[一定]要指定参数类型,否则抛出如下异常
// 在加之一个方法中只能同时存在一个可变长度参数,那么我们就无法在invoke()方法中同时出入实参和形参类型
// 因此当前手写版的动态代理只适用于目标方法是没有形参的!
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HandWriteProxyTest {
public static void main(String[] args) {
//目标类对象
TargetClass targetObject = new TargetClass();
//个人手写版代理对象
HandWriteProxy handWriteProxy = new HandWriteProxy(targetObject);
//调用代理对象的invoke(targetName)方法,实际是调用代理对象 代理的 目标对象的targetName方法
handWriteProxy.invoke("targetMethod1");
}
}
//代理类
class HandWriteProxy implements JustAInterface{
TargetClass targetObject;
public HandWriteProxy(TargetClass targetObject) {
this.targetObject = targetObject;
}
@Override
public void targetMethod1() {
targetObject.targetMethod1();
}
@Override
public void targetMethod2(String param) {
targetObject.targetMethod2(param);
}
//代理方法
public Object invoke(String methodName){
Object invokeResult = null;
try {
//利用(方法名)和(参数长度和类型)找到对应的目标方法
Method method = targetObject.getClass().getMethod(methodName);
System.out.println(methodName);
try {
invokeResult = method.invoke(targetObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return invokeResult;
}
}
//目标类
class TargetClass implements JustAInterface{
//目标方法
@Override
public void targetMethod1(){
System.out.println("调用了目标方法1");
}
@Override
public void targetMethod2(String param) {
System.out.println("调用了目标方法2:"+param.toString());
}
}
//接口
interface JustAInterface{
public void targetMethod1();
//这个方法在当前手写板的动态代理中是无法调用到的
public void targetMethod2(String param);
}
手写版动态代理致命点
因为java反射机制中clazz.getMethod(methodName,paramterType)有如下局限:
- 如果我们通过反射查找的方法没有参数,不用指定参数类型
- 如果我们通过反射查找的方法有参数,[一定]要指定参数类型,否则抛出如下异常 [源码如下]
- 在加之一个方法中只能同时存在一个可变长度参数,那么我们就无法在invoke()方法中同时出入实参和形参类型,那么我们就无法利用发射找出有形参的目标方法
- 因此当前手写版的动态代理只适用于目标方法是没有形参的!
至于此,可以理解为我们要写出一个比较完美的动态代理还是比较困难的,所以JAVA官方提供了一套动态代理实现机制----->对反射进行了封装,对外提供API,这就是JDK动态代理.
JDK动态代理
JDK动态代理没有以上两个版本代理的致命点,但是JDK动态代理相较于cglib动态代理有局限性
JDK动态代理使用前提:实现了接口的目标类才能使用JDK动态代理
JDK动态代理API
使用java.lang.reflect包中三个类和接口完成动态代理,以下反射工具类对JDK反射进行类封装
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler;
java.lang.reflect.Method;
InvocationHandler接口 目标类实现此InvocationHandler接口重写invoke()方法
Method:方法类,封装了某个类中某各个方法的信息,比如这个方法所属的类的全类名,这个方法的参数类型等
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
代码
接口
//1.接口
public interface Sell {
public Float sell(Float count);
}
目标类
//2.目标类
public class UsbFactory implements Sell {
//目标方法
@Override
public Float sell(Float count) {
System.out.println("目标类执行目标方法");
return count;
}
}
InvocationHandler实现类
//3.JDK动态代理帮我们生成代理类对象后调用的所有方法都会调用此方法invoke()
public class UsbInvocationHandler implements InvocationHandler {
Object targerObject;
// 动态代理:目标对象是活动的,不是固定的,需要传入进来
// 传入是谁,就给谁创建代理对象
public UsbInvocationHandler(Object targerObject) {
this.targerObject = targerObject;
}
@Override
public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable {
System.out.println("对目标方法的增强执行开始");
//执行目标方法
Object result = method.invoke(targerObject, args);
System.out.println("对目标方法的增强执行完毕");
return result;
}
}
动态代理对象(我们没有创建中介这个类,或者说代理这个类,利用JDK静态代理直接帮我们创建了代理对象)
package com.powernode.dynamicProxy;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
//目标类对象
Sell targetObject = new UsbFactory();
// 创建执行器
UsbInvocationHandler handler = new UsbInvocationHandler(targetObject);
// JDK动态代理帮我们生成代理对象
Sell o = (Sell) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
targetObject.getClass().getInterfaces(),
handler);
o.sell(11.0f);
}
}
运行结果
运行结果表明:
- JDK动态代理帮我们生成的代理对象调用sell方法时,调用了UsbInvocationHandler的invoke()方法
JDK动态代理源码
可问题是:
- 代理对象是怎么生成的?(我们没有写过代理类.java,也就没有代理类.class)
- UsbInvocationHandler的invoke方法是由谁来调用的?(其实知道了代理对象怎么生成的也就解决了此问题)
所有的问题都可以在Proxy.newProxyInstance(classLoader,Interfaces,invocationhandler)源码中解决
//动态代理的标配代码Proxy.newProxyInstance()
dynamicProxyObject = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
targetObject.getClass().getInterfaces(),
handler)
带着这两个问题我们去源码中解惑
/**
* loader:类加载器
* interfaces:目标对象实现的接口
* h:InvocationHandler的实现类
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
if (h == null) {
throw new NullPointerException();
}
//核心代码一: 先从缓存中查找代理类,没有就生成代理类字节码
Class cl = getProxyClass(loader, interfaces);
//核心代码二: 利用反射取出上面得到的代理类字节码clazz的构造器并创建代理对象
try {
// 2.1调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
Constructor cons = cl.getConstructor(constructorParams);
// 2.2生成代理类的实例并把UsbInvocationHandler的实例传给它的构造方法
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
//....省略异常处理代码
}
我们知道要在内存中生成一个对象,内存中肯定有这个类对应的字节码文件.
我们重来没有写过代理类,也就不可以编译出代理类字节码,也就不可能从磁盘中加载这个字节码到内存中作文Class类的一个对象.
没错:这个代理类字节码是程序在运行时在内存中帮我们生成的,源码如下
//核心代码一: 从缓存中查找代理类,没有就生成代理类
Class cl = getProxyClass(loader, interfaces);
--------------
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
//java动态代理是根据我们指定的接口来生成代理类的,但是我们指定的接口数不能超过65535个
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 声明一个Class类型的对于用于存储代理类字节码
Class proxyClass = null;
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
// 遍历代理类要实现的接口,并加载到内存中
for (int i = 0; i < interfaces.length; i++) {
// 拿到将要生成的代理类要实现的接口的名称
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
// 加载代理类要实现的接口到内存中
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
// 中间省略了一些异常处理和与我们研究主线不符的代码 .......
// 把代理类要实现的接口作为Class对象放到Set中
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
//========================以下是和缓存相关代码,可以跳过===============================================
// 把目标类实现的接口名称作为缓存(Map)中的key
Object key = Arrays.asList(interfaceNames);
Map cache;
synchronized (loaderToCache) {
// 从缓存中获取cache
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
// 如果获取不到,则新建地个HashMap实例
cache = new HashMap();
// 把HashMap实例和当前加载器放到缓存中
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
do {
// 根据接口的名称从缓存中获取对象
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果代理对象的Class实例已经存在,则直接返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
//========================以上是和缓存相关代码,可以跳过===============================================
try {
// 核心关键:这里就是动态生成代理类最关键的地方 :: 生成代理类字节码存放在内存中,这个代理类实现了所有的接口
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// 把代理类字节码作为一个Class对象clazz
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// 缓存机制
proxyClasses.put(proxyClass, null);
}
return proxyClass;
}
概述以上代码:
根据指定的接口生成唯一key尝试去缓存中找到代理类,没有就生成代理类,并把这个类clazz放入缓存中,下次就可以直接取
这就是官方和我们的区别,官方直接在内存中根据指定的接口生成了一个[代理类]
但是这个在内存中的代理类是什么样的?
进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// 这里动态生成代理类的字节码,由于比较复杂就不进去看了
final byte[] classFile = gen.generateClassFile();
// 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回代理类的字节码
return classFile;
}
通过给JVM传递一个系统参数让上面代码在生成代理类字节码是保存一份到硬盘上,反编译内容如下:
import dynamic.proxy.UserService;
import java.lang.reflect.*;
public final class $Proxy11 extends Proxy
implements UserService
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
// 在静态代码块中获取了4个方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
// 构造方法,参数就是刚才传过来的InvocationHandler接口实现类的实例
public $Proxy11(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
//这个方法是关键部分
public final void add()
{
try
{
// 实际上就是调用UsbInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
//=======非必要部分可以跳过==== JDK动态代理在帮我们实现我们指定的接口外,还贴心的实现了以下几个通用方法==========
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
}
简化上面代码
import dynamic.proxy.UserService;
import java.lang.reflect.*;
public final class $Proxy11 extends Proxy
implements UserService
{
private static Method m1;
// 在静态代码块中获取了UserService中的add方法
static
{
m1 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
}
// 构造方法,参数就是刚才传过来的InvocationHandler接口实现类的实例
public $Proxy11(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
//这个方法是关键部分
public final void add()
{
// 实际上就是调用UsbInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了
super.h.invoke(this, m3, null);
return;
}
}
至此,代理类字节码由JDK动态代理帮我们创建并保存在作为Class的一个对象保存在内存中了,利用反射拿到这个clazz的构造器,生成代理对象!代理类对象调用的每个方法底层都是调用目标类对象同名方法!
源码总结
JDK动态代理帮我们
- 在内存中根据指定的接口生成代理类字节码
- 创建代理类对象
JDK动态代理致命点
太难了,加油!