Spring框架学习 -- Spring xml方式整合第三方框架

3.8 Spring xml方式整合第三方框架

3.8.1 Mybatis原始代码使用

1.配置Mybatis信息
创建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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 设置数据源类型。由于表格存储JDBC驱动需要主动关闭后才能让进程退出,请根据实际使用选择合适的数据源类型。-->
            <!-- 如果程序常驻执行,则您可以使用POOLED维护一个连接池;如果希望程序完成数据查询后退出,则只能使用UNPOOLED。-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_learn"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 设置为映射配置文件的路径。-->
        <package name="com.itheima.mapper"/>
    </mappers>
</configuration>

在MySQL是8.0以上的版本时,driver为com.mysql.cj.jdbc.Driver;在MySQL是5.0版本的时候driver是com.mysql.jdbc.driver。以及在MySQL是8.0以上的版本时URL有时是需要添加时区等参数的,否则无法建立和数据库的链接,此次测试不需要添加这些参数,原因未知,可能原因是mysql驱动或者mybatis版本是新的所以解决了这个问题。

2.创建实体类
根据数据库的结构创建一个实体类。
数据库的结构如图:
在这里插入图片描述
实体类:

package com.itheima.pojo;

public class User {
    private int id;
    private String username;
    private int age;
    private String address;

/*
	...
	getter和setter方法,toString方法
	...
*/
}

3.创建Mapper以及SQL语句
创建一个简单的Mapper接口:

public interface UserMapper {
    List<User> findAll();
}

根据Mapper接口创建一个mapper的xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace: mapper接口的以用路径-->
<mapper namespace="com.itheima.mapper.UserMapper">
<!--    id: mapper接口中使用的方法
        resultType: SQL返回的数据类型-->
    <select id="findAll" resultType="com.itheima.pojo.User">
        select * from USER_INFO
    </select>
</mapper>

4.编写测试类

package com.itheima.test;

import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
    public static void main(String[] args) throws IOException {
//        1.获取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//        2.根据mybatis配置文件创建sqlSessionFactory,并且获取一个sqlSession
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        3.根据sqlSession生成userMapper的对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//        4.执行userMapper中的方法并且将结果输出
        List<User> all = userMapper.findAll();
        for (User user : all){
            System.out.println(user.toString());
        }

    }
}

MyBaits原始的使用方法比较繁琐,并且一般情况下是不会这么使用的,所以不需要刻意记住。
执行结果:
在这里插入图片描述


3.8.2 Spring整合Mybatis

1.导入Mybatis整合Spring的相关坐标

       <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

2.编写Mapper和Mapper.xml
上个案例中已经写了对应的mapper以及mapper.xml,可以去上个案例查看。
3.配置SqlSessionFactoryBean和MapperScannerConfigurer

<!--    数据源-->
    <bean name="DataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_learn"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
<!-- SqlSessionFactory bean-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="DataSource"></property>
    </bean>
<!--    MapperScannerConfigurer 扫描指定包,自动生成Mapper对象存入Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itheima.mapper"></property>
    </bean>

SqlSessionFactoryBean需要数据源作为参数才可以创建。
4.编写测试代码
我们编写一个简单的UserService接口,它有一个简单的fun()方法,之后编写一个实现类UserServiceImpl实现这个接口。
UserServiceImpl中设置一个简单的Mapper属性,fun方法中使用Mapper中的方法实现数据库的查询功能。

public interface UserService {
    public void fun();
}
public class UserServiceImpl implements UserService {

    private UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void fun() {
        List<User> all = userMapper.findAll();
//        lambda表达式实现遍历
        all.forEach(
                (user) -> {
                    System.out.println(user.toString());
                }
        );
    }
}

之后在Spring的配置文件中添加对于这个Bean的配置信息:

    <bean name="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userMapper" ref="userMapper"></property>
    </bean>

我们在从头到尾的过程中都没有配置UserMapper相关的Bean,但是在对UserService进行属性创建的时候却可以将userMapper作为bean传入,这是因为MapperScannerConfigurer会自己遍历对应路径下的mapper类并且生成bean存入Spring容器之中。

