Spring 最核心的部分就是控制反轉(zhuǎn),而要被控制的對(duì)象就是各種各樣的 Bean。
雖然現(xiàn)在大部分團(tuán)隊(duì)都直接用 Spring Boot 了,很少有人用 Spring MVC 了,但是基礎(chǔ)還是 Spring,只不過更多的是把 XML 配置改成了注解形式。
如果你用過 XML 配置的形式,那你知道 <context:component-scan> 、 <bean> 、 <aop:aspectj-autoproxy> 這些標(biāo)簽配置是怎么實(shí)現(xiàn)的嗎,了解了這些,相信對(duì)你進(jìn)一步認(rèn)識(shí) Spring 會(huì)有很大幫助。
來吧,開始了!Spring mvc 提供了擴(kuò)展 xml 的機(jī)制,用來編寫自定義的 xml bean ,例如 dubbo 框架,就利用這個(gè)機(jī)制實(shí)現(xiàn)了好多的 dubbo bean,比如 <dubbo:application> 、 <dubbo:registry> 等等,只要安裝這個(gè)標(biāo)準(zhǔn)的擴(kuò)展方式實(shí)現(xiàn)配置即可。
擴(kuò)展自定義 bean 的意義何在假設(shè)我們要使用一個(gè)開源框架或者一套 API,我們肯定希望以下兩點(diǎn):
1. 易用性,即配置簡(jiǎn)單,要配置的地方越少越好2. 封裝性,調(diào)用簡(jiǎn)單,也就是越高層封裝越好,少暴露底層實(shí)現(xiàn)基于以上兩點(diǎn),假設(shè)我們要實(shí)現(xiàn)一個(gè)自定義功能,用現(xiàn)有的 Spring 配置項(xiàng)也可以實(shí)現(xiàn),但可能要配置的內(nèi)容較多,而且還有可能要加入代碼輔助。導(dǎo)致邏輯分散,不便于維護(hù)。
所以我們用擴(kuò)展 Spring 配置的方式,將一些自定義的復(fù)雜功能封裝,實(shí)現(xiàn)配置最小化。
實(shí)現(xiàn)自定義擴(kuò)展的步驟本例只做簡(jiǎn)單示范,功能簡(jiǎn)單,即實(shí)現(xiàn)一個(gè)可配置參數(shù)的 Hacker bean,然后提供一個(gè)toString() 方法,輸入?yún)?shù)信息。 我們最終實(shí)現(xiàn)的 bean 配置如下:
<kite:hacker id="hacker" name="moon" language="english" age="18" isHide="true"/>
用 Spring 自帶的配置做個(gè)比較,例如:
<context:component-scan base-package="com.ebanghu"></context:component-scan>
1、實(shí)現(xiàn)自定義 bean 類,命名為 Hacker ,并在方法中重載toString()方法,輸入屬性名稱,代碼如下:package kite.lab.spring.config;
/**
* Hacker
* @author fengzheng
*/
public class Hacker {
private String name;
private String age;
private String language;
private boolean isHide;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public boolean isHide() {
return isHide;
}
public void setHide(boolean hide) {
isHide = hide;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("======================\n");
builder.append(String.format("hacker's name is :%s \n", this.getName()));
builder.append(String.format("hacker's age is :%s \n", this.getAge()));
builder.append(String.format("hacker's language is :%s \n", this.getLanguage()));
builder.append(String.format("hacker's status is :%s \n", this.isHide()));
builder.append("======================\n");
return builder.toString();
}
}
2、編寫 xsd schema 屬性描述文件,命名為 hacker.xsd ,這里把它放到項(xiàng)目 resources 目錄下的 META-INF 目錄中(位置可以自己決定),可以理解為:這個(gè)文件就是對(duì)應(yīng)剛剛創(chuàng)建的實(shí)體類作一個(gè) xml 結(jié)構(gòu)描述,內(nèi)容如下:<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://code.fengzheng.com/schema/kite"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://code.fengzheng.com/schema/kite"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:complexType name="hackType">
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The name of hacker ]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="age" type="xsd:int" use="optional" default="0">
<xsd:annotation>
<xsd:documentation><![CDATA[ The age of hacker. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="language" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The language of hacker. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="isHide" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The status of hacker. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="hacker" type="hackType">
<xsd:annotation>
<xsd:documentation><![CDATA[ The hacker config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema></pre>
注意上面的
xmlns="http://code.fengzheng.com/schema/kite
和
targetNamespace="http://code.fengzheng.com/schema/kite"
一會(huì)兒有地方要用到。
3、實(shí)現(xiàn) NamespaceHandler 類,代碼如下:package kite.lab.spring.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* HackNamespaceHandler
* @author fengzheng
*/
public class HackNamespaceHandler extends NamespaceHandlerSupport {
private static final Logger logger = LoggerFactory.getLogger(HackNamespaceHandler.class);
@Override
public void init() {
logger.info("執(zhí)行 HackNamespaceHandler 的 init 方法");
registerBeanDefinitionParser("hacker",new HackBeanDefinitionParser(Hacker.class));
logger.info("注冊(cè) 「hacker」 定義轉(zhuǎn)換器成功");
}
}
此類功能非常簡(jiǎn)單,就是繼承 NamespaceHandlerSupport 類,并重載 init 方法,調(diào)用 registerBeanDefinitionParser 方法,其中第一個(gè)參數(shù) hacker 即是我們之后在 spring 配置文件中要使用的名稱,即kite:hacker 這里的hacker; 第二個(gè)參數(shù)是下一步要說的。
4、實(shí)現(xiàn) BeanDefinitionParser 類,這個(gè)類的作用簡(jiǎn)單來說就是將第一步實(shí)現(xiàn)的類和 Spring xml中聲明的 bean 做關(guān)聯(lián),實(shí)現(xiàn)屬性的注入,來看代碼:package kite.lab.spring.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* HackBeanDefinitionParser
*
* @author fengzheng
*/
public class HackBeanDefinitionParser implements BeanDefinitionParser {
private static final Logger logger = LoggerFactory.getLogger(HackBeanDefinitionParser.class);
private final Class<?> beanClass;
public HackBeanDefinitionParser(Class<?> beanClass) {
this.beanClass = beanClass;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
logger.info("進(jìn)入 HckBeanDefinitionParser 的 parse 方法");
try {
String id = element.getAttribute("id");
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(beanClass);
rootBeanDefinition.setLazyInit(false);
//必須注冊(cè)才可以實(shí)現(xiàn)注入
parserContext.getRegistry().registerBeanDefinition(id, rootBeanDefinition);
String name = element.getAttribute("name");
String age = element.getAttribute("age");
String language = element.getAttribute("language");
String isHide = element.getAttribute("isHide");
MutablePropertyValues pvs = rootBeanDefinition.getPropertyValues();
pvs.add("name", name);
pvs.add("age", Integer.valueOf(age));
pvs.add("language", language);
pvs.add("hide", isHide.equals(null) ? false : Boolean.valueOf(isHide));
return rootBeanDefinition;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
此類實(shí)現(xiàn)自 BeanDefinitionParser,并且重載 parse 方法,parse 方法有兩個(gè)參數(shù),第一個(gè)Element可以理解為 Spring xml 配置的 bean 的實(shí)體對(duì)應(yīng),通過 element.getAttribute 方法可以獲取 配置的參數(shù)值,第二個(gè)參數(shù) ParserContext ,可以理解為 Spring 提供的接口對(duì)象,通過它實(shí)現(xiàn)注冊(cè) bean 的注入。 通過 RootBeanDefinition 實(shí)體對(duì)象的 getPropertyValues 方法可獲取自定義bean的屬性 kv 集合,然后像其中添加屬性值。 注意:kv 集合中的 key 并不是實(shí)體類中的屬性名稱,而是屬性對(duì)應(yīng)的 setter 方法的參數(shù)名稱,例如布爾型參數(shù)如果命名為 is 開頭的,使用編輯器自動(dòng)生成 setter 方法時(shí),對(duì)應(yīng)的 setter 方法的參數(shù)就會(huì)去掉 is ,并把后面的字符串做駝峰命名規(guī)則處理。當(dāng)然了如果要規(guī)避的話,可以自己寫 setter 方法。
5、注冊(cè) handler 和 xsd schema Spring 規(guī)定了兩個(gè) xml 注冊(cè)文件,并且規(guī)定這兩個(gè)文件必須項(xiàng)目資源目錄下的 META-INF 目錄中,并且文件名稱和格式要固定。spring.handlers 用于注冊(cè)第三步實(shí)現(xiàn)的 Handler 類
內(nèi)容如下:
http\://code.fengzheng.com/schema/kite=kite.lab.spring.config.HackNamespaceHandler
這是一個(gè)鍵值對(duì)形式,等號(hào)前面為命名空間,第一步已經(jīng)提到,這里就用到了,等號(hào)后面是 Handler 類的完全類名稱。注意冒號(hào)前面加轉(zhuǎn)義符
spring.schemas 用于注冊(cè)第二步中的 xsd 文件
內(nèi)容如下:
http\://code.fengzheng.com/schema/kite/kite.xsd=META-INF/hacker.xsd
等號(hào)前面是聲明的 xsd 路徑,后面是實(shí)際的 xsd 路徑。
6、 在 Spring 配置文件中使用<?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:kite="http://code.fengzheng.com/schema/kite"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.fengzheng.com/schema/kite
http://code.fengzheng.com/schema/kite/kite.xsd">
<kite:hacker id="hacker" name="moon" language="english" age="18" isHide="true"/>
注意前面引入了命名空間
xmlns:kite="http://code.fengzheng.com/schema/kite"
后面指定了 xsd 文件位置
http://code.fengzheng.com/schema/kite
http://code.fengzheng.com/schema/kite/kite.xsd</pre>
7、測(cè)試直接獲取配置文件的方式測(cè)試
public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
Hacker hacker = (Hacker) ac.getBean("hacker");
System.out.println(hacker.toString());
}
使用 SpringJUnit4ClassRunner 測(cè)試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:application.xml" })
public class HackTest {
@Resource(name = "hacker")
private Hacker hacker;
@Test
public void propertyTest() {
System.out.println(hacker.toString());
}
}
測(cè)試結(jié)果如圖:
感謝觀看,喜歡的話可以點(diǎn)個(gè)關(guān)注謝謝