前面文章有说到代理模式:http://aphysia.cn/archives/dynamicagentdesignpattern

那么回顾一下,代理模式怎么来的?假设有个需求:

在系统中所有的 controller 类调用方法之前以及之后,打印一下日志。

假设原来的代码:

public class Person{
public void method(){
// 表示自己的业务逻辑
process();
}
}

如果在所有的类里面都添加打印方法,这样肯定是不现实的,如果我有几百个这样的类,写到崩溃,况且重复代码太多,冗余,还耦合到一块了,要是我下次不打日志了,做其他的,那几百个类又全部改一遍。

public class Person{
public void method(){
log();
// 表示自己的业务逻辑
process();
log();
}
}

静态代理

怎么样写比较优美呢?静态代理 这时候出场了,先把方法抽象成为接口:

public class IProxy(){
public void method();
}

让具体的类去实现 IProxy,写自己的业务逻辑,比如:

public class Person implements IProxy(){
public void method(){
// 表示自己的业务逻辑
process();
}
}

然后弄个代理类,对方法进行增强:

public class PersonProxy implements IProxy{
private IProxy target;
public PersonProxy(IProxy target){
this.target = target;
}
@Override
public void method() {
log();
target.method();
log();
}
}

调用的时候,把真实的对象放到代理类的构造器里面,就可以得到一个代理类,对它的方法进行增强,好处就是,如果下次我要改,不打日志,做其他事情,我改代理类就可以了,不用到处改我的目标类的方法,而坏处还是很明显,要增强哪一个类,就要为它写一个代理类,这样好像不是很合理。

动态代理

怎么样能让他自动生成代理对象呢? 动态代理做的就是这个事情,它可以 动态 的根据我们提供的类,生成代理类的对象。

最主要的,是在运行时,动态生成,只要传入我们要代理增强的类相关的信息,比如类对象本身,类加载器,类的接口等,就可以生成,不用提前知道它是 A 类,B 类还是 C 类。

动态代理主要有三种实现方法,今天我们重点分析 JDK 动态代理:

  • JDK 代理:使用 JDK 提供的官方的 Proxy
  • 第三方 CGlib 代理:使用 CGLib 的 Enhancer 类创建代理对象
  • javassit:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。

JDK 动态代理

使用步骤

  1. 新建一个接口
  2. 新建一个类,实现该接口
  3. 创建代理类,实现 java.lang.reflect.InvocationHandler 接口

代码如下:

IPlayDao.java(玩的接口)

public interface IPlayDao {
void play();
}

StudentDao.java(实现了买东西,玩的接口的学生类)

public class StudentDao implements IPlayDao {
@Override
public void play() {
System.out.println("我是学生,我想出去玩");
}
}

MyProxy.java 代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
private Object target;
public MyProxy(Object target){
this.target=target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
// 一个接口可能很多方法,要是需要针对某一个方法,那么需要在函数里判断 method
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务 2");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务 2");
return returnValue;
}
}
);
}
}

测试类(Test.java)

