想要理解 Spring IoC,先要知道如何擴展 Spring 自定義 Bean

我是風箏,公衆號「古時的風箏」,一個不僅有技術的技術公衆號,一個在程序圈混跡多年,主業 Java,另外 Python、React 也玩兒的 6 的斜槓開發者。 Spring Cloud 系列文章已經完成,能夠到 個人 github 上查看系列完整內容。也能夠在公衆號內回覆「pdf」獲取我精心製做的 pdf 版完整教程。java

今天這篇文章算是爲下一篇 Spring IoC 實現的文章作個預熱,Spring 最核心的部分就是控制反轉,而要被控制的對象就是各類各樣的 Bean。git

雖然如今大部分團隊都直接用 Spring Boot 了,不多有人用 Spring MVC 了,可是基礎仍是 Spring,只不過更多的是把 XML 配置改爲了註解形式。github

若是你用過 XML 配置的形式,那你知道 <context:component-scan><bean><aop:aspectj-autoproxy>這些標籤配置是怎麼實現的嗎,瞭解了這些,相信對你進一步認識 Spring 會有很大幫助。web

來吧,開始了!spring

Spring mvc 提供了擴展 xml 的機制,用來編寫自定義的 xml bean ,例如 dubbo 框架,就利用這個機制實現了好多的 dubbo bean,好比 <dubbo:application><dubbo:registry>等等,只要安裝這個標準的擴展方式實現配置便可。mvc

擴展自定義 bean 的意義何在

假設咱們要使用一個開源框架或者一套 API,咱們確定但願如下兩點:app

  1. 易用性,即配置簡單,要配置的地方越少越好框架

  2. 封裝性,調用簡單,也就是越高層封裝越好,少暴露底層實現dom

基於以上兩點,假設咱們要實現一個自定義功能,用現有的 Spring 配置項也能夠實現,但可能要配置的內容較多,並且還有可能要加入代碼輔助。致使邏輯分散,不便於維護。編輯器

因此咱們用擴展 Spring 配置的方式,將一些自定義的複雜功能封裝,實現配置最小化。

實現自定義擴展的步驟

本例只作簡單示範,功能簡單,即實現一個可配置參數的 Hacker bean,而後提供一個toString() 方法,輸入參數信息。 咱們最終實現的 bean 配置以下:

<kite:hacker id="hacker" name="moon" language="english" age="18" isHide="true"/>
複製代碼

用 Spring 自帶的配置作個比較,例如:

<context:component-scan base-package="com.ebanghu"></context:component-scan>
複製代碼

一、實現自定義 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();
    }
}
複製代碼

二、編寫 xsd schema 屬性描述文件,命名爲 hacker.xsd ,這裏把它放到項目 resources 目錄下的 META-INF 目錄中(位置能夠本身決定),能夠理解爲:這個文件就是對應剛剛建立的實體類做一個 xml 結構描述,內容以下:

<?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>
複製代碼

注意上面的

xmlns="http://code.fengzheng.com/schema/kite
複製代碼

targetNamespace="http://code.fengzheng.com/schema/kite"
複製代碼

一下子有地方要用到。

三、實現 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("執行 HackNamespaceHandler 的 init 方法");
        registerBeanDefinitionParser("hacker",new HackBeanDefinitionParser(Hacker.class));
        logger.info("註冊 「hacker」 定義轉換器成功");
    }
}
複製代碼

此類功能很是簡單,就是繼承 NamespaceHandlerSupport 類,並重載 init 方法,調用 registerBeanDefinitionParser 方法,其中第一個參數 hacker 便是咱們以後在 spring 配置文件中要使用的名稱,即kite:hacker 這裏的hacker; 第二個參數是下一步要說的。

四、實現 BeanDefinitionParser 類,這個類的做用簡單來講就是將第一步實現的類和 Spring xml中聲明的 bean 作關聯,實現屬性的注入,來看代碼:

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;
    }
}
複製代碼

此類實現自 BeanDefinitionParser,而且重載 parse 方法,parse 方法有兩個參數,第一個Element能夠理解爲 Spring xml 配置的 bean 的實體對應,經過 element.getAttribute 方法能夠獲取 配置的參數值,第二個參數 ParserContext ,能夠理解爲 Spring 提供的接口對象,經過它實現註冊 bean 的注入。 經過 RootBeanDefinition 實體對象的 getPropertyValues 方法可獲取自定義bean的屬性 kv 集合,而後像其中添加屬性值。 注意:kv 集合中的 key 並非實體類中的屬性名稱,而是屬性對應的 setter 方法的參數名稱,例如布爾型參數若是命名爲 is 開頭的,使用編輯器自動生成 setter 方法時,對應的 setter 方法的參數就會去掉 is ,並把後面的字符串作駝峯命名規則處理。固然了若是要規避的話,能夠本身寫 setter 方法。

五、註冊 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 路徑。

六、 在 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"/>
</beans>
複製代碼

注意前面引入了命名空間

xmlns:kite="http://code.fengzheng.com/schema/kite"
複製代碼

後面指定了 xsd 文件位置

http://code.fengzheng.com/schema/kite  
http://code.fengzheng.com/schema/kite/kite.xsd
複製代碼

七、測試

直接獲取配置文件的方式測試

public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
         Hacker hacker = (Hacker) ac.getBean("hacker");
         System.out.println(hacker.toString());
    }
複製代碼

使用 SpringJUnit4ClassRunner 測試

@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());
    }
}
複製代碼

測試結果如圖:

本文只是簡要說明實現步驟,具體負責操做可參考 dubbo ,代碼在 dubbo-config-spring 模塊中,固然也能夠閱讀 Spring 源碼,例如 查看 context:component-scan 的實現,在 spring-context-版本號 模塊中。

創做不易,小小的贊,大大的暖,快來溫暖我。不用客氣了,讚我!

我是風箏,公衆號「古時的風箏」,一個在程序圈混跡多年,主業 Java,另外 Python、React 也玩兒的很 6 的斜槓開發者。能夠在公衆號中加我好友,進羣裏小夥伴交流學習,好多大廠的同窗也在羣內呦。

相關文章
相關標籤/搜索