JDK动态代理原理和mybatis的Mapper底层实现

一、jdk动态代理原理

所谓动态代理就是代理对象是动态生成的,不像静态代理那样写好一个代理类,到时候直接new一个对象来代理就可以,静态代理当然没有动态代理灵活,扩展性也没有动态代理好,程序都讲究高内聚低耦合,所以动态代理相对于静态代理来说使用还是大很多。

其实JDK动态代理要做的事情用三句话就可以总结:
1、拦截目标对象
2、查找生成指定代理类
3、根据代理类的构造函数构造代理对象
所以我们后面看源码的时候就根据这三个思路去思考源码为什么要这样写就可以了。

1、拦截目标对象

拦截目标对象其实就是找到被代理的对象,这里要注意的是被代理的对象一定要是实现接口的,后面通过源码分析也可以看出来为什么要实现接口。获取目标对象逻辑并不复杂就不分析源码了。

2、查找生成指定代理类

这条是最重要也是逻辑最复杂的,我们通过源码分析它的原理。
在使用动态代理的时候我们都会写一行代码就是:
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
那么我们就从这个方法的源码开始看起:

   @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
这行代码就是生成代理对象并返回代理对象的构造器的,点进去看看这个方法:

   private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }

创建代理类的核心逻辑其实是在
proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
这一句代码中,这里有一个缓存,避免每次生成代理类都去生成一次,是非常耗性能的。如果缓存中没有就用builder创建一个,那么创建代理类的代码肯定要都builder的build方法里面去找:

 Constructor<?> build() {
            Class<?> proxyClass = defineProxyClass(module, interfaces);
            final Constructor<?> cons;
            try {
                cons = proxyClass.getConstructor(constructorParams);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            return cons;
        }

终于看到定义代理类的代码了:
Class<?> proxyClass = defineProxyClass(module, interfaces);
里面都有哪些逻辑呢?还是进去看看吧:

  private static final class ProxyBuilder {
        private static final Unsafe UNSAFE = Unsafe.getUnsafe();

        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        // a reverse cache of defined proxy classes
        private static final ClassLoaderValue<Boolean> reverseProxyCache =
            new ClassLoaderValue<>();

        private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;  // non-public, final
                    String pkg = intf.getPackageName();
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // all proxy interfaces are public
                proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                                       : PROXY_PACKAGE_PREFIX;
            } else if (proxyPkg.isEmpty() && m.isNamed()) {
                throw new IllegalArgumentException(
                        "Unnamed package cannot be added to " + m);
            }

            if (m.isNamed()) {
                if (!m.getDescriptor().packages().contains(proxyPkg)) {
                    throw new InternalError(proxyPkg + " not exist in " + m.getName());
                }
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg.isEmpty()
                                    ? proxyClassNamePrefix + num
                                    : proxyPkg + "." + proxyClassNamePrefix + num;

            ClassLoader loader = getLoader(m);
            trace(proxyName, m, loader, interfaces);

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
            try {
                Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
                                                 0, proxyClassFile.length,
                                                 loader, null);
                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                return pc;
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }

这段代码非常长,但是正在在做定义代理类的代码也就一行:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
但是这行代码传入了三个参数,都是经过前面的代码加工而来的。

(1)第一个参数是将要生成的代理类的包名和类名
(2)第二个参数是代理类的接口列表
(3)第三个参数就是记录是否有不是public的接口,后面校验所有非public的接口是否都在一个包下面。

第一个参数的包名是怎么来的呢?请看下面的代码
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + “.” + m.getName()
: PROXY_PACKAGE_PREFIX;
如果你的接口有非public的那么就用被代理类的包名,如果都是public的就分为两种情况:
(1)如果配置了代理的子包名,就用全局的包名+子包名。最终生成的代理对象类似这样:“com.sun.proxy” + 配置的全局子包名 + “ P r o x y ” + s e r i a l N u m b e r ( 2 ) 如 果 没 有 配 置 就 直 接 用 全 局 的 包 名 。 最 终 生 成 的 代 理 对 象 类 似 这 样 : " c o m . s u n . p r o x y " + “ Proxy” + serialNumber (2)如果没有配置就直接用全局的包名。最终生成的代理对象类似这样:"com.sun.proxy" + “ Proxy+serialNumber2"com.sun.proxy"+Proxy” + serialNumber

最后 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);这行代码就生成字节码文件,并返回文件的字节数组。这里面的代码就不是主要代码就是文件系统的IO。
我们再来看看生成字节码文件后的下一行代码:
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
这行代码就是用刚才生成的字节码去加载一个类,并生成Class对象。
如果对类加载机制和原理有兴趣的可以去找相关资料了解。
类加载核心逻辑都是native的方法,这里就不分析了。

至此,代理类就生成成功了,至于后面返回构造器生成代理对象就是简单的反射,就不赘述了。

总结一下:

我们在使用代理对象的时候,调方法其实是调用的是代理对象的方法,代理对象的方法和被代理对象的方法方法名称其实是相同的。因为代理类也是实现了被代理类相同的实现接口的。
代理类在构造的时候注入了InvocationHandler接口的实现类的对象,所以在调用代理类的业务方法的时候,它会去调用InvocationHandler实现类的invoke方法。我们只要看看InvocationHandler的invoke方法的逻辑,这个代理执行流程就很清楚了,一般我们在实现InvocationHandler接口的时候大概都是这样实现的:

private Object target;

public SayHelloInvocationHandler(Object target){
    this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //前置处理逻辑
    // 执行相应的目标方法
    Object rs = method.invoke(target,args);
    //后置处理逻辑
    return rs;
}

InvocationHandler的实现类需要注入一个被代理对象,然后在执行invoke方法的时候会把被代理对象的相应逻辑执行一下。我们再在被代理对象业务逻辑前后增加我们要增加的代码,就可以增强被代理对象的功能了。

二、mybatis Mapper底层实现

mybatis的mapper底层用的就是jdk的动态代理实现的,我们来看看它的MapperProxy的代码。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

这里就很有意思了,这个代理虽然和上面我们分析的代理一样的,但是它却没有按照上面分析的那样执行。

为什么会这样呢?

其实mybatis在生成代理对象的时候并没有传入mapper接口的实现类的对象,只传入了一个SqlSession,其实这个也很好理解(不可能让用户去实现Mapper接口的方法吧?)。

这个方法那个proxy参数其实是代理对象本身,我们可以看看调用这个invoke方法的地方:

在这里插入图片描述

代理对象本身肯定和这里的method参数的所在的类不是同一个类,这个逻辑必然是不走的。

method参数其实是我们mapper接口里面的method。

其实总结起来就是,mybatis的mapper接口的代理类在代理mapper接口的时候,代理对象会实现mapper接口,在执行代理对象的对应接口方法的时候,会去调用handler也就是mapperProxy的invoke方法,这个方法的三个参数分别是:代理对象本身,接口的方法对象,接口方法的参数列表。在执行invoke方法的时候直接不执行接口的那个method,因为那个method没有任何实现,而去执行sqlSession里面的逻辑。sqlSession里面的执行逻辑才是mybatis的核心逻辑实现。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值