完全从零Java自学系列【入门篇】(第六课:Spring IOC&工厂设计模式&FactoryBean与框架集成)

本文介绍了如何使用Spring框架管理和集成Mybatis,通过控制反转和工厂设计模式简化对象依赖关系配置,涉及SqlSessionFactoryBean、MapperScannerConfigurer和MapperProxy等关键组件的源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概要

  在上一课的Mybatis框架使用中,我们通过sqlSession.getMapper(PopSingerMapper.class);这样的方式来获得我们配置的类。现在还有一些问题:

  1. 如果你做的是一个大型项目。Mapper这样的类会变得越来越多。
  2. 除了Mapper类以外还有非常非常多的其它类怎么办。
  3. 类与类之间的关系维护怎么办 (A依赖B依赖C依赖D依赖E 也就是 E是D的属性依次往前推),我们需要花非常多的时间来编写维护它们关系的代码。

上一篇中,我们通过直接加载mybatis的配置来完成了开发:在这里插入图片描述
今天我们将学习使用spring来管理mybatis中的类,以及它们之间的依赖关系。
在这里插入图片描述

Spring的基本原理

  我们前面的课程讲过反射,反射可以完成运行时对未知java类的加载与使用。它在Mybatis框架中通过反射与代理模式帮助我们封装了:管理数据库连接池,sql语句逻辑定义,请求参数与返回类型的处理。等等,这使得你的java代码变得更加纯粹。从而达到代码涉及到的都是关键内容的效果。
  Spring也是同理,它通过工厂设计模式反射的结合,来完成了它框架的基本意义-控制反转。反射是刚需,设计模式是为了让框架的代码变得优雅、可维护、可扩展等等。在下面的示例中,将初步的认识所谓的工厂设计模式,尽管你可能看完觉得它是没有什么意义的,但是没关系往下看。

控制反转

  Spring框架基于IOC (Inversion of Control, 控制反转)理念完成了该框架最基础的核心内容。它使得我们可以不再使用new关键字新建对象,或者主动的去编写维护类与类之间依赖关系的代码。而你需要做的事情就是做一些配置。

工厂设计模式

  简单的示例代码是无法完全证明设计模式的价值的,甚至你会觉得反而更复杂了,它只能帮助你理解。从某一个简单的切面来讲解它的意义与思想,而不是它的最终实现,你大可勇敢的提取它的核心意义去实践。
  下面我们示例简单工厂模式与在spring中的工厂模式与反射结合。

简单工厂:

  下面是简单工厂的生成类示例代码:

  1. 通过PopSinger定义了流行歌手的创建方法(或规范)
  2. JayChou与XuKunCai分别实现了自己的功能,当然在实际场景中生成代码不会如此的简单,还可以干很多别的啥事情。
  3. PopSingerFactory中生成具体的类之后,还可以将目标类做进一步处理再返回。
package lesson6;
public interface PopSinger {
    void singSong();
}
package lesson6;
public class XuKunCai implements PopSinger{
    @Override
    public void singSong() {
        System.out.println("鸡");
    }
}
package lesson6;
public class JayChou implements PopSinger{
    @Override
    public void singSong() {
    }
}

package lesson6;
public class PopSingerFactory {
    public static PopSinger popSinger(String type) {
        PopSinger popSinger = null;
        if(type.equals("jay")) {
            popSinger = new JayChou();
            /**
             * 完成其它处理比如代理,等等操作。
             */
        } else if (type.equals("xukun")) {
            popSinger = new XuKunCai();
            /**
             * 完成其它操作
             */
        }
        return popSinger;
    }
}

Spring 集成 Mybatis的使用

  根据我们前面使用Mybatis框架的经历,我们明白框架是为了通过配置从而省略很多繁琐的代码。Spring也是同理。Spring是用来管理你的所有java对象的,包括Mybatis中的对象。Spring的设计是高于Mybatis抽象的。Mybatis虽然代表着数据库操作,但归根结底都是一系列的java类。而Spring可以管理你项目中的一切Java类。

pom.xml的配置(在前面的课程中追加)

  如果只是需要Spring来帮助你生成你自己定义的类,其实spring-context就够了,并不需要其它依赖。
  spring-jdbc一方面是它封装了数据库操作和Mybatis类似(但很不一样),另外我们的数据库连接将从现在开始交由spring来管理,而不是Myabtis了。
  我们今天要讲解的核心内容除了spring-context以外还有spring是如何通过设计模式来集成其它框架的,也就是mybatis-spring依赖。

