首頁(yè)技術(shù)文章正文

Spring xml方式整合第三方框架【Spring應(yīng)用詳解】

更新時(shí)間:2023-04-17 來(lái)源:黑馬程序員 瀏覽量:

IT培訓(xùn)班

xml整合第三方框架有兩種整合方案:

不需要自定義名空間,不需要使用Spring的配置文件配置第三方框架本身內(nèi)容,例如:MyBatis;需要引入第三方框架命名空間,需要使用Spring的配置文件配置第三方框架本身內(nèi)容,例如:Dubbo。
Spring整合MyBatis,之前已經(jīng)在Spring中簡(jiǎn)單的配置了SqlSessionFactory,但是這不是正規(guī)的整合方式, MyBatis提供了mybatis-spring.jar專(zhuān)門(mén)用于兩大框架的整合。
Spring整合MyBatis的步驟如下:
? 導(dǎo)入MyBatis整合Spring的相關(guān)坐標(biāo);(請(qǐng)見(jiàn)資料中的pom.xml)
? 編寫(xiě)Mapper和Mapper.xml;
? 配置SqlSessionFactoryBean和MapperScannerConfigurer;
? 編寫(xiě)測(cè)試代碼。

配置SqlSessionFactoryBean和MapperScannerConfigurer:

<!--配置數(shù)據(jù)源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>  <property name="username" value="root"></property>
  <property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper包掃描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="com.itheima.dao"></property>
</bean>

編寫(xiě)Mapper和Mapper.xml

public interface UserMapper { 
    List<User> findAll(); 
}
<?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">
<mapper namespace="com.itheima.dao.UserMapper">
    <select id="findAll" resulttype="com.itheima.pojo.User">
        select * from tb_user
    </select>
</mapper>

編寫(xiě)測(cè)試代碼

ClassPathxmlApplicationContext applicationContext =
            new ClassPathxmlApplicationContext("applicationContext.xml");
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
List<User> all = userMapper.findAll();
System.out.println(all);

Spring整合MyBatis的原理剖析

整合包里提供了一個(gè)SqlSessionFactoryBean和一個(gè)掃描Mapper的配置對(duì)象,SqlSessionFactoryBean一旦被實(shí)例化,就開(kāi)始掃描Mapper并通過(guò)動(dòng)態(tài)代理產(chǎn)生Mapper的實(shí)現(xiàn)類(lèi)存儲(chǔ)到Spring容器中。相關(guān)的有如下四個(gè)類(lèi):

? SqlSessionFactoryBean:需要進(jìn)行配置,用于提供SqlSessionFactory;

? MapperScannerConfigurer:需要進(jìn)行配置,用于掃描指定mapper注冊(cè)BeanDefinition;

? MapperFactoryBean:Mapper的FactoryBean,獲得指定Mapper時(shí)調(diào)用getObject方法;

? ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自動(dòng)注入狀態(tài),所以

? MapperFactoryBean中的setSqlSessionFactory會(huì)自動(dòng)注入進(jìn)去。

配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean實(shí)現(xiàn)了FactoryBean和InitializingBean兩個(gè)接口,所以會(huì)自動(dòng)執(zhí)行g(shù)etObject() 和afterPropertiesSet()方法。

SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
    public void afterPropertiesSet() throws Exception {
      //創(chuàng)建SqlSessionFactory對(duì)象
      this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
    public SqlSessionFactory getObject() throws Exception {
      return this.sqlSessionFactory;
    }
}

配置MapperScannerConfigurer作用是掃描Mapper,向容器中注冊(cè)Mapper對(duì)應(yīng)的MapperFactoryBean,MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor和InitializingBean兩個(gè)接口,會(huì)在postProcessBeanDefinitionRegistry方法中向容器中注冊(cè)MapperFactoryBean。

class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean{
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }
}
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
        } else {
          this.processBeanDefinitions(beanDefinitions);
        }
      }
      private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        //設(shè)置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
        definition.setBeanClass(this.mapperFactoryBeanClass);
        definition.setAutowireMode(2); //設(shè)置MapperBeanFactory 進(jìn)行自動(dòng)注入
      }
}

autowireMode取值:1是根據(jù)名稱(chēng)自動(dòng)裝配,2是根據(jù)類(lèi)型自動(dòng)裝配。

class ClassPathBeanDefinitionScanner{
    public int scan(String... basePackages) {
      this.doScan(basePackages);
    }
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
      //將掃描到的類(lèi)注冊(cè)到beanDefinitionMap中,此時(shí)beanClass是當(dāng)前類(lèi)全限定名
      this.registerBeanDefinition(definitionHolder, this.registry);
      return beanDefinitions;
    }
}
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    public MapperFactoryBean(Class<T> mapperInterface) {
      this.mapperInterface = mapperInterface;
    }
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
      this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
    }

    public T getObject() throws Exception {
      return this.getSqlSession().getMapper(this.mapperInterface);
    }
}

Spring 整合其他組件時(shí)就不像MyBatis這么簡(jiǎn)單了,例如Dubbo框架在于Spring進(jìn)行整合時(shí),要使用Dubbo提供的命名空間的擴(kuò)展方式,自定義了一些Dubbo的標(biāo)簽。

