来梳理自己对spring扩展的简单理解.
从大的方面来看,我们可以扩展spring来让我们自己开发的框架融入她的体系中.小的方面,我们可以利用她开放的各种功能接口来自定义一些功能替代spring的默认值.我就从这俩方面来举几个例子来讲下spring扩展的特点.
那现在比如我们自己捣鼓了一个框架,想把她给用起来.然后发现,呀.我们很多系统都是基于spring做的,那么我们自己搞的框架兼容到spring里面啊.放不到spring里面,那我们不是白做了这么牛鼻哄哄的框架了嘛.
这里spring的那些专家或许也考虑过,我们开发的spring这么牛鼻,怎么让人家把自家的框架都兼容咱家的spring啊.哈哈,于是我们就看见现在的mybatis-spring,spring-data-redis等,她们都是通过spring自定义schemas实现的,也就是说只要你按照spring要求的做,保证你在spring的怀里舒舒服服的.
那我们怎么做了.首先,我们知道spring本质是一个bean容器.而bean就是spring定义的一个实体类BeanDefinition.所以,我们要做的就是把我们自定义框架的东西转化为spring里面的一个或多个bean.那想想我们平常是怎么做的?我们是不是要在xml里面各种的写出来,那同理,现在我们要做的也就是做一个xml让spring来把里面的内容解析成bean.
spring需要我们自定义三个文件,一个Spring.handlers,一个Spring.schemas.
,还有一个example.xsd(这个名字随意).我们看看这都写了啥.
http\://www.aaa.com/schema/example=com.ccc.MyNamespaceHandler
http\://www.aaa.com/schema/example.xsd=META-INF/example.xsd
<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.aaa.com/schema/example"
xmlns:tns="http://www.aaa.com/schema/example" elementFormDefault="qualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="user">
<xsd:complexType>
<xsd:attribute name="name" type="string"/>
xsd:complexType>
xsd:element>
xsd:schema>
我们自己先看一下,这三文件有啥关系,Spring.handlers好像定义了一个叫MyNamespaceHandler类的路径.Spring.schemas定义了example.xsd的路径.而example.xsd是啥了?她就是我们要拜托spring去解析的xml,大意我想都能看出去定义一个叫user的标签,她有个属性叫name.不过我们还看到xml上面有个
targetNamespace="http://www.aaa.com/schema/example"
xmlns:tns="http://www.aaa.com/schema/example"
这个也是前面俩文件等号左边的东西,是不是像我们每次在使用springxml的时候都要复制粘贴的一大段头部代码.
"http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
">
其实和我们的意图一样,也是告诉spring所需要的bean都在哪些xml里面.哈哈,是不是很有趣,我们也加了个www上去,nice.
好了,然后我们去看看自定义的MyNamespaceHandler
public class MyNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionParser("user", new AbstractSimpleBeanDefinitionParser (){
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
BeanDefinitionRegistry registry = parserContext.getRegistry();
User user = new User();
user.setName(name);
GenericBeanDefinition def = new GenericBeanDefinition();
def.setBeanClass(user.getClass());
def.setLazyInit(false);
parserContext.getRegistry().registerBeanDefinition("user", def);
}
});
}
}
其实也就是通过继承NamespaceHandlerSupport,然后注册了个bean的解析器,解析器会去解析xml,我们通过定义了一个GenericBeanDefinition成功将user融入spring里面.然后user这个bean就可以被我们平常那样随便使用了.
这就是成功创建一个bean标签的过程,以后我们就可以使用这样来定义个bean了.通过这样我们就可以将牛鼻哄哄的自家框架兼容到spring了,非常nice.
小的方面我们来说下spring里的通知机制.
比如用户注册功能,注册函数做完了需要像用户发一封欢迎邮件.如果我们直接在注册函数里面写,一方面不符合解耦合原则,另一方面可能发送功能报错了,导致注册失败就更糟了.所以,我们一般就会考虑用异步做,比如消息队列.但是其实spring自带了一种通知机制,非常的方便易用.通过applicationevent实现,我们来看看如何使用.
我们先定义一个listener用于监听MessageEvent事件的发布.
public class MessageEvent extends ApplicationEvent {
public MessageEvent(Object msg) {
super(msg);
}
}
public MessageEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(final ApplicationEvent event) {
if (event instanceof MessageEvent) {
MessageEvent msgEvent = (MessageEvent) event;
doSomething(msgEvent.getSource());
}
}
}
代码一目了然,获取到MessageEvent事件,然后处理事件中包含的消息.然后我们在具体的代码里面怎么做了.
@Autowired
private ApplicationEventPublisher eventPublisher;
public void register() {
doRegister();
this.eventPublisher.publishEvent(new MessageEvent(msg));
}
ApplicationEventPublisher是spring自身用于发布事件的bean,我们通过调用publishEvent来发布事件,这样就实现了很好的解耦.
那我们来看看,spring是如何让我们实现解耦的.
publishEvent是在AbstractApplicationContext里面实现的,里面关键代码是这样的
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
这句代码的意思获取事件广播器然后广播我们的事件,那么去看看事件广播器做了啥,我们进入到SimpleApplicationEventMulticaster里面看multicastEvent方法做了啥.
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
这里获取所有的监听器,然后判断是否定义了异步线程池,如果有,则放入线程池,这里推荐使用线程池异步调用,可以防止意外错误的发生影响主业务的进行.之后通过invokeListener一步步到最后的调用代码是
listener.onApplicationEvent(event);
看到没,这就是我们自定义事件监听器所重写的代码,完成我们定义的事件调用.
spring事件广播机制讲完了,我们总结下这里的扩展.首先,spring定义了一个总的ApplicationEventMulticaster然后自己做了一个简化版的实现,在我们发布事件的时候,通过筛选我们监听器,来回调onApplicationEvent方法.可以看到,spring在每一步都做了开放式设计,包括监听器,线程池,接口设计,来保证用户最大的可定制化.
好了,通过spring里面常用的俩个功能来说了下spring的扩展机制.总的来说学习一个框架或者一种方法的时候,我觉得如何设计才是最重要的.如上面我们所讲的,spring几乎在接口和抽象上面做的非常的好,我们既可以把spring看作一个空的架子,因为她给了我们足够开放的接口.又可以把spring当作一个开箱即用的框架,因为她提供了我们很多既定的默认实现.所以,当我学习spring的时候,我并没有去纠结spring的每一句代码,太多了,没有必要.我们需要知道的是她做了什么功能,大致的结构是什么.观其大略.只在我们对某个细节很困惑的时候才去深究一下,这也是我目前学习的一种方式.