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()方法,输入属性名称,代码如下:
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"?>
注意上面的
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"?>
注意前面引入了命名空间
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()); }}
测试结果如图:
感谢观看,喜欢的话可以点个关注谢谢