首页 > 其他分享 >手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

时间:2024-01-19 15:32:48浏览次数:30  
标签:Mapper UserMapper 代理 Class 基础架构 mapperInterface public



这里写目录标题

  • 前言
  • 温馨提示
  • 手把手带你解析 @MapperScan 源码
  • 手把手带你解析 @MapperScan 源码细节剖析
  • 工厂模式+Jdk 代理手撕脚手架,复刻 BeanDefinitionRegistryPostProcessor
  • 手撕 FactoryBean
  • 代理 Mapper 在 Spring 源码中的生成流程
  • 手撕 MapperProxyFactory
  • 手撕增强逻辑 InvocationHandler
  • 源码级别解读 Mapper 要被设计成接口的原因
  • 自定义 Executor 实现,框架 Dao 层
  • 手写基础架构效果演示
  • 总结
  • 小咸鱼的技术窝


前言

最近在码云搜 Es 的开源项目学学技术,无意间搜到 Easy-Es 这么一个项目,里面的用法和 Mybatis-Plus 一模一样,当时心想我擦,这个人是直接悟透了 Mybatis-Plus 吗,虽然老早前看过源码。之前大概看了一下,就是对 Mapper 对象进行代理,植入了一些自定义逻辑而已,没仔细看过实现细节,现在网上居然有人直接又造了一个轮子,直呼 666,于是乎深入看了 Mybatis-Plus 是如何生成 Mapper 代理对象的全部源码,并且一比一复刻出来了。

温馨提示

阅读以下文章了解前置知识对理解本文更有帮助

  1. 深入jdk动态代理源码解析
  2. 模拟jdk动态代理(完整版)
  3. Factorybean与BeanFactory的区别
  4. 手把手debug自动装配源码、顺带弄懂了@Import等相关的源码(全文3w字、超详细)
  5. spring 源码解析(配图文讲解)顺带搞懂了循环依赖、aop底层实现

手把手带你解析 @MapperScan 源码

废话不多说直接步入正题,我们在使用 Mybatis 的时候要要设置 @MapperScan 扫描对应的 Mapper 接口,一步步点进去

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_开发语言


手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_java_02


发现其实就是注册了 MapperScannerConfigurer 这个 Bean ,都是些常用套路。然后发现 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor、InitializingBean。

  1. BeanDefinitionRegistryPostProcessor:Spring 为我们提供的扩展点,让程序员可以自己干预 Bean 的生成
  2. InitializingBean:在 Bean 填充属性(populateBean)完成后会调用

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_03


直接看重写了 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法就行,看下图可以看到就是利用 ClassPathMapperScanner (路径扫描器)去扫描指定包下面的类,然后生成对应的 BeanDefinition 注册到 BeanDefinitionMap 中,然后 Spring 会将 BeanDefinitionMap 中的所有 BeanDefinition 生成 Bean 放到 Spring 单例池里面提供给程序员使用。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_04


然后来到 scan 源码,cmd+art+b 查看 doScan 实现类,点进第二个,第一个是 Spring 实现的,而我们看的是 Mybatis 的源码这里大家要注意一下!

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_目标对象_05


然后你会发现扫描完 basePackages 下的类生成对应的 BeanDefinition ,后还会去处理一下这些 BeanDefinition,click 进去。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_06


发现得到的所有 Mapper 的 BeanDefinition 的 BeanClass 都被替换成了mapperFactoryBeanClass (工厂 bean)

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_ide_07


手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_开发语言_08


到这里我大概就明白了,所有的 Mapper BeanDefinition 统一设置为 MapperFactoryBean 类型,最终生成的 Bean 本质 Class 是 MapperFactoryBean 但是名字依然是原来的名字,然后通过代理工厂统一生成代理对象(这也是很多开源框架的常用套路)。接下来验证一下我的猜想。看一下 MapperFactoryBean 构造实现了 FactoryBean 。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_09


当我们的项目中使用了如下代码时,拿到的 Bean 其实是在紧挨上图一中的 getObject 方法中创建的。

@Autowired
UserMapper userMapper;

然后进入 getMapper 方法里面。看到确实是通过 MapperProxyFactory (代理工厂)生成的代理对象 Mapper。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_10

看到这你是不是觉得源码也不过如此,对于整个简单的流程虽然走完了,但是作为一个要进行开发整个轮子的开发者来说,还远远不够。还需要了解更多细节

  1. 如何将指定包路径下的所有类生成 BeanDefinition ?。
  2. MapperProxyFactory 如何初始化,并且 MapperProxyFactory 如何根据感知生产什么类型的代理对象等

