更新時間:2023-04-17 來源:黑馬程序員 瀏覽量:

xml整合第三方框架有兩種整合方案:
不需要自定義名空間,不需要使用Spring的配置文件配置第三方框架本身內(nèi)容,例如:MyBatis;需要引入第三方框架命名空間,需要使用Spring的配置文件配置第三方框架本身內(nèi)容,例如:Dubbo。
Spring整合MyBatis,之前已經(jīng)在Spring中簡單的配置了SqlSessionFactory,但是這不是正規(guī)的整合方式, MyBatis提供了mybatis-spring.jar專門用于兩大框架的整合。
Spring整合MyBatis的步驟如下:
? 導入MyBatis整合Spring的相關(guān)坐標;(請見資料中的pom.xml)
? 編寫Mapper和Mapper.xml;
? 配置SqlSessionFactoryBean和MapperScannerConfigurer;
? 編寫測試代碼。
配置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>
編寫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>
編寫測試代碼
ClassPathxmlApplicationContext applicationContext =
new ClassPathxmlApplicationContext("applicationContext.xml");
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
List<User> all = userMapper.findAll();
System.out.println(all);Spring整合MyBatis的原理剖析
整合包里提供了一個SqlSessionFactoryBean和一個掃描Mapper的配置對象,SqlSessionFactoryBean一旦被實例化,就開始掃描Mapper并通過動態(tài)代理產(chǎn)生Mapper的實現(xiàn)類存儲到Spring容器中。相關(guān)的有如下四個類:
? SqlSessionFactoryBean:需要進行配置,用于提供SqlSessionFactory;
? MapperScannerConfigurer:需要進行配置,用于掃描指定mapper注冊BeanDefinition;
? MapperFactoryBean:Mapper的FactoryBean,獲得指定Mapper時調(diào)用getObject方法;
? ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自動注入狀態(tài),所以
? MapperFactoryBean中的setSqlSessionFactory會自動注入進去。
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean實現(xiàn)了FactoryBean和InitializingBean兩個接口,所以會自動執(zhí)行g(shù)etObject() 和afterPropertiesSet()方法。
SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
public void afterPropertiesSet() throws Exception {
//創(chuàng)建SqlSessionFactory對象
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
public SqlSessionFactory getObject() throws Exception {
return this.sqlSessionFactory;
}
}配置MapperScannerConfigurer作用是掃描Mapper,向容器中注冊Mapper對應(yīng)的MapperFactoryBean,MapperScannerConfigurer實現(xiàn)了BeanDefinitionRegistryPostProcessor和InitializingBean兩個接口,會在postProcessBeanDefinitionRegistry方法中向容器中注冊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 進行自動注入
}
}autowireMode取值:1是根據(jù)名稱自動裝配,2是根據(jù)類型自動裝配。
class ClassPathBeanDefinitionScanner{
public int scan(String... basePackages) {
this.doScan(basePackages);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//將掃描到的類注冊到beanDefinitionMap中,此時beanClass是當前類全限定名
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 整合其他組件時就不像MyBatis這么簡單了,例如Dubbo框架在于Spring進行整合時,要使用Dubbo提供的命名空間的擴展方式,自定義了一些Dubbo的標簽。
<?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)用名稱--> <dubbo:application name="dubbo1-consumer"/> <!--配置注冊中心地址--> <dubbo:registry address="zookeeper://localhost:2181"/> <!--掃描dubbo的注解--> <dubbo:annotation package="com.itheima.controller"/> <!--消費者配置--> <dubbo:consumer check="false" timeout="1000" retries="0"/> </beans>
為了降低我們此處的學習成本,不在引入Dubbo第三方框架了,以Spring的 context 命名空間去進行講解,該方式也是命名空間擴展方式。
需求:加載外部properties文件,將鍵值對存儲在Spring容器中。
jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=root
引入context命名空間,在使用context命名空間的標簽,使用SpEL表達式在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>其實,加載的properties文件中的屬性最終通過Spring解析后會被存儲到了Spring容器的environment中去,不僅自己定義的屬性會進行存儲,Spring也會把環(huán)境相關(guān)的一些屬性進行存儲。微信截圖_16817264376863.png)
原理剖析解析過程,只能從源頭ClassPathXmlApplicationContext入手,經(jīng)歷復(fù)雜的源碼追蹤,找到如下兩個點:
1)在創(chuàng)建DefaultNamespaceHandlerResolver時,為處理器映射地址handlerMappingsLocation屬性賦值,并加載命名空間處理器到MaphandlerMappings 中去。
this.handlerMappingsLocation = "META-INF/spring.handlers";

第一點完成后,Map集合handlerMappings就被填充了很多XxxNamespaceHandler,繼續(xù)往下追代碼
2)在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,發(fā)現(xiàn)如下邏輯:
//如果是默認命名空間
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
//否則是自定義命名空間
} else {
delegate.parseCustomElement(ele);
}如果是默認命名空間,則執(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í)行的標簽
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}在執(zhí)行resovle方法時,就是從MaphandlerMappings中根據(jù)命名空間名稱獲得對應(yīng)的處理器對象,此處是ContextNamespaceHandler,最終執(zhí)行NamespaceHandler的parse方法。
ContextNamespaceHandler源碼如下,間接實現(xiàn)了NamespaceHandler接口,初始化方法init會被自動調(diào)用。由于context命名空間下有多個標簽,所以每個標簽又單獨注冊了對應(yīng)的解析器,注冊到了其父類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());
}
}通過上述分析,我們清楚的了解了外部命名空間標簽的執(zhí)行流程,如下:
將自定義標簽的約束 與 物理約束文件與網(wǎng)絡(luò)約束名稱的約束 以鍵值對形式存儲到一個spring.schemas文件里,該文件存儲在類加載路徑的 META-INF里,Spring會自動加載到;
將自定義命名空間的名稱 與 自定義命名空間的處理器映射關(guān)系 以鍵值對形式存在到一個叫spring.handlers文件里,該文件存儲在類加載路徑的 META-INF里,Spring會自動加載到;
準備好NamespaceHandler,如果命名空間只有一個標簽,那么直接在parse方法中進行解析即可,一般解析結(jié)果就是注冊該標簽對應(yīng)的BeanDefinition。如果命名空間里有多個標簽,那么可以在init方法中為每個標簽都注冊一個BeanDefinitionParser,在執(zhí)行NamespaceHandler的parse方法時在分流給不同的BeanDefinitionParser進行解析(重寫doParse方法即可)。