前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战代理模式,模拟Mybatis

实战代理模式,模拟Mybatis

作者头像
Yuyy
发布2022-09-21 10:06:42
2070
发布2022-09-21 10:06:42
举报
文章被收录于专栏:yuyy.info技术专栏

实战代理模式,模拟Mybatis

在使用mybatis操作数据库时,我们只需要定义一个接口,然后在xml里编写对应的sql,就能查询数据。其原理是Mybatis通过@mypperscan指定扫描的mapper接口路径,对mapper接口进行动态代理,生成的代理类通过解析xml得到对应sql,最终开发人员只需要调用接口就能执行sql了。

今天我们来实现一个简单的demo,利用动态代理,模拟mybatis查询数据。

代码实现

自定义注解

代码语言:javascript
复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
    // sql语句
    String value() default "";
}

DAO层接口使用注解绑定sql

代码语言:javascript
复制
public interface IUserDAO {
    @Select("'select user from user where userId = ' + #userId")
    String queryUser(String userId);
}

生产代理类bean的工厂bean

代码语言:javascript
复制
public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;

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

    @Override
    public T getObject() {
        InvocationHandler handler = (proxy, method, args) -> {
            final StandardEvaluationContext context = new StandardEvaluationContext();
            final Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                context.setVariable(parameters[i].getName(), args[i]);
            }

            final Select select = method.getAnnotation(Select.class);
            final SpelExpressionParser parser = new SpelExpressionParser();
            final String sql = parser.parseExpression(select.value())
                                     .getValue(context, String.class);
            System.out.println("执行SQL:" + sql);
            return "查询结果:xxx";
        };
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 由于需要定制化bean(DAO接口的动态代理类),这里定义了个工厂bean,来生产代理类bean。
  • getObjectType()反馈生产的bean的类型,使用构造函数透传被代理类,在mybatis中也是使用这样的方式进行透传。
  • 将参数名,参数值放入SpEL表达式的上下文里,再去解析出真正的sql
  • 通过反射获取的参数名不对
image-20211018115034088
image-20211018115034088
  • 解决方式一:javac编译时增加参数javac -parameters
  • 解决方式二:maven设置编译参数,重新编译项目即可
image-20211019110654094
image-20211019110654094
  • 成功获取到参数名
image-20211018141901678
image-20211018141901678

注册工厂bean的bean定义到容器

代码语言:javascript
复制
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        final GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        beanDefinition.setScope("singleton");
        beanDefinition.getConstructorArgumentValues()
                      .addGenericArgumentValue(IUserDAO.class);

        final BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDAO");
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

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

    }
}
  • 覆盖name为userDAO的bean,下面会定义个同名的bean

注入到IOC里

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-autowire="byName">

  <bean id="userDAO" class="com.yuyy.java.training.base.动态代理模拟Mybatis.RegisterBeanFactory"/>

</beans>
  • beanName同样设置为userDAO

测试

代码语言:javascript
复制
@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("动态代理模拟Mybatis/spring-config.xml");
    IUserDAO userDao = beanFactory.getBean("userDAO", IUserDAO.class);
    System.out.println(userDao.queryUser("100001"));
}

结果

代码语言:javascript
复制
执行SQL:select user from user where userId = 100001
查询结果:xxx

分析流程

解析spring配置文件

image-20211019094958274
image-20211019094958274
  • 读取到name为userDAO,class为com.yuyy.java.training.base.动态代理模拟Mybatis.RegisterBeanFactory的bean

运行bean的postProcessBeanDefinitionRegistry()方法

image-20211019094927763
image-20211019094927763
  • bean实现了bean定义注册后置处理接口BeanDefinitionRegistryPostProcessor
  • 在spring初始化核心方法refresh中的这个阶段
image-20211019093849406
image-20211019093849406
  • 将beanName为userDAO的bean定义,覆盖成我们写的工厂bean的bean定义
  • 将被代理类透传到工厂bean

创建工厂bean

image-20211019095135779
image-20211019095135779
  • 在refresh中的实例化所有非懒加载单例bean阶段
image-20211019095326859
image-20211019095326859

获取name为userDAO的bean

image-20211019095435079
image-20211019095435079
  • 指定需要的bean类型

通过工厂bean生产我们需要的对象

如果不是FactoryBean就直接返回bean,如果是,就通过工厂bean来生产需要的bean

image-20211018092325881
image-20211018092325881

得到工厂bean生产的动态代理类对象

image-20211019100339486
image-20211019100339486

根据提供的bean类型做类型转换 AbstractBeanFactory#doGetBean()

代码语言:javascript
复制
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
    try {
        return getTypeConverter().convertIfNecessary(bean, requiredType);
    }
    catch (TypeMismatchException ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Failed to convert bean '" + name + "' to required type '" +
                    ClassUtils.getQualifiedName(requiredType) + "'", ex);
        }
        throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    }
}    

运行代理类的增强方法

image-20211019100455891
image-20211019100455891
  • 通过lambda表达式传入的增强方法
image-20211019100916646
image-20211019100916646
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实战代理模式,模拟Mybatis
    • 代码实现
      • 自定义注解
      • DAO层接口使用注解绑定sql
      • 生产代理类bean的工厂bean
      • 注册工厂bean的bean定义到容器
      • 注入到IOC里
      • 测试
      • 结果
    • 分析流程
      • 解析spring配置文件
      • 运行bean的postProcessBeanDefinitionRegistry()方法
      • 创建工厂bean
      • 获取name为userDAO的bean
      • 通过工厂bean生产我们需要的对象
      • 运行代理类的增强方法
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档