我是风筝,公众号「古时的风筝」,一个不仅有技术的技术公众号,一个在程序圈混迹多年,主业 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
假设咱们要使用一个开源框架或者一套 API,咱们确定但愿如下两点:app
易用性,即配置简单,要配置的地方越少越好框架
封装性,调用简单,也就是越高层封装越好,少暴露底层实现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 的斜杠开发者。能够在公众号中加我好友,进群里小伙伴交流学习,好多大厂的同窗也在群内呦。