手把手带你解析 @MapperScan 源码细节剖析

这部分的文章读者可选择自行跳过,

knownMappers 中的数据什么时候初始化的?

回到 MapperFactoryBean 类中可以看到 checkDaoConfig 方法左侧有一个这个小图标,说明就是抽象接口的实现类,一般为了简化操作很多框架包括我也喜欢利用抽象接口封装逻辑

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_mybatis_11


点击来到了上层的实现类,发现还被包裹了一层逻辑接着点向上的那个图标

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_开发语言_12


来到最顶层的 checkDaoConfig 发现原来 MapperFactoryBean 居然实现了 InitializingBean 接口,当 MapperFactoryBean 属性填充完成以后,进行调用 afterPropertiesSet 方法,触发我们的 checkDaoConfig 方法调用。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_目标对象_13

最终会发现在进行 addMapper 的时候会以 key:mapperInterface ,value:MapperProxyFactory 的键值对放到 knownMappers 里面,而 mapperInterface 其实就UserMapper 的 Class。

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_开发语言_14


手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_java_15

Mapper 是使用什么代理创建的?

答:看一下 MapperProxyFactory 源码得知是用的 Jdk 代理,直接代理接口

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_目标对象_16

如何动态的批量创建、修改 Bean ?

答:通过实现 Spring 提供的扩展接口 BeanDefinitionRegistryPostProcessor 动态注册、修改 BeanDefinition 即可。

如何实现动态的将一个普通 Bean 改成工厂 Bean ?

答:通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下

genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass());   
genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);

源码中的 mapperInterface 是什么东西?

答:Mapper 对象的 Class。举个例子当项目中用到了

@Autowired
UserMapper userMapper;

此时的 mapperInterface 就是 UserMapper.Class

为什么 Mapper 的代理对象能转换成目标对象?

了解 Jdk 动态代理的都知道,代理对象不能转换成目标对象,只能装换成目标对象的接口实现类或者 Proxy 对象,原因就是如下,可以看到代理对象和目标对象半毛钱关系都没有。

代理对象 extends proxy implments 目标对象实现接口

那为什么 UserMapper 的代理对象但是还能用 UserMapper 接收呢?项目中应该这样使用才对啊!!

@Autowired
Proxy userMapper;

工厂模式+Jdk 代理手撕脚手架,复刻 BeanDefinitionRegistryPostProcessor

里面的逻辑主要就是扫描指定包下面的类,生成对应的 BeanDefinition,然后自定义一个我们自己的后置处理器,将所有 BeanDefinition 替换成工厂 Bean。读者可自行封装对应的后置处理器,方便其他使用者进行扩展。整个流程对标 ClassPathMapperScanner 源码中的 doScan 逻辑。

/**
 * 扫描哪些包是 mapper,并统一设置类型为 BaseFactoryBean
 */
@Slf4j
@Component
public class RegistryPostProcessorConfig implements BeanDefinitionRegistryPostProcessor {
    private Class<? extends BaseFactoryBean> mapperFactoryBeanClass = BaseFactoryBean.class;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //扫描指定路径下的 BeanDefinition
        Set<BeanDefinitionHolder> beanDefinitions = scan();
        if (beanDefinitions.size() >= 1) {
            //后置处理器:全部替换成工厂 Bean
            factoryBeanDefinitionPostProcess(beanDefinitions);
        }
        //注册 BeanDefinition
        register(beanDefinitions,registry);
        log.info("自定义 Mapper 扫描注册完成");
    }

    /**
     * 扫描指定包下面的类,包装成一个个的 BeanDefinitionHolder,我这里就简单写写直接指定了
     */
    public Set<BeanDefinitionHolder> scan() {
        HashSet<BeanDefinitionHolder> beanDefinitions = new HashSet<>();
        GenericBeanDefinition scanBeanDefinition = new GenericBeanDefinition();
        scanBeanDefinition.setBeanClassName("userMapper");
        scanBeanDefinition.setBeanClass(UserMapper.class);
        GenericBeanDefinition scanBeanDefinition2 = new GenericBeanDefinition();
        scanBeanDefinition2.setBeanClassName("studentMapper");
        scanBeanDefinition2.setBeanClass(StudentMapper.class);
        beanDefinitions.add(new BeanDefinitionHolder("userMapper",scanBeanDefinition));
        beanDefinitions.add(new BeanDefinitionHolder("studentMapper",scanBeanDefinition2));
        return beanDefinitions;
    }

    public void factoryBeanDefinitionPostProcess(Set<BeanDefinitionHolder> beanDefinitions) {
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
            GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            genericBeanDefinition.setLazyInit(false);
            /**
             * 设置 bean 创建的构造 class,必须设置不然 bean 无法被创建
             */
            genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition()).getBeanClass());
            genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);
        }
    }

    public void register(Set<BeanDefinitionHolder> beanDefinitions, BeanDefinitionRegistry registry) {
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
            /**
             * BeanDefinition 重置了 BeanClass 为 BaseFactoryBean 后,对应的 BeanClassName 会自动变成 com.zzh.service2.structure.factory.bean.BaseFactoryBean
             * 造成所有的 Mapper 接口的 BeanDefinition 的 BeanClassName 都是 com.zzh.service2.structure.factory.bean.BaseFactoryBean 导致注册报错!!!
             * 因此自定义包装 BeanDefinitionHolder 对象,设置原始 BeanName
             * 例如:BeanDefinitionHolder(key->userMapper,value->BeanDefinition)
             * BeanDefinitionHolder(key->studentMapper,value->BeanDefinition)
             */
            registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(), beanDefinitionHolder.getBeanDefinition());
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

}

