前言

Spring 从 3.x 开始支持事件机制。在 Spring 的事件机制中,我们可以令一个事件类继承 ApplicationEvent 类,然后将实现了 ApplicationListenerBean 注册到 spring 容器,最后向 ApplicationEventPublisher 推送事件对象即可令所有订阅者收到事件。在 4.2 以后,甚至不需要实现 ApplicationListener 接口,仅需在 Bean 中方法标记 @EventListener 注解即可。

笔者将基于 Spring 源码的 5.2.x 分支,分析该功能是如何实现的。

本文是其中的第一篇文章,将分析广播器与监听的是如何被初始化,并完成注解流程的。

在开始前,推荐先阅读前文了解 Spring 容器的初始化过程与 BeanFactoryBean 的创建,如果可能,还可以了解一点 Spring 的注解机制,这将更有利于流程与一些代码的理解。

相关文章:

一、广播器的创建

在前文,我们知道容器的初始化是通过 AbstractApplicationContext.refresh() 方法完成的,事件机制的相关组件同样也离不开容器,因此事件系统的初始化也通过该方法完成。

AbstractApplicationContext.initApplicationEventMulticaster() 是第一步,它的作用很简单:

如果当前 BeanFactory 有名为 “applicationEventMulticaster”ApplicationEventMulticaster,就把它设置为当前上下文的事件广播器,否则就创建并在 BeanFactory 中注册一个SimpleApplicationEventMulticaster 实例作为当前上下文的事件广播器。

protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 是否存在“applicationEventMulticaster”这个Bean
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
// 如果存在就把它设置为当前上下文的事件广播器
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 没有就创建一个SimpleApplicationEventMulticaster作为当前上下文的事件广播器
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}

二、编程式监听器的注册

4.2 及以前版本,监听器需要显式的实现 ApplicationListener 接口,我们管这种监听器叫做编程式监听器。

编程式监听器在 AbstractApplicationContext.registerListeners() 这个方法的调用过程中被注册到注册广播器中,这一块代码逻辑也很简单:

  • 向事件广播器注册已经被注册的 BeanFactroy 中,且实现了 ApplicationListener 接口的监听器;
  • 向事件广播器注册还没有被实例化的监听器的 BeanName
  • 发布一些早期事件;
protected void registerListeners() {
// 向事件广播器注册已经被注册的上下文中的监听器
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
} // 向事件广播器注册指定的监听器,不过这里只注册BeanName,
// 因为有些监听器Bean是由FactoryBean生产的,而在这里FactoryBean实际上还没被生成出来
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
} // 发布一些早期事件
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

我们需要注意的是,在这一步,虽然向广播器注册了监听器,但是实际上这只是一种关系,真正的监听器实例不一定有被创建出来

不过在如果上下文中存在“早期事件”,则会触发广播,此时调用 ApplicationEventMulticaster.multicastEvent() 将会提前触发广播器中那些监听器的初始化,否则按正常情况这些将等到上下文主动初始化 BeanFactory 中全部非懒加载 Bean 的时候才会一并初始化。

三、注解式监听器的注册

4.2 版本以后,我们可以通过在成员方法上添加 @EventListener 或者 @TransactionalEventListener 注解的方法声明一个监听器,我们管这种监听器叫做注解式监听器。

实际上,由于注解式监听器的类上没有注解或接口作为标识,因此无法直接从 BeanFactory 中查找,所以它的注册显然不能与编程式监听器一样,在 AbstractApplicationContext.registerListeners() 通过从 BeanFactory 中直接找到然后注册。

3.0 以后支持的一些注解式配置的原理一样,@EventListener 是通过 EventListenerMethodProcessor 这个特殊的后置处理器完成注册的。

EventListenerMethodProcessor 实现的接口如下:

public class EventListenerMethodProcessor
implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
}

