实战代理模式,模拟Mybatis
本文最后更新于 1009 天前,其中的信息可能已经有所发展或是发生改变。

实战代理模式,模拟Mybatis

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

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

代码实现

自定义注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
    // sql语句
    String value() default "";
}

DAO层接口使用注解绑定sql

public interface IUserDAO {
    @Select("'select user from user where userId = ' + #userId")
    String queryUser(String userId);
}

生产代理类bean的工厂bean

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

    • 解决方式一:javac编译时增加参数javac -parameters

    • 解决方式二:maven设置编译参数,重新编译项目即可

    image-20211019110654094

    • 成功获取到参数名

    image-20211018141901678

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

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里

<?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

测试

@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"));
}

结果

执行SQL:select user from user where userId = 100001
查询结果:xxx

分析流程

解析spring配置文件

image-20211019094958274

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

运行bean的postProcessBeanDefinitionRegistry()方法

image-20211019094927763

  • bean实现了bean定义注册后置处理接口BeanDefinitionRegistryPostProcessor

  • 在spring初始化核心方法refresh中的这个阶段

    image-20211019093849406

  • 将beanName为userDAO的bean定义,覆盖成我们写的工厂bean的bean定义

  • 将被代理类透传到工厂bean

创建工厂bean

image-20211019095135779

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

    image-20211019095326859

获取name为userDAO的bean

image-20211019095435079

  • 指定需要的bean类型

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

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

    image-20211018092325881

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

    image-20211019100339486

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

    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

  • 通过lambda表达式传入的增强方法

    image-20211019100916646

作者:Yuyy
博客:https://yuyy.info
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