手撕 FactoryBean

实现 FactoryBean 接口,同时设置成泛型,让任何类型的 Mapper 接口都是转换成此 FactoryBean,当 Spring 进行属性填充完成之后,进行初始化 Bean 的时候会调用 InitializingBean 接口里面的方法,此时我们将 UserMapper.Class 放到一个临时容器中,等 BaseFactoryBean.getObject 方法被调用的时候,再去容器里面拿到 UserMapper.Class 进行 Jdk 代理创建代理对象。

@Data
public class BaseFactoryBean<T> implements FactoryBean<T>, InitializingBean {
    /**
     * > 如何实现动态的将一个普通 Bean 改成工厂 Bean ?
     * 通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下
     * ```javascript
     * genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass());
     * genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);
     * ```
     */
    public BaseFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    private Class<T> mapperInterface;

    /**
     * 通过 MapperProxyFactory 工厂统一生产代理对象
     */
    @Override
    public T getObject() throws Exception {
        return (T) BaseMapperRegistry
                .getMapper(mapperInterface)
                .newInstance(DaoTemplateFactory.getInstance().getDaoTemplate());
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }


    @Override
    public void afterPropertiesSet() {
        BaseMapperRegistry.addMapper(this.mapperInterface);
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

补充一嘴 Spring 中的源码逻辑,BeanDefinitionMap 中所有的 BeanDefinition 都会走
CreateBean 的流程,先是调用 createBeanInstance 方法创建一个实例对象,然后调用 populateBean 方法为实例对象填充属性,接着才是调用 InitializingBean 里面的方法。可以看到此时的 mapperInterface 是 UserMapper.Class

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_ide_17

代理 Mapper 在 Spring 源码中的生成流程

当创建好 UserMapper 这个 Bean 的时候,会调用 getObjectForBeanInstance 方法获取其实例,发现 UserMapper 是个工厂 Bean,于是乎调用 getObject 方法,走我们的 Jdk 创建代理对象的逻辑,最终放到 Ioc 容器里面的是我们自己创建的代理对象!

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_java_18

然后顺着栈帧来到 getObject,到此整个流程结束!

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_ide_19

手撕 MapperProxyFactory

根据目标对象的 Class 生成代理对象,同时 InvocationHandler 里面织入我们手写的 DaoTemplate,用来与数据库进行交互。
亮点代码只有一行:由于目标对象自身是一个 Mapper 接口,参数二实现类的接口用的是自己本身 new Class[]{mapperInterface} 这样生成的代理对象就可以转换成目标对象了。

T proxyInstance = (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);
@Slf4j
public class MapperProxyFactory<T> {
    /**
     * 被代理对象 Class
     */
    @Getter
    private final Class<T> mapperInterface;
    private ConcurrentHashMap methodCaches = new ConcurrentHashMap<Object, Object>();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(MapperProxyInvoke<T> baseMapperProxy) {
        T proxyInstance = (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);
        log.info("proxyInstance instanceof BaseMapper:{}", proxyInstance instanceof BaseMapper);
        log.info("proxyInstance instanceof BaseMapper:{}", proxyInstance instanceof UserMapper);
        return proxyInstance;
    }

    /**
     * Mybatis-Plus 封装了 SqlSession 对象操作 db,我这里也简单封装一个 DaoTemplate 做做样子
     * @param daoTemplate
     * @return
     */
    public T newInstance(DaoTemplate daoTemplate) {
        MapperProxyInvoke<T> baseMapperProxy = new MapperProxyInvoke<T>(daoTemplate, mapperInterface, methodCaches);
        return newInstance(baseMapperProxy);
    }

    /**
     * 为啥jdk生成的代理对象居然不支持类型转换为目标对象?
     * 
     */
    public static void main(String[] args) {
        test1();
        test2();
    }

    /**
     * 强制代理对象实现 UserMapper 接口,从而实现 jdk生成的代理对象支持转换为目标对象!!!!!!!
     * 关键代码:new Class[]{UserMapper.class}
     * 这也是为什么 Mapper 要设计成接口的原因!!!!!!!
     * 代理对象结构:代理对象 extends proxy implments UserMapper
     */
    static void test1() {
        Mapper proxyInstance = (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.err.println("代理前置输出");
                return null;
            }
        });
        System.err.println(proxyInstance instanceof UserMapper); //true
        System.err.println(proxyInstance instanceof BaseMapper);
        System.err.println(proxyInstance instanceof Mapper);
    }