其中, SmartInitializingSingletonBeanFactoryPostProcessor 接口非常直观的告诉了我们它被调用的时机:

  • BeanFactoryPostProcessor:在上下文初始化的时候,通过 AbstractApplicationContext.invokeBeanFactoryPostProcessors 这个方法跟其他 BeanFactory 的后置处理器被一起集中调用;
  • SmartInitializingSingleton:在这个 Bean 完成初始化的时候;

接下来我们通过处理器分析注解式监听器的注册流程。

1、监听器方法处理器的注册

根据前文,我们知道容器在初始化过程中,通过 AbstarctApplicationContext.obtainFreshBeanFactory 创建新 BeanFactory 的时候,最终会一路绕到 AbstractRefreshableApplicationContext.loadBeanDefinitions 这个方法上,通过这个方法上下文会为自己的 BeanFactory 提前加载好 BeanDefinition

而这个抽象方法在不同的上下文会有不同的实现,但是基本都要通过不同的 BeanDefinitionReader 去完成这个过程。

支持注解式配置的上下文会用 AnnotatedBeanDefinitionReader 去读取配置的时候,会通过 AnnotationConfigBeanDefinitionParser 将配置信息解析为具体的 BeanDefinition 。而 Spring 就在这一步将默认配置的一些 BeanBeanDefinition 给加上了。

实际上,不止 EventListenerMethodProcessor ,几乎所有针对 Spring 注解的后置处理器都是通过这种方式注册到 BeanFactory 的。

具体代码参见 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 其他的一些注解处理器,比如 @Configuration,@Autowrite 之类的注解处理器... ... // 注册 EventListenerMethodProcessor
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
} // 注册名为“org.springframework.context.event.internalEventListenerFactory” EventListenerFactory
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
} return beanDefs;
}

2、获取监听器工厂

EventListenerMethodProcessor 被作为一个 BeanFactoryPostProcessor 被调用时,它会从 BeanFactory 中收集所有实现了 EventListenerFactory 接口的 Bean,然后记录在成员变量 eventListenerFactories 中:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
List<EventListenerFactory> factories = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(factories);
this.eventListenerFactories = factories;
}

而监听器工厂这个类作用也显而易见,他用于把被注解的方法适配为监听器对象:

public interface EventListenerFactory {
// 是否支持处理该方法
boolean supportsMethod(Method method);
// 将bean中带有@EventListener注解的方法转为ApplicationListener
ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method);
}

值得一提的是,由于注册 EventListenerMethodProcessor 的时候也会默认支持一个名为 :"org.springframework.context.event.internalEventListenerFactory"DefaultEventListenerFactory,这保证至少有一个保底的监听器工厂。

EventListenerFactory 提供两个默认的实现:

  • DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodAdapter 的监听器;

  • TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodTransactionalAdapter 的监听器;

3、将方法适配为监听器

EventListenerMethodProcessor 作为一个 SmartInitializingSingleton 被调用的时候:

public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
// 获取工厂中的全部 Bean
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
Class<?> type = null;
try {
// 如果是代理对象,则获取原始对象的类型
type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (type != null) {
// 实现了ScopedObject接口
if (ScopedObject.class.isAssignableFrom(type)) {
try {
// 获取原始的Bean对象
Class<?> targetClass = AutoProxyUtils.determineTargetClass(
beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
if (targetClass != null) {
type = targetClass;
}
}
catch (Throwable ex) {
// An invalid scoped proxy arrangement - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
}
}
}
try {
// 处理 Bean
processBean(beanName, type);
}
catch (Throwable ex) {
throw new BeanInitializationException("Failed to process @EventListener " +
"annotation on bean with name '" + beanName + "'", ex);
}
}
}
}
}

抛开对代理对象的一些检验和处理,我们直接看看 processBean 方法:

