美文网首页
MyBatis动态代理(一)

MyBatis动态代理(一)

作者: Doooook | 来源:发表于2020-07-21 22:56 被阅读0次

概要:在我们使用MyBatis的时候,发现Mapper仅仅只是一个接口,而不是一个包含逻辑的实现类的,我们都知道,一个接口是没有办法去执行的,那么为什么MyBatis就可以呢?这就涉及到动态代理了。

image.png

反射技术

被代理的类:

/**
 * @author: Jason
 * @date: 2020-07-19 22:19
 */
public class ReflectService {

    /**
     * 服务方法
     * @param name 姓名
     */
    public void sayHello(String name) {
        System.err.println("hello " + name);
    }

}

通过反射创建ReflectService并调用其sayHello方法:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author: Jason
 * @date: 2020-07-19 22:21
 */
public class ReflectTest {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 通过反射创建ReflectService对象
        Object reflectService = Class.forName(ReflectService.class.getName()).newInstance();
        // 获取服务方法:sayHello
        Method method = reflectService.getClass().getMethod("sayHello", String.class);
        // 反射调用方法
        Object zhangsan = method.invoke(reflectService, "zhangsan");
        // void 无返回值,null
        System.out.println(zhangsan);
    }
}

JDK动态代理

  1. 编写服务类(HelloService)和接口(HelloServiceImpl),这个是真正的服务提供者,在JDK代理中接口是必须的。
  2. 编写代理类,实现InvocationHandler接口
  3. 通过bind方法绑定委托对象返回一个代理类
  4. 实现invoke方法,通过method调用真是对象的方法,并可以在前后做增强
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author: Jay Mitter
 * @date: 2020-07-19 22:34
 */
public class HelloServiceProxy implements InvocationHandler {

    /**
     * 真实的代理对象
     */
    private Object target;

    /**
     * 绑定委托对象返回一个代理类
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        // target.getClass().getInterfaces() ==> JDK代理需要提供接口
        // spring默认使用jdk动态代理,如果类没有接口,则使用cglib。
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     *
     * @param proxy 代理对象
     * @param method 被调用的方法
     * @param args 方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("################### 我是JDK动态代理 ###################");
        Object result = null;
        // 增强
        // 反射方法前调用
        System.err.println("我准备说hello。");
        // 执行方法,相当于调用HelloServiceImpl类的sayHello方法
        result = method.invoke(target, args);
        // 反射方法后调用
        System.err.println("我说过了hello。");
        return result;
    }
}

HelloService接口:

/**
 * @author: Jay Miiter
 * @date: 2020-07-19 22:28
 */
public interface HelloService {

    /**
     * sayHello
     * @param name 姓名
     */
    void sayHello(String name);

}

HelloService接口实现类HelloServiceImpl:

/**
 * @author: Jason
 * @date: 2020-07-19 22:29
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.err.println("hello " + name);
    }
}

JDK的代理最大的缺点是需要提供接口,而MyBatis的Mapper就是一个接口,它采用的就是JDK的动态代理。

CgLib动态代理

JDK提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服这个缺陷,我们可以使用开源框架——CGLIB,它是一种流行的动态代理。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author: Jay Mitter
 * @date: 2020-07-20 21:43
 */
public class HelloServiceCgLib implements MethodInterceptor {