<!--ioc-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
<!--spring数据库-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis和spring集成依赖-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.3.1</version>
</dependency>

简化 mybatis-config.xml 配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 开启驼峰映射 否则无法解释userName到mysql的映射 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    <mappers>
        <!--加载sql映射文件-->
        <package name="mybatis"/>
    </mappers>
</configuration>

在resources下新增一个 jdbc.properties 文件

jdbc.url=jdbc:mysql://localhost:3306/edu_samuel?useSSL=false
jdbc.username=root
jdbc.password=123456789

在resources下新增一个 spring.xml 文件

  这里就是spring主要的配置内容了,在spring的世界里,一切都是bean(是spring进一步封装的java类)。

  1. 前面新增的jdbc.properties文件在此引用了。
  2. <bean>标签中id将会被作为spring的bean名称
  3. <bean>标签中class指定了类的全限定名
  4. <bean>标签中init-method指定了这个类初始化时执行的方法,destroy-method制定了销毁时的方法(请注意,是在spring生命周期中)
  5. <bean>标签中property则是定义了属性url等的初始属性值,value是用于指定基础类型的值,ref则是引用其它bean。
  6. property中${}引用了在前面新增的.properties文件中的内容。
  7. sqlSessionFactory这个bean的dataSource属性定义,通过ref指向了前面定义的dataSource。这里就是类的关系的管理了,通过这样的方式我们将不需要再java类中再处理。
  8. PopSingerServiceImpl这个bean是我自定义的类。
  9. 另外值得注意的是org.mybatis.spring前缀的类,它们代表了spring与mybatis的集成。我们在本篇后面的内容再展开。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url"
                  value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="20"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="mybatis"/>
    </bean>
    <bean id="PopSingerServiceImpl" class="lesson6.PopSingerServiceImpl">
        <property name="popSingerMapper" ref="popSingerMapper"/>
    </bean>
</beans>

新增PopSingerServiceImpl.java

  由于我们的类关系都交由spring管理的关系,和mybatis一样,在java中所有框架都一样。通过骆驼峰命名的方式,显示的设定get set属性的方法。在这里popSingerMapper是前面课程中的mybatis mapper类不变。从这里开始可以感觉到将不再手动的去管理myabtis的一切内容。

package lesson6;
import lesson5.PopSinger;
import mybatis.PopSingerMapper;
import java.util.List;
public class PopSingerServiceImpl {
    private PopSingerMapper popSingerMapper;
    public List<PopSinger> getSinger() {
        return popSingerMapper.select();
    }
    public PopSingerMapper getPopSingerMapper() {
        return popSingerMapper;
    }
    public void setPopSingerMapper(PopSingerMapper popSingerMapper) {
        this.popSingerMapper = popSingerMapper;
    }
}

新增测试类

  在这里,至少有一点和mybatis框架的使用是一样的,就是编写测试类的时候,仍然需要显示的去指定配置文件,加载它:ApplicationContext ac = new ClassPathXmlApplicationContext(in);。得到的ApplicationContext对象就是管理bean(java类)的上下文对象,它通过封装帮你生成类,并且维护关系包括集成其它框架,并且定义了额外的类生命周期。
  ac.getBean("PopSingerServiceImpl")对象获取到了我们自己定义的操作类,在该类中我们调用了mybatis的mapper操作。可以发现我们并没有对mapper有任何的初始化操作了,也不需要我们自己管理sqlsession等对象。

package lesson6;
import lesson5.PopSinger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class Test {
    public static void main(String[] args) throws InterruptedException {
        //读取spring主配置文件
        String in = "spring.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(in);
        PopSingerServiceImpl service = (PopSingerServiceImpl) ac.getBean("PopSingerServiceImpl");
        List<PopSinger> list = service.getSinger();
        System.out.println(list.size()+"行数据");
    }
}

Spring中的bean与FactoryBean

  前面的内容有了对spring的初步的了解,也知道了通过反射与工厂方法帮助我们新建了类,并封装成了bean,也知道了bean的生命周期中其中的init close(xml中配置的)。
  那么它是通过什么来集成mybatis的,因为毕竟SqlSession的各种构件在mybatis的框架内容中,我们现在需要将它配置到spring中去初始化。
  spring中除了使用自己的工厂方法帮助生成bean以外,还提供了FactoryBean让你自定义初始化过程,只要实现了该接口类:

