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("======================\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 ,这里把它放到项目 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  

  

Spring 实现自定义 bean 的扩展的更多相关文章

  1. Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战

    一. 前言 [APP 移动端]Spring Security OAuth2 手机短信验证码模式 [微信小程序]Spring Security OAuth2 微信授权模式 [管理系统]Spring Se ...

  2. Spring中自定义Schema扩展机制

    一.前言 Spring 为基于 XML 构建的应用提供了一种扩展机制,用于定义和配置 Bean. 它允许使用者编写自定义的 XML bean 解析器,并将解析器本身以及最终定义的 Bean 集成到 S ...

  3. spring扩展点之二:spring中关于bean初始化、销毁等使用汇总,ApplicationContextAware将ApplicationContext注入

    <spring扩展点之二:spring中关于bean初始化.销毁等使用汇总,ApplicationContextAware将ApplicationContext注入> <spring ...

  4. Spring Cloud 中自定义外部化扩展机制原理及实战

    Spring Cloud针对Environment的属性源功能做了增强, 在spring-cloud-contenxt这个包中,提供了PropertySourceLocator接口,用来实现属性文件加 ...

  5. Spring配置文件解析--bean属性

    1.bean设置别名,多个别名用逗号隔开 <!--使用alias--> <bean id="app:dataSource" class="...&quo ...

  6. spring schema自定义

    今天看了一下分布式服务框架的那本书,于是里面提到了spring schema的自定义,于是去简单的了解了一下 参考资源:spring schema扩展: http://www.yihaomen.com ...

  7. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  8. spring AOP自定义注解 实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  9. spring加载bean流程解析

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步.不过作为朝夕相处的框架,我们必须得明白一个问题就是sprin ...

随机推荐

  1. angular.js小知识总结

    angular-watch.html 代码如下: <script> var app = angular.module('app',[]); app.controller('ctrl',fu ...

  2. Kafka 客户端实现逻辑分析

    这里主要分析kafka 客户端实现 (代码分析以perl kafka实现为准) kafka客户端分为生产者和消费者,生产者发送消息,消费者获取消息. 在kafka协议里客户端通信中用到的最多的四个协议 ...

  3. WPF 杂谈——Trigger触发器

    笔者在使用的WPF过程中,见过的触发器有三种:Trigger.DataTrigger.EventTrigger.其中最为常用的要属Trigger.至于触发器的作用就是当某个属性的值发生变化,应该去做某 ...

  4. accp8.0转换教材第4章MySQL高级查询(二)理解与练习

    知识点:EXISTS子查询.NOT EXISTS子查询.分页查询.UNION联合查询 一.单词部分 ①exist存在②temp临时的③district区域 ④content内容⑤temporary暂时 ...

  5. 处理input标签的border-radius

    给input设置border-radius效果时一定要先设置border属性,否则会出现左上部有阴影的效果.

  6. peoplesoft function PSTREENODE 通过 deptid 获得部门树 全路径 名称

    create or replace function getUnitFullName(deptid in varchar) return varchar2 is r ); c int; n ); m ...

  7. JS运动框架的封装过程(一)

    给大家出一道题,从起点A走到目的地B,一共用了1000毫秒,每一次是30毫秒,请问你在这里面得到了哪些信息? 信息有哪些呢? 第一个,总时长是:1000毫秒 第二个,多久时间走一次?30毫秒 第三个, ...

  8. php七牛批量删除空间内的所有文件方法

    相信大家都在使用七牛的免费云存储服务的同时,有清空七牛云存储镜像文件或者批量删除七牛云空间文件的需求,该怎么做?官方的工具好像并没有提供批量删除的功能,七牛云官方给出的建议是删除空间,再新建一个这样太 ...

  9. 刨根究底字符编码之十四——UTF-16究竟是怎么编码的

    UTF-16究竟是怎么编码的 1. 首先要注意的是,代理Surrogate是专属于UTF-16编码方式的一种机制,UTF-8和UTF-32是不用代理的. 如前文所述,为了让UTF-16能继续编码基本平 ...

  10. 使用ant自动构建apk

    最近因渠道过多,需要单独接入渠道支付sdk的渠道也很多,而首发在即.人手不足,所以着手了部分相关的工作,看了下目前的操作流程..无奈人比较懒,所以决定进行一波简化的技术,先考虑到了两种方案: 1)脚本 ...