private void processBean(final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType) &&
// targetType类名不以“java.”开头,且不为Ordered接口
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
// 是未被@Component注解的Spring内部类
!isSpringContainerClass(targetType)) { Map<Method, EventListener> annotatedMethods = null;
try {
// 查找直接或间接带有@EventListener注解的方法
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
} // 如果该类没有直接或间接带有@EventListener注解的方法,则记录并在下次查询时跳过
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
}
else {
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
Assert.state(factories != null, "EventListenerFactory List not initialized");
// 遍历注解方法,并遍历监听器工厂
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
// 若工厂支持处理
if (factory.supportsMethod(method)) {
// 将方法包装为ApplicationListener
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
// 如果监听器类型为ApplicationListenerMethodAdapter,则需要传入专门的SpEL表达式解析器EventExpressionEvaluator用于支持@EventListener.condition属性
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 将监听器加入中的
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
} @Override
public void addApplicationListener(ApplicationListener<?> listener) {
Assert.notNull(listener, "ApplicationListener must not be null");
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
this.applicationListeners.add(listener);
}

Spring 在这一步主要趁着 EventListenerMethodProcessorBeanFactory 中初始化的时候干了两件事:

  • 检查 BeanFactory 中的所有的 Bean,筛选出其中有成员方法直接或间接带有 @EventListener 注解的 Bean
  • 将此类 Bean 的方法通过 EventListenerFactory 封装为 ApplicationListener 对象;
  • 然后将这些转换后得到的 ApplicationListener 注册到上下文中的广播器中;

此外,这里有一个比较有意思的细节,就是由于 @EventListener 注解是支持在 condition 中通过 SpEL 表达式进行一些判断的,因此在这一步,针对默认的监听适配器实现 ApplicationListenerMethodAdapter ,提供了一个 init 方法用于把 SpEL 表达式解析器塞进去:

if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}

换而言之,如果我们希望让 @EventListener.condition 支持更多功能,就可以在这个地方动点手脚,比如向 SpEL 表达式上下文注册更多变量。

4、监听器的注册

上一节中提到,在 EventListenerMethodProcessor.processBean 将方法转换为 ApplicationListener 后会将其注入广播器:

public void addApplicationListener(ApplicationListener<?> listener) {
Assert.notNull(listener, "ApplicationListener must not be null");
if (this.applicationEventMulticaster != null) {
// 注册到上下文中的广播器中
this.applicationEventMulticaster.addApplicationListener(listener);
}
this.applicationListeners.add(listener);
}

AbstractApplicationContext 会将该方法代理到内部持有的广播器实例的 ApplicationEventMulticaster.addApplicationListener 方法:

public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.defaultRetriever) {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same listener.
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}

该方法最终将监听器添加到广播器持有的 DefaultListenerRetriever 对象实例中,跟已经注册到其中的编程式监听器一起,以待后续使用。

四、监听器工厂

通过上文,我们知道注解式监听器依赖监听器工厂 EventListenerFactoryBean 中的注解方法转为 ApplicationListener 实例。

实际上,我们知道 spring 除了支持 @EventListener 注解外,还提供了 @TransactionalEventListener 注解,用于注册支持事务的注解式监听器,因此 EventListenerFactory 实际上也提供了两类工厂分别用于支持这两种实现:

  • DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodAdapter 的监听器;

  • TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodTransactionalAdapter 的监听器;

1、通用监听器工厂

通用监听器工厂的代码及其简单,它的特点如下:

  • 支持处理任何方法:supportsMethod 方法固定返回 true
  • 总是最晚被执行:getOrder 默认返回 Ordered.LOWEST_PRECEDENCE
  • 总是将注解方法适配为 ApplicationListenerMethodAdapter 类型的监听器;
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {

    private int order = LOWEST_PRECEDENCE;

    public void setOrder(int order) {
this.order = order;
} @Override
public int getOrder() {
return this.order;
} @Override
public boolean supportsMethod(Method method) {
return true;
} @Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodAdapter(beanName, type, method);
} }

2、事务监听器工厂