<?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:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd 
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!--配置應(yīng)用名稱(chēng)-->
    <dubbo:application name="dubbo1-consumer"/>
    <!--配置注冊(cè)中心地址-->
    <dubbo:registry address="zookeeper://localhost:2181"/>
    <!--掃描dubbo的注解-->
    <dubbo:annotation package="com.itheima.controller"/>
    <!--消費(fèi)者配置-->
    <dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>

為了降低我們此處的學(xué)習(xí)成本,不在引入Dubbo第三方框架了,以Spring的 context 命名空間去進(jìn)行講解,該方式也是命名空間擴(kuò)展方式。

需求:加載外部properties文件,將鍵值對(duì)存儲(chǔ)在Spring容器中。

jdbc.url=jdbc:mysql://localhost:3306/mybatis 
jdbc.username=root 
jdbc.password=root

引入context命名空間,在使用context命名空間的標(biāo)簽,使用SpEL表達(dá)式在xml或注解中根據(jù)key獲得value。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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.xsd
                            http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:jdbc.properties" />

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="url" value="${jdbc.url}"></property>
      <property name="username" value="${jdbc.username}"></property>
      <property name="password" value="${jdbc.password}"></property>
    </bean>
    
<beans>

其實(shí),加載的properties文件中的屬性最終通過(guò)Spring解析后會(huì)被存儲(chǔ)到了Spring容器的environment中去,不僅自己定義的屬性會(huì)進(jìn)行存儲(chǔ),Spring也會(huì)把環(huán)境相關(guān)的一些屬性進(jìn)行存儲(chǔ)。
1681726493712_企業(yè)微信截圖_16817264376863.png

原理剖析解析過(guò)程,只能從源頭ClassPathXmlApplicationContext入手,經(jīng)歷復(fù)雜的源碼追蹤,找到如下兩個(gè)點(diǎn):

1)在創(chuàng)建DefaultNamespaceHandlerResolver時(shí),為處理器映射地址handlerMappingsLocation屬性賦值,并加載命名空間處理器到MaphandlerMappings 中去。

this.handlerMappingsLocation = "META-INF/spring.handlers";

1681726712753_11.png

第一點(diǎn)完成后,Map集合handlerMappings就被填充了很多XxxNamespaceHandler,繼續(xù)往下追代碼

2)在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,發(fā)現(xiàn)如下邏輯:

//如果是默認(rèn)命名空間
if (delegate.isDefaultNamespace(ele)) {
  this.parseDefaultElement(ele, delegate);
//否則是自定義命名空間
} else {
  delegate.parseCustomElement(ele);
}

如果是默認(rèn)命名空間,則執(zhí)行parseDefaultElement方法

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
      this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
      this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
      this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
      this.doRegisterBeanDefinitions(ele);
    }
}

如果是自定義命名空間,則執(zhí)行parseCustomElement方法

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
   //解析命名空間
   String namespaceUri = this.getNamespaceURI(ele);
   //獲得命名空間解析器
   NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
   //解析執(zhí)行的標(biāo)簽
   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

在執(zhí)行resovle方法時(shí),就是從MaphandlerMappings中根據(jù)命名空間名稱(chēng)獲得對(duì)應(yīng)的處理器對(duì)象,此處是ContextNamespaceHandler,最終執(zhí)行NamespaceHandler的parse方法。

ContextNamespaceHandler源碼如下,間接實(shí)現(xiàn)了NamespaceHandler接口,初始化方法init會(huì)被自動(dòng)調(diào)用。由于context命名空間下有多個(gè)標(biāo)簽,所以每個(gè)標(biāo)簽又單獨(dú)注冊(cè)了對(duì)應(yīng)的解析器,注冊(cè)到了其父類(lèi)NamespaceHandlerSupport的Map<String, BeanDefinitionParser>

parsers中去了。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    public ContextNamespaceHandler() {
    }

    public void init() {
      this.registerBeanDefinitionParser("property-placeholder", new 
PropertyPlaceholderBeanDefinitionParser());
      this.registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
      this.registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
      this.registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
      this.registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
      this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
      this.registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
      this.registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

通過(guò)上述分析,我們清楚的了解了外部命名空間標(biāo)簽的執(zhí)行流程,如下:

將自定義標(biāo)簽的約束 與 物理約束文件與網(wǎng)絡(luò)約束名稱(chēng)的約束 以鍵值對(duì)形式存儲(chǔ)到一個(gè)spring.schemas文件里,該文件存儲(chǔ)在類(lèi)加載路徑的 META-INF里,Spring會(huì)自動(dòng)加載到;

將自定義命名空間的名稱(chēng) 與 自定義命名空間的處理器映射關(guān)系 以鍵值對(duì)形式存在到一個(gè)叫spring.handlers文件里,該文件存儲(chǔ)在類(lèi)加載路徑的 META-INF里,Spring會(huì)自動(dòng)加載到;

準(zhǔn)備好NamespaceHandler,如果命名空間只有一個(gè)標(biāo)簽,那么直接在parse方法中進(jìn)行解析即可,一般解析結(jié)果就是注冊(cè)該標(biāo)簽對(duì)應(yīng)的BeanDefinition。如果命名空間里有多個(gè)標(biāo)簽,那么可以在init方法中為每個(gè)標(biāo)簽都注冊(cè)一個(gè)BeanDefinitionParser,在執(zhí)行NamespaceHandler的parse方法時(shí)在分流給不同的BeanDefinitionParser進(jìn)行解析(重寫(xiě)doParse方法即可)。


分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!