    /**
     * 普通 Jdk 代理对象只实现目标对象的实现接口
     * 代理对象结构:代理对象 extends proxy implments 目标对象实现接口
     */
    static void test2() {
        Mapper proxyInstance = (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), UserMapper.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.err.println("代理前置输出");
                return null;
            }
        });
        System.err.println(proxyInstance instanceof UserMapper); //false
        System.err.println(proxyInstance instanceof BaseMapper);
        System.err.println(proxyInstance instanceof Mapper);
    }

}

手撕增强逻辑 InvocationHandler

实现了 InvocationHandler 接口,每当代理 Mapper 中的方法被调用的时候,都会执行 invoke 中的逻辑。里面分默认方法(被 default 修饰的方法)与 db 查询的方法

/**
 * 代理 Mapper 增强逻辑
 */
@Slf4j
public class MapperProxyInvoke<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final DaoTemplate daoTemplate;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxyInvoke(DaoTemplate daoTemplate, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.daoTemplate = daoTemplate;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    /**
     *
     * @param proxy 生成的代理对象
     * @param method 被调用的目标对象方法
     * @param args 被调用的目标对象方法中的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("代理对象前置输出!!!!");
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                /**
                 * Mapper 自带的默认方法走这调用(userMapper.say())
                 */
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        /**
         * (userMapper.seleteById(1))走这调用
         * 此处需要 Method 与 daoTemplate 中的方法名称、参数做匹配然后调用 daoTemplate 中的方法
         * 源码中也是这么干的,我懒这里直接硬编码匹配 seleteById 了
         */
        return daoTemplate.seleteById(1);
//        mybatis 源码中还做了方法缓存加快处理速度
//        final MapperMethod mapperMethod = cachedMapperMethod(method);
//        return mapperMethod.execute(sqlSession, args);
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass,
                        MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }
}

源码级别解读 Mapper 要被设计成接口的原因

这里也贴一下 UserMapper 的代码吧,也解释一下 Mapper 为什么要采用接口的形式

/**
 * UserMapper 只能是接口,如果 UserMapper 为类,生成的代理对象不能转换为 UserMapper
 * 只能转换为 proxy、或者 BaseMapper ,原因:代理对象 extends proxy implments BaseMapper
 * 但是我们需要 @Autowire UserMapper 这样使用。需要代理对象为 UserMapper 类型,因此 UserMapper 只能是接口
 * 让生成的代理对象 extends proxy implments UserMapper
 */
public interface UserMapper extends BaseMapper<User> {
    default String say() {
        return "UserMapper say";
    }
}

自定义 Executor 实现,框架 Dao 层

代理对象会根据被调用的方法匹配 DaoTemplate 中的方法进行执行,在这里面可以自行封装类似于 Mybatis 二级缓存,多级 Executor ,动态数据源切换的逻辑,工程量巨大,我这里只提供思路,简单查个库给大家演示一下设计原理。到此所有组件全部开发完成。

/**
 * 封装原始的 jdbc 逻辑,可扩展组件:多级缓存查询、多级 Executor 查询、数据库连接池切换等等
 */
public class DaoTemplate {
    String driver = "com.mysql.cj.jdbc.Driver";
    String url = "url";
    String username = "root";
    String password = "pwd";

    public Connection getConnection() throws SQLException, ClassNotFoundException {
        Class.forName(driver);
        return DriverManager.getConnection(url, username, password);
    }