事件监听器工厂代码也并不复杂,相比 DefaultEventListenerFactory,它的特点如下:

  • 默认比 DefaultEventListenerFactory 更先被调用:getOrder 默认返回 50,比 DefaultEventListenerFactory 返回的 Ordered.LOWEST_PRECEDENCE 值更小;
  • 仅支持处理直接或间接被 @TransactionalEventListener 注解的方法;
  • 总是将注解方法适配为 ApplicationListenerMethodTransactionalAdapter 类型的监听器;
public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {

    private int order = 50;

    public void setOrder(int order) {
this.order = order;
} @Override
public int getOrder() {
return this.order;
} @Override
public boolean supportsMethod(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);
} @Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
} }

总结

当 Spring 容器启动,上下文调用 AbstractApplicationContext.refresh 方法对其进行初始化时,Spring 事件机制的两个核心组件:广播器、监听器也在该过程完成初始化。

  • AbstractApplicationContext.initApplicationEventMulticaster 这一步,Spring 为上下文创建并挂载了广播器 ApplicationEventMulticaster 的实例;

  • AbstractApplicationContext.registerListeners 这一步,Spring 将容器中所有实现了 ApplicationListener 接口的 Bean 注册到广播器中;

  • 而在 AbstractApplicationContext.finishBeanFactoryInitialization 这一步,Spring 会初始化容器中所有非懒加载的 Bean,此时会一并实例化 EventListenerMethodProcessor 这个 Bean 后置处理器:

    1. 这个 Bean 由上下文在准备 BeanFactory 时,调用 loadBeanDefinitions 方法时注册到容器;

    2. 由于 EventListenerMethodProcessor 本身实现了 SmartInitializingSingleton 接口,因此实例化后会触发其回调函数,此时 EventListenerMethodProcessor 会把容器中所有的 Bean 都拿出来解析;

    3. Bean 中存在直接或间接被 @EventListener 注解的方法,则会将其取出,并通过 EventListenerFactory 将其适配为对应的 ApplicationListener 实例,然后再将适配完的监听器注册到容器中;

    4. 由于 Spring 除了 @EventListener 注解还提供了支持事务的@TransactionalEventListener 注解,因此提供了两类监听器工厂:

      DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法;

      TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法;

至此,当 AbstractApplicationContext.refresh 执行完毕,即上下文初始化完成后,广播器与所有编程式或注解式监听器皆初始化完毕,并且完成了注册。

深入理解Spring事件机制(一):广播器与监听器的初始化的更多相关文章

  1. Spring事件机制详解

    一.前言 说来惭愧,对应Spring事件机制之前只知道实现 ApplicationListener 接口,就可以基于Spring自带的事件做一些事情(如ContextRefreshedEvent),但 ...

  2. 搞清楚Spring事件机制后:Spring的源码看起来简单多了

    本文主讲Spring的事件机制,意图说清楚: 什么是观察者模式? 自己实现事件驱动编程,对标Spring的事件机制 彻底搞懂Spring中的事件机制,从而让大家 本文内容较长,代码干货较多,建议收藏后 ...

  3. spring事件机制

    前置知识补充: 程序里面所谓的“上下文”就是程序的执行环境,打个比方:你就相当于web程序,你的房子就相当于web程序的上下文,你可以在家里放东西,也可以取东西,你的衣食住行都依赖这个房子,这个房子就 ...

  4. 观察者模式之spring事件机制

    ddsspring中的事件机制使用到设计模式中的观察者模式 ,观察者模式有两个概念,1.观察者.被观察者.2.被观察者做出相应得动作,观察者能接收到.不分析设计模式,学习下spring中的事件机制实际 ...

  5. 深入理解DOM事件机制系列第四篇——事件模拟

    × 目录 [1]引入 [2]模拟机制 [3]自定义事件 前面的话 事件是网页中某个特别的瞬间,经常由用户操作或通过其他浏览器功能来触发.但实际上,也可以使用javascript在任意时刻来触发特定的事 ...

  6. Spring 事件机制

    通过模拟邮件的发送,说明Spring的事件监听机制 事件类 package org.zln.module_chapter2.event; import org.springframework.cont ...

  7. 深入理解DOM事件机制系列第三篇——事件对象

    × 目录 [1]获取 [2]事件类型 [3]事件目标[4]事件代理[5]事件冒泡[6]事件流[7]默认行为 前面的话 在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事 ...

  8. 深入理解DOM事件机制系列第二篇——事件处理程序

    × 目录 [1]HTML [2]DOM0级 [3]DOM2级[4]IE[5]总结 前面的话 事件处理程序又叫事件侦听器,实际上就是事件的绑定函数.事件发生时会执行函数中相应代码.事件处理程序有HTML ...

  9. 深入理解DOM事件机制系列第一篇——事件流

    × 目录 [1]历史 [2]事件冒泡 [3]事件捕获[4]事件流 前面的话 javascript操作CSS称为脚本化CSS,而javascript与HTML的交互是通过事件实现的.事件就是文档或浏览器 ...

  10. 深入理解Spring的容器内事件发布监听机制

    目录 1. 什么是事件监听机制 2. JDK中对事件监听机制的支持 2.1 基于JDK实现对任务执行结果的监听 3.Spring容器对事件监听机制的支持 3.1 基于Spring实现对任务执行结果的监 ...