编写测试方法,调用userService的bean:

public class MybatisSpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.fun();
    }
}

测试结果:
在这里插入图片描述


3.8.3 Spring整合MyBatis的原理解析

整合包里提供了SqlSessionFactoryBean和一个扫描Mapper的配置对象。SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并且通过动态代理产生Mapper的实现类存储到Spring容器之中。其中主要相关的包括一下四个类:
SqlSessionFactoryBean:需要配置,用于提供SqlSessionFactory;
MapperScannerConfigurer:需要进行配置,用于扫描指定的mapper注册BeanDefiniftion;
MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法(因为mybatis中mapper均是接口形式,想要生成对应的对象就要使用FactoryBean)
ClassPathMapperScanner:会将扫描到的Mapper Bean的注入状态是自动注入状态definition.setAutowireMode(2),所以MapperFactoryBean中的SqlSessionFactory会自动注入进去。

SqlSessionFactoryBean
SqlSessionFactoryBean在初始化阶段调用afterPropertiesSet()方法,创建SqlSessionFactory并且设置在属性中,创建的方法和原始的MyBatis代码相同,都是先创建SqlSessionFactoryBuilder之后使用SqlSessionFactoryBuilder创建SqlSessionFactory。

MapperScannerConfigurer的原理解析

Mapper扫描配置器。
我们在看Spring整合MyBatis时难免会有一个疑问:Mapper接口都是接口,不可以直接生成对象Bean,Spring容器是怎么生成的MapperBean呢?
答: 当Spring容器扫描到了Mapper文件,并且根据Mapper文件创建了BeanDefinition之后,会统一将Mapper的BeanDefinition文件中的BeanClass设置为MapperFactoryBean,并且将注入模式设置设置为自动注入。MapperFactoryBean是FactoryBean的子类,专门负责生成Mapper Bean。MapperFactoryBean将sqlSessionFactory作为属性,这个类是MyBatis原代码生成Mapper对象的工厂,之后Spring容器会自动将创建好的sqlSessionFactory Bean作为属性注入其中。之后在Spring容器的初始化阶段时,根据BeanDefinition创建实例时,会根据BeanDefinition将对应Mapper接口的Class传入作为构建参数,创建出这个Mapper专用的MapperFactoryBean,之后调用MapperFactoryBean的getObject方法,生成对应Mapper的代理bean。
MapperSessionFactoryBean的getObject方法:
在这里插入图片描述

MapperBean的创建过程:
在这里插入图片描述

3.8.4 Spring框架处理自定义命名空间原理解析

Spring框架处理XML中的自定义命名空间的步骤如下:

1. 获取XML中的自定义命名空间标签(element)
2. 根据元素自定义命名空间的URI获取自定义命名空间的解析器,使用获取到的解析器解析自定义的命名空间标签

1.获取XML中的自定义命名空间标签(element)
在Spring扫描完XML配置文件时,会将配置文件中的标签都获取到,并且依次解析(parse)这些标签。
这个获取XML中标签并且分析这些标签的过程处理代码如下:(此类为DefaultBeanDefinitionDocumentReader.java

/*类名: DefaultBeanDefinitionDocumentReader */
		...
		...
		...
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();

            for(int i = 0; i < nl.getLength(); ++i) {
                Node node = nl.item(i);//获取xml中的节点信息
                if (node instanceof Element) {
                	//如果节点类型为element则获取并且进行下一步处理
                    Element ele = (Element)node;
                    if (delegate.isDefaultNamespace(ele)) {
                    	/*如果element为默认的命名空间(import、alias、bean、beans),
                    	就使用默认命名空间解析的方式*/
                        this.parseDefaultElement(ele, delegate);
                    } else {
                    	/*如果element不是默认的命名空间,
                    	那么就启用自定义命名空间的处理方式*/
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }
    }
		...
		...
		...

2.根据元素自定义命名空间的URI获取自定义命名空间的解析器,使用获取到的解析器解析自定义的命名空间标签
Spring根据自定义命名空间的URI获取到此标签对应的解析器并且完成解析(parseDefaultElement())。
其代码实现如下(DefaultNamespaceHandlerResolver.java):

	...
    @Nullable
    public BeanDefinition parseCustomElement(Element ele) {
    	//调用自身类对应的parseCustomElement()方法
        return this.parseCustomElement(ele, (BeanDefinition)null);
    }

    @Nullable
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    	//获取标签对应的命名空间
        String namespaceUri = this.getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        } else {
        	//获取命名空间对应的处理器(NamespaceHandler)
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler == null) {
                this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                return null;
            } else {
            	//使用命名空间处理器来解析标签
                return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
            }
        }
    }
    ...

