Java基础一篇过(十)Java静/动态代理
一、前言
关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。
了解代理模式
定义:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。
作用:代理模式最主要的就是有一个公共接口或者父类,一个具体的类,一个代理类,代理类持有具体类的实例,代为执行具体类实例方法。代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
常见实现:Spring的AOP实现原理就是用到了代理模式。
被代理类
一般指我们的业务类,实现具体业务方法的类。
代理类
一般指对业务类进行“增强”的类,实现一些不影响主业务的增强方法,如添加日志等功能。
结构图
二、静态代理
定义:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
场景分析
场景:假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
代码思路:首先,我们创建一个IStudent接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
代码实现
使用代理类的好处
三、动态代理
定义:代理类在程序运行时才被创建的代理方式被成为动态代理。
优势:可以很方便的对代理类的方法进行统一的处理,而不用修改每个代理类中的方法。
代码实现
四、动态代理源码解析
1 /** 2 * Proxy生成动态代理类【源码解析】 3 * 4 * @param loader 类加载器 5 * @param interfaces 需要生成动态代理器的接口集合 6 * @param h 自定义的动态代理器对象 7 * @return 8 * @throws IllegalArgumentException 9 * @author 有梦想的肥宅 10 */ 11 @CallerSensitive 12 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { 13 //1、校验“自定义的动态代理器对象”不能为空 14 Objects.requireNonNull(h); 15 16 //2、clone一份需要生成动态代理器的接口集合,用于下面的操作 17 final Class<?>[] intfs = interfaces.clone(); 18 19 //3、创建安全管理器 20 final SecurityManager sm = System.getSecurityManager(); 21 if (sm != null) { 22 //权限检查 23 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); 24 } 25 26 //4、查找并生成指定的代理类【获得一个代理类的对象】 27 Class<?> cl = getProxyClass0(loader, intfs); 28 29 //5、调用构造函数去创建代理类对象 30 try { 31 if (sm != null) { 32 //校验创建新代理类的权限 33 checkNewProxyPermission(Reflection.getCallerClass(), cl); 34 } 35 36 //5.1 从刚才生成的代理类对象中查找参数为InvocationHandler的构造器 37 final Constructor<?> cons = cl.getConstructor(constructorParams); 38 final InvocationHandler ih = h; 39 40 //5.2 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的。 41 // 【什么都不加 是0 ,public 是1,private 是2,protected 是4,static 是8 ,final 是16】 42 if (!Modifier.isPublic(cl.getModifiers())) { 43 //5.3 把非public方法设置为可访问的 44 AccessController.doPrivileged(new PrivilegedAction<Void>() { 45 public Void run() { 46 cons.setAccessible(true); 47 return null; 48 } 49 }); 50 } 51 //6、通过反射机制,将h【我们自定义的代理类生成器】作为参数,实例化并返回代理类实例对象。 52 return cons.newInstance(new Object[]{h}); 53 } catch (IllegalAccessException | InstantiationException e) { 54 throw new InternalError(e.toString(), e); 55 } catch (InvocationTargetException e) { 56 Throwable t = e.getCause(); 57 if (t instanceof RuntimeException) { 58 throw (RuntimeException) t; 59 } else { 60 throw new InternalError(t.toString(), t); 61 } 62 } catch (NoSuchMethodException e) { 63 throw new InternalError(e.toString(), e); 64 } 65 }
Proxy.newProxyInstance是Proxy的静态方法,代码并不难理解,除去权限相关的代码外,就剩下两步:
- 1,获取代理类对象(27行)
- 2,利用反射技术实例化代理类,并返回实例化对象(52行)
详解查看参考文章的第一篇:JAVA设计模式-动态代理(Proxy)源码分析【强推👍】
五、JDK动态代理和CGLIB代理的区别
区别
- JDK动态代理只能对实现了接口的类生成代理,而不能针对普通类【针对接口】
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)【针对父类】
选择依据
- (1)当Bean实现接口时,Spring就会用JDK的动态代理
- (2)当Bean没有实现接口时,Spring使用CGlib是实现
- (3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
效能区别【作了解即可】
- (1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
- (2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
参考文章: