Spring 最核心的部分就是控制反轉,而要被控制的對象就是各種各樣的 Bean。
雖然現在大部分團隊都直接用 Spring Boot 了,很少有人用 Spring MVC 了,但是基礎還是 Spring,只不過更多的是把 XML 配置改成了註解形式。
如果你用過 XML 配置的形式,那你知道 、 、 這些標籤配置是怎麼實現的嗎,瞭解了這些,相信對你進一步認識 Spring 會有很大幫助。
來吧,開始了!
Spring mvc 提供了擴展 xml 的機制,用來編寫自定義的 xml bean ,例如 dubbo 框架,就利用這個機制實現了好多的 dubbo bean,比如 、 等等,只要安裝這個標準的擴展方式實現配置即可。
擴展自定義 bean 的意義何在
假設我們要使用一個開源框架或者一套 API,我們肯定希望以下兩點:
1. 易用性,即配置簡單,要配置的地方越少越好
2. 封裝性,調用簡單,也就是越高層封裝越好,少暴露底層實現
基於以上兩點,假設我們要實現一個自定義功能,用現有的 Spring 配置項也可以實現,但可能要配置的內容較多,而且還有可能要加入代碼輔助。導致邏輯分散,不便於維護。
所以我們用擴展 Spring 配置的方式,將一些自定義的複雜功能封裝,實現配置最小化。
實現自定義擴展的步驟
本例只做簡單示範,功能簡單,即實現一個可配置參數的 Hacker bean,然後提供一個toString() 方法,輸入參數信息。 我們最終實現的 bean 配置如下:
用 Spring 自帶的配置做個比較,例如:
1、實現自定義 bean 類,命名為 Hacker ,並在方法中重載toString()方法,輸入屬性名稱,代碼如下:
<code>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(); } } /<code>
2、編寫 xsd schema 屬性描述文件,命名為 hacker.xsd ,這裡把它放到項目 resources 目錄下的 META-INF 目錄中(位置可以自己決定),可以理解為:這個文件就是對應剛剛創建的實體類作一個 xml 結構描述,內容如下:
<code> /<code>
注意上面的
xmlns="http://code.fengzheng.com/schema/kite
和
targetNamespace="http://code.fengzheng.com/schema/kite"
一會兒有地方要用到。
3、實現 NamespaceHandler 類,代碼如下:
<code>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("執行 HackNamespaceHandler 的 init 方法"); registerBeanDefinitionParser("hacker",new HackBeanDefinitionParser(Hacker.class)); logger.info("註冊 「hacker」 定義轉換器成功"); } } /<code>
此類功能非常簡單,就是繼承 NamespaceHandlerSupport 類,並重載 init 方法,調用
registerBeanDefinitionParser 方法,其中第一個參數 hacker 即是我們之後在 spring 配置文件中要使用的名稱,即kite:hacker 這裡的hacker; 第二個參數是下一步要說的。
4、實現 BeanDefinitionParser 類,這個類的作用簡單來說就是將第一步實現的類和 Spring xml中聲明的 bean 做關聯,實現屬性的注入,來看代碼:
<code>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("進入 HckBeanDefinitionParser 的 parse 方法"); try { String id = element.getAttribute("id"); RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(); rootBeanDefinition.setBeanClass(beanClass); rootBeanDefinition.setLazyInit(false); //必須註冊才可以實現注入 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; } } /<code>
此類實現自 BeanDefinitionParser,並且重載 parse 方法,parse 方法有兩個參數,第一個Element可以理解為 Spring xml 配置的 bean 的實體對應,通過 element.getAttribute 方法可以獲取 配置的參數值,第二個參數 ParserContext ,可以理解為 Spring 提供的接口對象,通過它實現註冊 bean 的注入。 通過 RootBeanDefinition 實體對象的 getPropertyValues 方法可獲取自定義bean的屬性 kv 集合,然後像其中添加屬性值。 注意:kv 集合中的 key 並不是實體類中的屬性名稱,而是屬性對應的 setter 方法的參數名稱,例如布爾型參數如果命名為 is 開頭的,使用編輯器自動生成 setter 方法時,對應的 setter 方法的參數就會去掉 is ,並把後面的字符串做駝峰命名規則處理。當然瞭如果要規避的話,可以自己寫 setter 方法。
5、註冊 handler 和 xsd schema Spring 規定了兩個 xml 註冊文件,並且規定這兩個文件必須項目資源目錄下的 META-INF 目錄中,並且文件名稱和格式要固定。
spring.handlers 用於註冊第三步實現的 Handler 類
內容如下:
http\://code.fengzheng.com/schema/kite=kite.lab.spring.config.HackNamespaceHandler
這是一個鍵值對形式,等號前面為命名空間,第一步已經提到,這裡就用到了,等號後面是 Handler 類的完全類名稱。注意冒號前面加轉義符
spring.schemas 用於註冊第二步中的 xsd 文件
內容如下:
http\://code.fengzheng.com/schema/kite/kite.xsd=META-INF/hacker.xsd
等號前面是聲明的 xsd 路徑,後面是實際的 xsd 路徑。
6、 在 Spring 配置文件中使用
<code> /<code>
注意前面引入了命名空間
xmlns:kite="http://code.fengzheng.com/schema/kite"
後面指定了 xsd 文件位置
<code>http://code.fengzheng.com/schema/kite http://code.fengzheng.com/schema/kite/kite.xsd/<code>
7、測試
直接獲取配置文件的方式測試
<code>public static void main(String[] args){ ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml"); Hacker hacker = (Hacker) ac.getBean("hacker"); System.out.println(hacker.toString()); } /<code>
使用 SpringJUnit4ClassRunner 測試
<code>@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()); } } /<code>
測試結果如圖:
感謝觀看,喜歡的話可以點個關注謝謝