其中,一般而言自定义命名空间的jar包中会在META-INF/spring.handlers目录文件中存放命名空间URI对应的类(Spring会自动加载):
在这里插入图片描述
每个命名空间处理器(NamespaceHandler)都起码会有俩个方法:init()parse()
init()方法:来注册命名空间中每个标签的处理器
parse()方法:用来解析标签

3.8.5 案例:自制自定义命名空间

我们根据上述原理分析知道自定义一个命名空间需要以下几个条件:

1.在META-INF目录下的spirng.handlers以及spring.schemas文件中有命名空间的键值对
2.拥有对应的xsd约束文件
3.有对应的解析器handler
4.各个对应标签均有对应的解析器

那么我们就不难根据以上条件来自己创建一个自定标签。此标签做一个简单的功能:将一个自定的BeanPostProcessor注册入Spring容器。

1.创建META-INF目录,在其中创建spring.handlers以及spring.schemas文件,在其中填写好自定义命名空间对应的处理器以及对应的约束文件。
在这里插入图片描述
spring.handlers文件:
在这里插入图片描述
spring.schemas文件:
在这里插入图片描述
约束文件BugMakerAnno.xsd对应的内容:

<?xml version="1.0" encoding="UTF-8" ?>

<xsd:schema xmlns="http://www.itheima.com/BugMaker"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.itheima.com/BugMakerAnno">
    <xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>

targetNamespace属性必须为之前的自定义命名空间。

2.创建自定义命名空间对应的处理器类BugMakerAnnoHandler
BugMakerAnnoHandler.java:

package com.itheima.handlers;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class BugMakerAnnoHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        注册解析器
//		“annotation-driven”为自定义命名空间中的标签名称,后面的时标签对应的处理类
        this.registerBeanDefinitionParser("annotation-driven",new BugMakerAnnoParser());
    }
}

我们可以看到,这个类结构十分简单,仅仅是在init()方法中注册了标签对应的解析器。这是因为此类继承的父类NamespaceHandlerSupport类,这个类的parse()方法会根据传入的标签名称获取到已经在这个类中注册过的解析器,并且调用解析器的parse()方法进行解析
在这里插入图片描述
3.编写解析类
BugMakerAnnoParser类:

public class BugMakerAnnoParser implements BeanDefinitionParser {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //在Spring容器中注册一个容器
//        1.创建BeanDefinition
        BeanDefinition annoBeanDefinition = new RootBeanDefinition();
        annoBeanDefinition.setBeanClassName("com.itheima.postProcessor.AnnoBeanPostProcessor");
//        2.将BeanDefiniton注册入Spring容器之中
        parserContext.getRegistry().registerBeanDefinition("BugMakerAnnoBD",annoBeanDefinition);
        return annoBeanDefinition;
    }
}

BugMakerAnnoParser的代码逻辑即为创建一个BeanDefition,并且将创建的BeanDefition文件注册入Spring容器之中。
AnnoBeanPostProcessor(BeanPostProcessor类):

public class AnnoBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("自定义标签注入的BeanPostProcessor已经运行");
        return bean;
    }
}

AnnoBeanPostProcessor类的逻辑也很简单,只是在每个类实例化之前输入一个简单的日志。

4.在XML配置文件添加自定义命名空间以及对应的标签
在这里插入图片描述
5.运行项目
在这里插入图片描述

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值