    /**
     * 创建代理对象
     * 根据一个类型产生代理类
     * @param clazz
     * @return
     */
    public Object getInstance(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    /**
     * 回调方法
     *
     * @param object
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.err.println("################# 我是CGLIB的动态代理 #################");
        // 增强
        // 反射方法前调用
        System.err.println("我准备说hello。");
        Object returnObj = methodProxy.invokeSuper(object, args);
        // 反射方法后调用
        System.err.println("我说过hello了。");
        return returnObj;
    }

}

这样便能够实现CGLIB的动态代理。在MyBatis中通常在延迟加载的时候才会用到CGLIB的动态代理。

无需接口的HelloServiceImplNoInterface类:

/**
 * @author: Jay Mitter
 * @date: 2020-07-19 22:29
 * CgLib 无需实现接口
 */
public class HelloServiceImplNoInterface {

    public void sayHello(String name) {
        System.err.println("hello " + name);
    }

}

测试

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author: Jay Mitter
 * @date: 2020-07-20 21:31
 */
public class HelloServiceMain {

    public static void main(String[] args) {
        testJdkProxy();
        testCgLibProxy();
    }

    private static void testJdkProxy() {
        // 将生成的字节码保存到本地,
        createProxyClassFile();
        HelloServiceProxy helloServiceHandler = new HelloServiceProxy();
        HelloService proxy = (HelloService) helloServiceHandler.bind(new HelloServiceImpl());
        proxy.sayHello("张三");
    }

    private static void testCgLibProxy() {
        HelloServiceCgLib helloServiceCgLib = new HelloServiceCgLib();
        HelloServiceImplNoInterface instance = (HelloServiceImplNoInterface) helloServiceCgLib.getInstance(HelloServiceImplNoInterface.class);
        instance.sayHello("李四");
    }

    private static void createProxyClassFile() {
        String name = "ProxyHelloService";
        byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{HelloService.class});
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(name + ".class");
            System.out.println((new File("hello")).getAbsolutePath());
            out.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

通过字节啊生成的实现类:

/**
 * @author Jay Mitter
 * @date 2020-07-20 21:43
 * JDK动态代理通过字节码生成的最终真正的代理类,它继承自Proxy并实现了我们定义的HelloService接口
 * 在实现Subject接口方法的内部,通过反射调用了InvocationHandlerImpl的invoke方法。
 */
public final class ProxyHelloService extends Proxy implements HelloService {

    private static final long serialVersionUID = 4642095113614858015L;
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public ProxyHelloService(InvocationHandler var1) {
        super(var1);
    }

    @Override
    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    @Override
    public final void sayHello(String var1) {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    @Override
    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    @Override
    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.pengjs.kkb.mybatis.service.HelloService").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

相关文章

  • mybatis 源码解析之如何实现 mapper 动态代理

    从源码解析 mybatis 是如何实现 mapper 动态代理的。 mybatis 底层是基于 JDK 动态代理来...

  • MyBatis动态代理(二)

    接着上一篇MyBatis动态代理(一),这一篇我们来追踪下MyBatis的动态代理 0. 通过SqlSession...

  • Mybatis动态代理

    Mybatis动态代理 实现 MapperProxy实现InvocationHandler接口,重写...

  • MyBatis-Mapper动态代理

    MyBatis官方教程MyBatis二级缓存设计Mybatis中Mapper动态代理的实现原理制作Mybatis插...

  • 设计模式之动态代理

    动态代理模式,在当前流行框架(如:Spring、Mybatis、Dubbo)中应用非常广泛,掌握动态代理模式是理...

  • MyBatis动态代理(一)

    概要:在我们使用MyBatis的时候,发现Mapper仅仅只是一个接口,而不是一个包含逻辑的实现类的,我们都知道,...

  • MyBatis之Mapper动态代理开发

    title: MyBatis之Mapper动态代理开发tags: MyBatiscategories: MyBat...

  • javassist动态代理与cglib动态代理

    mybatis的懒加载是用到了javassist的动态代理,所以想先简单说一下这个,顺便带上cglib动态代理。 ...

  • 动态代理

    动态代理理论及Mybatis实战动态代理在我们平时编程并不常用,但因为Spring AOP以及大量框架均使用动态代...

  • Mybatis插件

    Mybatis插件 Mybatis插件又称拦截器。 Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器...

网友评论

      本文标题:MyBatis动态代理(一)

      本文链接:https://www.haomeiwen.com/subject/uisgkktx.html