    //随便写写了,直接拼接
    public User seleteById(Integer id) throws SQLException, ClassNotFoundException {
        String sql = "select * from user where id = " + id;
        ResultSet resultSet = getConnection().createStatement().executeQuery(sql);
        resultSet.next();
        return new User()
                .setId(resultSet.getInt("id"))
                .setName(resultSet.getString("name"));

    }

}

手写基础架构效果演示

基础依赖包如下

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_ide_20


可以看到使用我们手撕的 UserMapper 可以成功的查到 db 中的数据

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)_java_21

总结

本文基于源码分析了 Mybatis 中代理 Mapper 创建的详细流程,基于理解一比一手撕复刻了出来,期间遇到的问题都总结在注释里面了。有人说会这个有啥用,会这个你可以将所有和数据库打交道的技术,都封装成类似于 Mybatis-Plus 的框架造福全宇宙!!!让技术不在复杂让小学生都会写代码,你就是明日之星!!!!

小咸鱼的技术窝

关注不迷路,日后分享更多技术干货,B站微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页


标签:Mapper,UserMapper,代理,Class,基础架构,mapperInterface,public
From: https://blog.51cto.com/u_16414043/9330410

相关文章

  • 当“服务器上部署多个Web应用”,使用Nginx反向代理配置
    当“服务器上部署多个Web应用”,使用Nginx反向代理配置:https://wangcw.blog.csdn.net/article/details/80567233?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-80567233-blog-130914904.235%5Ev40%5Epc_relevant_a......
  • Nginx配置反向代理实例及Keepalived主从切换
    概述工作中经常需要帮同事配置反向代理,反向代理的使用场景一般为办公网跨网访问生产网应用资源。今天简单记录下操作步骤,以备之后查阅。NGX配置nginx的配置一般放置在/etc/nginx/nginx.conf下,可以使用whereisnginx查看nginx的具体位置[root@NGXapp01~]#whereisnginxngi......
  • scoop设置代理 , 解决”scoop fatal: unable to access“问题
    报错提示UpdatingScoop...fatal:unabletoaccess'https://github.com/ScoopInstaller/Scoop/':Failedtoconnecttogithub.comport443after21091ms:Couldn'tconnecttoserverRemove-Item:找不到路径“C:\Users\kai\scoop\apps\scoop\new”,因为该路......
  • 如何配置让pyenv使用代理(Windows)
    根据官方文档,只需要在系统的环境变量中设置http_proxy和https_proxy.Question:HowdoIconfiguremycompanyproxyinpyenvforwindows?Answer:Setthehttp_proxyorhttps_proxyenvironmentvariablewiththehostnameorIPaddressoftheproxyserverinURLfor......
  • 42 干货系列从零用Rust编写负载均衡及代理,wmproxy中配置tcp转websocket
    wmproxywmproxy已用Rust实现http/https代理,socks5代理,反向代理,静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子项目地址国内:https://gitee.com/tickbh/wmproxygithub:https://github.com/......
  • 使用nginx代理emqx的TCP、WS、WSS连接请求
    项目代理关系: 注:主机上已存在名为:nginx-proxy的一级nginx的代理,将监听了主机的80、443端口docker-compose.ymlversion:"3.7"services:emqx:image:emqx/emqx:4.4.18restart:unless-stoppedcontainer_name:emqxenvironment:EMQX_ADMI......
  • 13.代理配置
    什么是代理使用代理之前 title客户端服务端交互scale200widthscale400heightautonumberparticipant客户端asclientparticipant服务端asserverclient->server:发起请求server->client:返回响应使用代理之后 scale200widthscale700h......
  • 22. 从零用Rust编写正反向代理,一个数据包的神奇HTTP历险记!
    wmproxywmproxy已用Rust实现http/https代理,socks5代理,反向代理,静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子项目地址国内:https://gitee.com/tickbh/wmproxygithub:https://github.com/tickbh/wmproxy数......
  • 23. 从零用Rust编写正反向代理,流控小姐姐的温柔一刀!
    wmproxywmproxy已用Rust实现http/https代理,socks5代理,反向代理,静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子项目地址国内:https://gitee.com/tickbh/wmproxygithub:https://github.com/tickbh/wmproxy温......
  • 测试!芝麻代理效果怎么样?数据采集成功率?
    芝麻代理的风评有点两级分化了,有人说垃圾,也有人认为贵有贵的道理。别整这些有的没有的,我们今天就来测试一下,看看真相具体怎么样。HTTP代理的稳定性、匿名程度、响应速度、IP池可用率以及带宽这几个点,是保证我们的数据采集业务成功率,所以我们主要也是测试这些。1.配置我用 python ......