• 模拟mybatis动态生成Mapper实例的实现


    动态代理常用的有两种实现方式,一是java自带的方式,一种是cglib提供的

    mybatis使用cglib的动态代理生成mapper实例

    这里模拟一下两种实现

    常用的mybatis操作数据库的方式如下:

    定义一下接口,里面的每个方式对应 *Mapper.xml(如bookMapper.xml)的每个sql

    public interface BookMapper {
        int getCount();
    }

    使用时一般是

    @Autowired
    private BookMapper bookMapper;
    
     public static void main(String[] args) {
            int count = bookMapper.getCount();
            log.info("图片总数量:{}", count);
    }

    下面开始代码实现(这里简化了从xml文件到可解析的sql的过程)

    1.java 动态代理 方式

    1.1简单定义sql上下文相关类

    enum DaoOperation {
        Creat,
        Retrieve,
        Update,
        Delete
    }
    
    @AllArgsConstructor
    @Data
    class DaoContext {
        private DaoOperation operation;
        private String sql;
    }
    
    class DaoInfo {
        // 这个类的Map就可以通过检索所有 xml的方式来生成生成
        static Map<String, DaoContext> map = ImmutableMap.<String, DaoContext>builder()
                .put("getCount", new DaoContext(DaoOperation.Retrieve, "select count(1) from book"))
                .build();
    }

    1.2动态代理

    import com.google.common.collect.ImmutableMap;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Map;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    class CustomerMapperMethodProxy implements InvocationHandler {
    
        public static Object getInstance(Class<?> cls) {
            CustomerMapperMethodProxy invocationHandler = new CustomerMapperMethodProxy();
            Object newProxyInstance = Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, invocationHandler);
            return newProxyInstance;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 当前method所属的class
            Class classz = method.getDeclaringClass();
            if (classz.equals(Object.class)) {
                //调用的是Object的方法,如 toString(),equal()等
                throw new RuntimeException("不是Mapper方法");
            }
    
            // 获取方法名对应的信息.
            DaoContext daoContext = DaoInfo.map.get(method.getName());
            if (daoContext == null) {
                throw new RuntimeException("没有实现该方法的sql语句");
            }
    
            return execSql(daoContext);
    
        }
    
        private Object execSql(DaoContext daoContext) {
            // 这里可以引入DAO来真正执行sql
            log.info("正在执行sql语句,类型为:{},语句为:{}", daoContext.getOperation().toString(), daoContext.getSql());
    
            return 1;
        }
    }

    1.3 测试代码

    /**
     * java 动态代理.
     */
    @Slf4j
    public class DynamicProxyTest {
    
        /**
         * 动态生成的bookMapper实例
         * 这个可以做成bean的方式给spring使用
         */
        private static BookMapper bookMapper = (BookMapper) CustomerMapperMethodProxy.getInstance(BookMapper.class);
    
        public static void main(String[] args) {
            int count = bookMapper.getCount();
            log.info("图片总数量:{}", count);
    
            String s = bookMapper.toString();
            log.info("toString:{}", s);
        }
    }

    结果

    10:51:50.893 [main] INFO com.g2.proxy.java.CustomerMapperMethodProxy - 正在执行sql语句,类型为:Retrieve,语句为:select count(1) from book
    10:51:52.037 [main] INFO com.g2.proxy.java.DynamicProxyTest - 图片总数量:1
    Disconnected from the target VM, address: '127.0.0.1:12200', transport: 'socket'
    Exception in thread "main" java.lang.RuntimeException: 没有实现该方法的sql语句
        at com.g2.proxy.java.CustomerMapperMethodProxy.invoke(DynamicProxyTest.java:52)
        at com.sun.proxy.$Proxy0.toString(Unknown Source)
        at com.g2.proxy.java.DynamicProxyTest.main(DynamicProxyTest.java:32)

    2.cglib实现

    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * Created by zhangjy on 2020/1/19.
     */
    @Slf4j
    public class CglibTest {
        private static BookMapper bookMapper = (BookMapper) SqlProxy.getInstance(BookMapper.class);
    
        public static void main(String[] args) {
            int count = bookMapper.getCount();
            log.info("图片总数量:{}", count);
        }
    }
    
    @Slf4j
    class SqlProxy implements MethodInterceptor {
    
        public static Object getInstance(Class<?> clz) {
            MethodInterceptor target = new SqlProxy();
            //创建加强器,用来创建动态代理类
            Enhancer enhancer = new Enhancer();
            //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
            enhancer.setSuperclass(clz);
            //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
            enhancer.setCallback(target);
            // 创建动态代理类对象并返回
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Class classz = method.getDeclaringClass();
            if (classz.equals(Object.class)) {
                //调用的是Object的方法,如 toString(),equal()等
                throw new RuntimeException("不是Mapper方法");
            }
    
            // 获取方法名对应的信息.
            DaoContext daoContext = DaoInfo.map.get(method.getName());
            if (daoContext == null) {
                throw new RuntimeException("没有实现该方法的sql语句");
            }
    
            return execSql(daoContext);
    
        }
    
        private Object execSql(DaoContext daoContext) {
            // 这里可以引入DAO来真正执行sql
            log.info("正在执行sql语句,类型为:{},语句为:{}", daoContext.getOperation(), daoContext.getSql());
    
            return 1;
        }
    }

    3.两种动态代理的区别

        JDK动态代理是通过"组合"方式,将业务类当做动态生成的代理类的一个属性且实现业务类的各个方法,最终调用业务实现类的同名方法;

        CGlib动态代理是通过"继承"方式,继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;(效率较高,各大框架都使用这种方式)

  • 相关阅读:
    Java的注释,标识符,标识符的命名规范
    Java入门程序(Java的开发流程)
    EasyMock使用总结
    【转载】 Java并发编程:深入剖析ThreadLocal
    java.util.Stack类中的peek()方法
    Jquery和Ajax
    关于HTTP协议及SOCKET通信
    (转载)session token机制
    TCP/IP知识总结(TCP/IP协议族读书笔记四)
    TCP/IP知识总结(TCP/IP协议族读书笔记三)
  • 原文地址:https://www.cnblogs.com/zhshlimi/p/12212863.html
Copyright © 2020-2023  润新知