• Spring 实现自定义 bean 的扩展


    Spring mvc 提供了扩展 xml 的机制,用来编写自定义的 xml bean ,例如 dubbo 框架,就利用这个机制实现了好多的 dubbo bean,比如 <dubbo:application> 、<dubbo:registry> 等等,只要安装这个标准的扩展方式实现配置即可。

    扩展自定义 bean 的意义何在

    假设我们要使用一个开源框架或者一套 API,我们肯定希望以下两点:

    1. 易用性,即配置简单,要配置的地方越少越好

    2. 封装性,调用简单,也就是越高层封装越好,少暴露底层实现

    基于以上两点,假设我们要实现一个自定义功能,用现有的 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>
    

    1、实现自定义 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("======================
    ");
            builder.append(String.format("hacker's name is :%s 
    ", this.getName()));
            builder.append(String.format("hacker's age is :%s 
    ", this.getAge()));
            builder.append(String.format("hacker's language is :%s 
    ", this.getLanguage()));
            builder.append(String.format("hacker's status is :%s 
    ", this.isHide()));
            builder.append("======================
    ");
            return builder.toString();
        }
    }
    

    2、编写 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"
    

    一会儿有地方要用到  

    3、实现 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;

    第二个参数是下一步要说的。

    4、实现 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 方法。

    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 配置文件中使用

    <?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 

    7、测试

    直接获取配置文件的方式测试

    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-版本号 模块中。


    古时的风筝 【微信公众号】gushidefengzheng  

      

  • 相关阅读:
    线程池
    自定义死锁
    不安全线程取钱
    JUC Lock实现类ReentrantLock使用说明
    同步方法跟同步方法块 synchronized
    线程的管程法跟信号灯法_生产者消费模式
    CopyOnWriteArrayList JUC当中安全容器
    inserttextatcursorinacontenteditablediv
    Android开发——NDK开发入门
    Linux下线程同步对象(1)——互斥量
  • 原文地址:https://www.cnblogs.com/fengzheng/p/7338262.html
Copyright © 2020-2023  润新知