随机推荐

  1. 使用maven搭建SpringMVC项目环境

    Window环境下用maven新建一个项目: mvn archetype:generate -DarchetypeCatalog=internal -DgroupId=cn-cisol -Dartif ...

  2. WebMidiLink

    g200kg > WebMidiLink > 1.Introduction WebMidiLink 2012/06/26 1.Introduction « Prev 1.Introduct ...

  3. ALV的颜色分为行的颜色、列的颜色和CELL的颜色

    ALV的颜色分为行的颜色.列的颜色和CELL的颜色.任务要求,将一定的Tabellenfeld 用黄色填充,也就是说CELL的颜色 DATA:ls_cellcolorTYPElvc_s_scol,co ...

  4. surface RT app安装心得

    打开store,然后在键盘输入字母,就出现搜索栏了. 想安装qq,但是输入后找不到软件,原因是我在初始化系统的时候,我的所在地选择的是新加坡,因此找不到软件.在屏幕右下方的setting,然后将所在地 ...

  5. JQuery Datatables(一)

    最近项目中用了Bootstrap的样式风格,控件用了JQuery Datatables,主要有几下几点目标: 实现BootStrap风格的table,使用Ajax获取数据,并有勾选项 可以实现全选,单 ...

  6. Jquery:Jquery中的事件<一>

    由于今天有一个比较重要的面试,所以昨天晚上对以前做的一些项目做了一下总结,直接导致昨天的学习笔记断更了,哎,计划永远赶不上变化啊!今天学习了Jquery中是事件,就此做一个笔记,便于日后复习. 一.加 ...

  7. Zabbix 中文使用手册

    一.Zabbix简介 详情参考"企业监控利器-zabbix"http://waringid.blog.51cto.com/65148/904201. 二.Zabbix使用 2.1 ...

  8. JS画几何图形之五【过圆外一点作切线】

    样例:http://www.zhaojz.com.cn/demo/draw9.html 依赖:[点].[直线].[圆] //画切线 //point 圆外的一点 //dot 圆心 //r 半径 func ...

  9. (八)CXF添加自定义拦截器

    前面我们说到CXF添加内置的拦截器,今天的话,我们来讲下如何添加自定义拦截器: 我们的实例是客户端访问服务端webservice接口要加权限认证. 我们思路先说下.我们可以通过在SOAP消息的Head ...

  10. poj 1631 最多能有多少条不交叉的线 最大非降子序列 (LIS)

    左边的数字是1 2 3 4 5.... 右边的数字 第一个输入的和1连 第2个输入的和2连 右边再按从小到大排序 要求连线不能交叉 问最多能有多少条不交叉的线 假如右边有5个1 那么答案会是5 所以是 ...