package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    @Nullable
    T getObject() throws Exception;
    @Nullable
    Class<?> getObjectType();
    default boolean isSingleton() {
        return true;
    }
}

  也就是说通过这样的方式,我们则拥有了把其它框架中的类集成到spring中,让它在我们的spring环境下得到发挥。这是spring集成其它框架的基础能力。无论是我们的类,还是其他框架或依赖中的类,我们都有了重新定义它的能力。

SqlSessionFactoryBean

  所以回忆在spring配置文件中,我们已经定义了sqlsession到我们的spring中。无论是mybatis,spring还是这个依赖也好,都是开源社区提供的,这也是java的强大之处。也就是说如果没有他们,这个FactoryBean你将亲自手动实现。

<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

SqlSessionFactoryBean 源码(构建了SqlSession)

  在源码中,可以看到实现了两个关键的接口,FactoryBean,InitializingBean。前者是给该类提供工厂bean的能力。后者代表了生命周期中的初始化阶段要实现的能力。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean

  我们看看它的getObject()方法。如果读取的时候对象为空则调用初始化方法。(FactoryBean接口定义实现的)

public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }
        return this.sqlSessionFactory;
    }

  InitializingBean接口中定义的函数,它是生命周期中的一种。它将被spring自动的执行,spring在初始化时会扫描所有类中实现了该接口的类。并执行它。这个周期代表了初始时在属性设置后执行。

package org.springframework.beans.factory;
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

  SqlSessionFactoryBean中对它的实现。上面源码中getObject为空时调用的也是这个函数。但其实它在xml中定义后,spring扫描到实现了InitializingBean接口,则会在属性设置周期完成后自动的调用该函数。
在这里插入图片描述

MapperScannerConfigurer 源码(代理了mapper)

  通过上面的源码我们了解到如何将SqlSeesion创建到spring容器中交给spring管理,那么我们使用Mapper的时候,又是如何能让Spring帮我们管理使用Mapper时所需要的SqlSeesion相关操作的呢。
  分析问题的方式有很多种,直接看源码是一种,大胆的调试是另一种。我们可以通过调试发现我们当前Mapper类已经变成了MapperProxy,从名字上我们不难发现它已经不再是Mybatis中的Mapper定义了。通过pom中mybatis-spring的依赖中的MapperScannerConfigurer生成的。
在这里插入图片描述
  在MapperScannerConfigurer源码中它实现了BeanDefinitionRegistryPostProcessor接口,该接口也是spring生命周期的其中之一,它可以完成spring在类注册时对注册过程的干预。

//MapperScannerConfigurer:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor{
}
//BeanDefinitionRegistryPostProcessor:
package org.springframework.beans.factory.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

  我们来直接看MapperScannerConfigurer中postProcessBeanDefinitionRegistry的实现关键:

  1. 根据我们配置的basePackage来扫描 在这里插入图片描述
  2. 如果一步步跟进去会发现调用到了spring的源码中(看源码的时候一定要注意所在的包),在我框柱的部分是最终的核心,它去调用了Spring实现的代理,名词:作用域代理。具体目前为止你没有必要了解的特别深。:在这里插入图片描述

MapperProxy 源码(Mapper的具体代理实现)

  还记得前面的InvocationHandler接口吗,不记得了得回去看看了哦,在这里不赘述了。
  在源码中,invoke函数通过cachedInvoker函数来返回了一个接口,代表了多种不同的实现,
在这里插入图片描述

MapperFactoryBean 源码 (自定义加载Mapper到Spring中的SqlSession容器中)

  首先它依次继承了 MapperFactoryBean类 -> SqlSessionDaoSupport类 -> SqlSessionDaoSupport类 -> DaoSupport类 > InitializingBean接口。
在这里插入图片描述
  在spring加载MapperFactoryBean的时候,会调用afterPropertiesSet方法,因为它的父类DaoSupport实现了InitializingBean生命周期。
在这里插入图片描述
  所以这里checkDaoConfig会被默认执行,可以看到MapperFactoryBean构造函数接收了一个mapperInterface的Class对象,并将对象设置到。 在这里插入图片描述

补充

  在此节课程中,可能不会让你感觉到spring为你带来了多大的便利,主要是在配置的方式上,可能会让你觉得更加麻烦。但是没关系,咱们往后看,我会一步一步的告诉大家更简化的方法。
  从最基础的配置开始,往往可以让你更好的理解它们原始的样子和背后的基本逻辑,了解之后,咱们再来使用更多方便的手段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值