当前位置: 首页 > news >正文

Spring扩展点之Mybatis整合模拟

Spring扩展点之Mybatis整合

  • 单独使用MyBaitis
  • 模拟整合MyBatis到Spring

单独使用MyBaitis

通过配置文件生成sqlSessionFactory,用sqlSessionFactory开启session。通过session获取到mapper执行对应的sql。

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
String result = userMapper.selectById();

sqlSession.commit();
sqlSession.flushStatements();
sqlSession.close();

模拟整合MyBatis到Spring

在Spring中我们可以通过如下方式使用mapper。通过分析我们知道userMapper是MyBatis生成的代理对象。但是在下面代码中的mapper并不是代理对象,并且运行还会报错,因为接口不会扫描成为bean。

public interface UserMapper {
    @Select("select 'user'")
    String selectById();
}

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void test() {
        System.out.println(userMapper.selectById());
    }
}

@ComponentScan("org.example")
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        UserService userService = context.getBean(UserService.class);
        userService.test();
    }
}

我们可以通过FactoryBean接口模拟代理对象的实现。

@Component
public class XianNuFactoryBean implements FactoryBean {
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 处理代理逻辑
                return null;
            }
        });
        return proxyInstance;
    }

    public Class<?> getObjectType() {
        return UserMapper.class;
    }
}

添加上述代码后,代码可以正常运行,可以userMapper可以注入成功。但是还有一个问题,这里的代理对象是写死的,我们不能每个mapper都写一个工厂bean吧。此时,我们可以想到利用spring的BeanDefinition把工厂bean注册到容器中。

public class XianNuFactoryBean implements FactoryBean {

    public Class clazz;
    public XianNuFactoryBean(Class clazz) {
        this.clazz = clazz;
    }

    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 处理代理逻辑
                return null;
            }
        });
        return proxyInstance;
    }

    public Class<?> getObjectType() {
        return clazz;
    }
}

工厂bean进行改造,增加构造器指定bean类型。

@Component
public class XianNuBeanDefinitionRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        //...
    }
}

接下来就顺利成章了,我们想要扫描用户所有的mapper然后自动注册到Spring容器中。利用@Import注解配合ImportBeanDefinitionRegistrar。@Import可以获取到所有类的元数据信息,如其他注解的扫描路径。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface XianNuMapperScan {
    String value();
}

@ComponentScan("org.example")
@XianNuMapperScan("org.example.mapper")
@Import(XianNuImportBeanDefinitionRegistrar.class)
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        UserService userService = context.getBean(UserService.class);
        userService.test();
    }
}

public class XianNuImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 解析扫描路径
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(XianNuMapperScan.class.getName());
        String path = (String) annotationAttributes.get("value");
        System.out.println(path);

        // 扫描路径下的bean?

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
    }
}

得到扫描路径后需要扫描路径下的类,我们可以利用spring的扫描器来处理。由于Spring本身的扫描器不处理接口,因此我们需要重新 isCandidateComponent(AnnotatedBeanDefinition beanDefinition)方法让他处理接口。此外扫描时还有一个exclude/includeFilters的判断,Spring默认添加component注解进行扫描,因此我们可以在mapper上加注解就能扫描到,此外还有一个方法就是在扫描器添加includeFilter。

public class XianNuBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public XianNuBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }
}

public class XianNuImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 解析扫描路径
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(XianNuMapperScan.class.getName());
        String path = (String) annotationAttributes.get("value");
        System.out.println(path);

        // 扫描路径下的bean
        XianNuBeanDefinitionScanner scanner = new XianNuBeanDefinitionScanner(beanDefinitionRegistry);
        scanner.addIncludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
        scanner.scan(path);

//        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
//        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
//        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
//        beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
    }

此时扫描出的mapper的BeanDefinition的class是它本身,并不是代理对象,因此还需要对beanDefinition做一些处理。重写scanner父类的doScan方法。

public class XianNuBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public XianNuBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    // 修改BeanDefinition的class为工厂类
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(XianNuFactoryBean.class.getName());
        }
        return beanDefinitionHolders;
    }

    // 让扫描器扫描接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }

此时还有一个问题就,就是mapper的代理逻辑是我们自己写的,不是用的MyBatis的。我们看mybatis的原始用法发现,mapper可以通过sqlSessionFactory得到,而sqlSessionFactory是通过配置文件得到。

public class XianNuFactoryBean implements FactoryBean {

    public Class clazz;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    public XianNuFactoryBean(Class clazz) {
        this.clazz = clazz;
    }

    public Object getObject() throws Exception {
//        Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                // 处理代理逻辑
//                System.out.println(method.getName());
//                return null;
//            }
//        });
//        return proxyInstance;
        sqlSessionFactory.getConfiguration().addMapper(clazz);
        return sqlSessionFactory.openSession().getMapper(clazz);
    }

    public Class<?> getObjectType() {
        return clazz;
    }
}

@ComponentScan("org.example")
@XianNuMapperScan("org.example.mapper")
@Import(XianNuImportBeanDefinitionRegistrar.class)
@Configuration
public class AppConfig {

    @Bean
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}

至此模拟整合完成。

相关文章:

  • 深入理解 JSP 与 Servlet:原理、交互及实战应用
  • Java计算机毕业设计基于SSM宠物美容信息管理系统数据库源代码+LW文档+开题报告+答辩稿+部署教程+代码讲解
  • HTML中,title和h1标签的区别是什么?
  • DNS, domain name system
  • 深搜专题2:组合问题
  • 第15天学习:类和对象的概念
  • ragflow-RAPTOR到底是什么?请通俗的解释!
  • 智能交通系统(Intelligent Transportation Systems):智慧城市中的交通革新
  • 【网络安全】常见的web攻击
  • 23. AI-大语言模型-DeepSeek简介
  • deepseek自动化代码生成
  • 《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成
  • [SQL] 事务的四大特性(ACID)
  • 设计心得——解耦的实现技术
  • C++/JavaScript ⭐算法OJ⭐用两个队列实现栈
  • Java-13
  • Kafka系列之:记录一次源头数据库刷数据,造成数据丢失的原因
  • Chrome 推出全新的 DOM API,彻底革新 DOM 操作!
  • 【MySQL】索引和视图
  • Starlink卫星动力学系统仿真建模第七讲-卫星姿轨控系统(Attitude and Orbit Control System, AOCS)设计规范
  • 谢震业领衔挑战世界顶尖高手,这场长三角田径钻石赛值得期待
  • 甘肃公布校园食品安全专项整治案例,有食堂涉腐败变质食物
  • 政府效率部效果不佳?马斯克有意寻求支持,含糊表态部门未来
  • 八成盈利,2024年沪市主板公司实现净利润4.35万亿元
  • 马上评丨准入壁垒越少,市场活力越足
  • 美的集团一季度净利增长38%,库卡中国机器人接单增超35%