public class Test {
public static void main(String [] args){
// 保存生成代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); IPlayDao target =new StudentDao();
System.out.println(target.getClass());
IPlayDao proxy = (IPlayDao) new MyProxy(target).getProxyInstance();
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.play(); }
} 由于加了这句代码,我们可以把生成的代理类的字节码文件保存下来, 其实通过输出也可以看到,两个对象不是同一个类,代理类是动态生成的: ```java
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

源码分析

跟着源码一步步看,先从调用的地方 Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

进入方法里面,省略各种异常处理,主要剩下了生成代理类字节码 以及 通过构造函数反射构造新对象

    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);
} /*
* 查找或者生成代理对象
*/
Class<?> cl = getProxyClass0(loader, intfs); 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});
}

上面注释里面说查找或者生成代理对象,为什么有查找?因为并不是每一次都生成,生成的代理对象实际上会缓存起来,如果没有,才会生成,看源码 Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)

    private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length> 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 调用缓存代理类的 cache 来获取类加载器
return proxyClassCache.get(loader, interfaces);
}

如果由实现给定接口的给定加载器定义的代理类存在,这将简单地返回缓存的副本; 否则,它将通过 ProxyClassFactory 创建代理类,proxyClassCache 其实就是个 weakCache:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

初始化的时候,proxyClassCache 指定了两个属性,一个是 KeyFactory, 另外一个是 ProxyClassFactory, 从名字就是猜到 ProxyClassFactory 是代理类工厂:

    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}

记住这里的 subKeyFactory,实际上就是传入的 ProxyClassFactory,那前面 proxyClassCache.get(loader, interfaces); 到底是怎么操作的?

上面调用到了 subKeyFactory.apply(key, parameter),这个 subKeyFactory 实际上是我们传的 ProxyClassFactory, 进入里面去看:

    private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成的代理类前缀
private static final String proxyClassNamePrefix = "$Proxy"; // 下一个生成的代理类的名字的计数器,一般是 $Proxy0,$Proxy1
private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* 检验类加载器是否能通过接口名称加载该类
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + "is not visible from class loader");
}
/*
* 判断接口类型
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + "is not an interface");
}
/*
* 判断是否重复
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface:" + interfaceClass.getName());
}
}
// 代理包名字
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /*
* 记录非公共代理接口的包,以便在同一个包中定义代理类。确认所有非公共代理接口都在同一个包中。
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
} if (proxyPkg == null) {
// 如果没有非公共代理接口,请使用 com.sun.proxy 包
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
} /*
* 为要生成的代理类选择一个名称。
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num; /*
* 生成指定的代理类。(这里是重点!!!)
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* 这里的 ClassFormatError 意味着 (禁止代理类生成代码中的错误) 提供给代理类创建的参数有其他一些无效方面 (比如超出了虚拟机限制)。
*/
throw new IllegalArgumentException(e.toString());
}
}
}

上面调用一个方法生成代理类,我们看看 IDEA 反编译的代码:

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
// 生成文件
final byte[] var4 = var3.generateClassFile();
// 判断是否要写入磁盘!!!
if (saveGeneratedFiles) {
// 开启高权限写入
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1> 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
} Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file:" + var4x);
}
}
});
} return var4;
}

生成代理文件实际上和我们想的差不多,就是一些 hashCode(),toString(),equals(), 原方法,代理方法等:

这与我们之前看到的文件一致:

然后之所以我们代码要设置写入磁盘,是因为这个变量, 控制了写磁盘操作:

    private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

为什么只支持接口实现,不支持继承普通类?

因为代理类继承了 Proxy 类,并且实现了接口,Java 不允许多继承,所以不能代理普通类的方式,并且在静态代码块里面,用反射方式获取了所有的代理方法。

JDK 代理看起来像是个黑盒,实际上,每一句代码,都有其缘由。其实本质上也是动态的为我们的原始类,动态生成代理类。

生成的代理类里面其实对原始类进行增强(比如 play() 方法)的时候, 调用了 super.h.invok() 方法,其实这里的 h 是什么呢?

h 是父类的 h, 生成的代理类的父类是 Proxy,Proxy 的 h,就是我们传入的 InvocationHandler:

    public final void play() throws  {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}

生成的代码里面通过反射调用到的其实是我们自己重写的那部分逻辑,所以就有了增强的功能,不得不说,这种设计确实巧妙:

动态代理有多香

动态代理是Java语言里面的一个很强大的特性,可以用来做一些切面,比如拦截器,登录验证等等,但是它并不是独立存在的,任何一个知识点都不能独立说明语言的强大,重要的是它们的组合。

动态代理要实现强大的功能,一般需要和反射,注解等一起合作,比如对某些请求进行拦截,拦截后做一些登录验证,或者日志功能。最重要的一点,它能够在减少耦合度的前提下实现增强。

【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析JDBCMybatisSpringredis分布式剑指OfferLeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

剑指Offer全部题解PDF

2020年我写了什么?

开源编程笔记

设计模式【3.2】-- JDK动态代理源码分析有多香?的更多相关文章

  1. 设计模式之JDK动态代理源码分析

    这里查看JDK1.8.0_65的源码,通过debug学习JDK动态代理的实现原理 大概流程 1.为接口创建代理类的字节码文件 2.使用ClassLoader将字节码文件加载到JVM 3.创建代理类实例 ...

  2. jdk 动态代理源码分析

    闲来无事,撸撸源码 使用方法 直接看代码吧.. package com.test.demo.proxy; import java.lang.reflect.InvocationHandler; imp ...

  3. JDK动态代理源码分析

    先抛出一个问题,JDK的动态代理为什么不支持对实现类的代理,只支持接口的代理??? 首先来看一下如何使用JDK动态代理.JDK提供了Java.lang.reflect.Proxy类来实现动态代理的,可 ...

  4. 动态代理学习(二)JDK动态代理源码分析

    上篇文章我们学习了如何自己实现一个动态代理,这篇文章我们从源码角度来分析下JDK的动态代理 先看一个Demo: public class MyInvocationHandler implements ...

  5. java 1.8 动态代理源码分析

    JDK8动态代理源码分析 动态代理的基本使用就不详细介绍了: 例子: class proxyed implements pro{ @Override public void text() { Syst ...

  6. JDK动态代理源码学习

    继上一篇博客设计模式之代理模式学习之后http://blog.csdn.net/u014427391/article/details/75115928,本博客介绍JDK动态代理的实现原理,学习一下JD ...

  7. JDK动态代理源码解析

    动态代理.静态代理优缺点     关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的 ...

  8. 深入剖析JDK动态代理源码实现

    动态代理.静态代理优缺点优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性.这是代理的共有优点.动态代理只有在用到被代理对象的时候才会对被代理类进行类加载. 而静态代理在编译器就已经开始占内存了 ...

  9. JDK7动态代理源码分析

    IObject proxy = (IObject) Proxy.newProxyInstance(IObject.class.getClassLoader(), new Class[]{IObject ...

  10. JDK动态代理源码剖析

    关键代码: 1.Proxy.newInstance: private static final Class<?>[] constructorParams = { InvocationHan ...

随机推荐

  1. 声明式 Shadow DOM:简化 Web 组件开发的新工具

    在现代 Web 开发中,Web 组件已经成为创建模块化.可复用 UI 组件的标准工具.而 Shadow DOM 是 Web 组件技术的核心部分,它允许开发人员封装组件的内部结构和样式,避免组件的样式和 ...

  2. Material Design In XAML Toolkit 5.0.0 Migration Guide

    MaterialDesignInXamlToolkit 5.0有破坏性的更新,下面的连接可以用于4.x升级到5.0的一个手册.仅供参考,欢迎升级5.0时使用. https://github.com/M ...

  3. SuperMap iManager云套件数据动态更新刷新地图与数据服务

    一.使用背景 有这么一个需求,后端也就通过SuperMap iDesktop或数据库更新了新增或更新某个数据地理信息后,云套件SuperMap iManager中的服务没有更新,无法实时查看到更新的数 ...

  4. Windows右下角时间显示到秒(改注册表)

    ​ 事件起因: 由于京东秒杀,要准点抢购,于是想着能不能把Windows右下角的时间显示到秒,于是在网上查了一下,修改注册表即可 解决办法: 新建一个 ShowSecondsInSystemClock ...

  5. 安装mysql5.7报错启动失败(3534)的处理

    一 ,在官网下载好了mysql5.7之后,解压到对应的文件的夹下 二, 在cmd中进入到mysql下单bin目录下,一定要是管理员权限,执行mysqld --initialize 命令,会看到根目录下 ...

  6. iOS Masonry使用小结

    一.Masonry简介 Masonry是一个轻量级的布局框架,它拥有自己的描述语法(采用更优雅的链式语法封装)来自动布局,具有很好可读性且同时支持iOS和Max OS X等. 二.Masonry的基本 ...

  7. js 中什么情况下返回 undefined 值

    1. 声明变量没有赋值 <script> let num console.log(num) //undefined </script> 2. 访问不存在的属性 <scri ...

  8. mysql进阶-SQL优化篇

    SQL优化 -插入数据 批量插入:(一次尽量不超过1000条) Insert into tb test values(1,'Tom'),(2,'cat'),(3, Jerny'); 手动事务提交: s ...

  9. C++ mutable与常对象语义详解

    摘编自 <Effective C++> 条款三. "成员函数如果是const" 或者 "一个对象是const对象"到底意味什么?有两个流行概念:bi ...

  10. 10个一行Python代码:利用sort()函数进行高效排序

    今天我们要玩点有趣的--用一行代码来展示sort()函数的超级能力!通常,排序算法可能让你觉得是编程中的"重量级选手",但有了Python的sort(),一切都变得轻而易举,甚至可 ...