前言

解耦,是我们一直以来都很重视的事情。而事件,就是我们实现解耦的一大利器。通过观察者模式,我们可以实现事件的发布和订阅之间的解耦。而且在日常开发中,事件的使用场景也是很常见的。比如用户注册完成,可以产生一个事件,以便后续进行操作。或者用户下单,也可以产生一个事件,然后各种监听器对下单这个事件进行自己的处理。这些都是事件机制的使用案例。

这次,我们就来实现Spring中的事件。我们使用Spring框架之后,就可以使用Spring给我们提供的事件发布机制,不需要我们自己去实现了。而且Spring本身也会将自己的一些行为作为事件广播出去,我们可以通过注册相应的监听器,来接收这些事件,从而进行一些自己的处理。

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              ├─beans
│  │  │              │  ├─exception
│  │  │              │  │      BeanException.java
│  │  │              │  │
│  │  │              │  └─factory
│  │  │              │      │  Aware.java
│  │  │              │      │  BeanClassLoaderAware.java
│  │  │              │      │  BeanFactory.java
│  │  │              │      │  BeanFactoryAware.java
│  │  │              │      │  BeanNameAware.java
│  │  │              │      │  ConfigurableListableBeanFactory.java
│  │  │              │      │  DisposableBean.java
│  │  │              │      │  FactoryBean.java
│  │  │              │      │  HierarchicalBeanFactory.java
│  │  │              │      │  InitializingBean.java
│  │  │              │      │  ListableBeanFactory.java
│  │  │              │      │
│  │  │              │      ├─config
│  │  │              │      │      AutowireCapableBeanFactory.java
│  │  │              │      │      BeanDefinition.java
│  │  │              │      │      BeanDefinitionRegistryPostProcessor.java
│  │  │              │      │      BeanFactoryPostProcessor.java
│  │  │              │      │      BeanPostProcessor.java
│  │  │              │      │      BeanReference.java
│  │  │              │      │      ConfigurableBeanFactory.java
│  │  │              │      │      DefaultSingletonBeanRegistry.java
│  │  │              │      │      PropertyValue.java
│  │  │              │      │      PropertyValues.java
│  │  │              │      │      SingletonBeanRegistry.java
│  │  │              │      │
│  │  │              │      ├─support
│  │  │              │      │      AbstractAutowireCapableBeanFactory.java
│  │  │              │      │      AbstractBeanDefinitionReader.java
│  │  │              │      │      AbstractBeanFactory.java
│  │  │              │      │      BeanDefinitionReader.java
│  │  │              │      │      BeanDefinitionRegistry.java
│  │  │              │      │      CglibSubclassingInstantiationStrategy.java
│  │  │              │      │      DefaultListableBeanFactory.java
│  │  │              │      │      DisposableBeanAdapter.java
│  │  │              │      │      FactoryBeanRegistrySupport.java
│  │  │              │      │      InstantiationStrategy.java
│  │  │              │      │      SimpleInstantiationStrategy.java
│  │  │              │      │
│  │  │              │      └─xml
│  │  │              │              XmlBeanDefinitionReader.java
│  │  │              │
│  │  │              ├─context
│  │  │              │  │  ApplicationContext.java
│  │  │              │  │  ApplicationContextAware.java
│  │  │              │  │  ApplicationEvent.java
│  │  │              │  │  ApplicationEventPublisher.java
│  │  │              │  │  ApplicationListener.java
│  │  │              │  │  ConfigurableApplicationContext.java
│  │  │              │  │
│  │  │              │  ├─event
│  │  │              │  │      AbstractApplicationEventMulticaster.java
│  │  │              │  │      ApplicationContextEvent.java
│  │  │              │  │      ApplicationEventMulticaster.java
│  │  │              │  │      ContextClosedEvent.java
│  │  │              │  │      ContextRefreshEvent.java
│  │  │              │  │      SimpleApplicationEventMulticaster.java
│  │  │              │  │
│  │  │              │  └─support
│  │  │              │          AbstractApplicationContext.java
│  │  │              │          AbstractRefreshableApplicationContext.java
│  │  │              │          AbstractXmlApplicationContext.java
│  │  │              │          ApplicationContextAwareProcessor.java
│  │  │              │          ClasspathXmlApplicationContext.java
│  │  │              │
│  │  │              ├─core
│  │  │              │  └─io
│  │  │              │          ClasspathResource.java
│  │  │              │          DefaultResourceLoader.java
│  │  │              │          FileSystemResource.java
│  │  │              │          Resource.java
│  │  │              │          ResourceLoader.java
│  │  │              │          UrlResource.java
│  │  │              │
│  │  │              └─util
│  │  │                      ClassUtils.java
│  │  │
│  │  └─resources
│  └─test
│      ├─java
│      │  └─com
│      │      └─akitsuki
│      │          └─springframework
│      │              └─test
│      │                  │  ApiTest.java
│      │                  │
│      │                  ├─event
│      │                  │      MyEvent.java
│      │                  │
│      │                  └─listener
│      │                          ContextCloseListener.java
│      │                          ContextRefreshListener.java
│      │                          MyEventListener.java
│      │
│      └─resources
│              spring.xml

子弹:事件定义

要实现事件机制,我们首先得有事件。事件其实就是用来被广播和监听的一个对象,它可以有很多种,在Spring中,我们的事件都继承自一个顶级的事件:ApplicationEvent

package com.akitsuki.springframework.context;import java.util.EventObject;/*** 抽象的事件类** @author ziling.wang@hand-china.com* @date 2022/12/1 15:09*/
public abstract class ApplicationEvent extends EventObject {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ApplicationEvent(Object source) {super(source);}
}

可以看到,这个事件继承了Java内置的 EventObjectEventObject中有一个Object类型的 source属性,我们可以用这个属性来传递一些我们必要的信息。

那么有了这个顶级的事件,我们就可以细化我们的事件了。对于Spring来说,ApplicationContext无疑是极为重要的一部分,针对于它的操作也很多,所以我们需要基于它,来构造出一系列事件。

package com.akitsuki.springframework.context.event;import com.akitsuki.springframework.context.ApplicationContext;
import com.akitsuki.springframework.context.ApplicationEvent;/*** applicationContext的事件** @author ziling.wang@hand-china.com* @date 2022/12/1 15:10*/
public abstract class ApplicationContextEvent extends ApplicationEvent {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ApplicationContextEvent(Object source) {super(source);}public final ApplicationContext getApplicationContext() {return (ApplicationContext) getSource();}
}

可以看到,我们在这个事件中,将 ApplicationContext作为需要传递的信息。而且提供了一个获取context的方法。

接下来,在前面的学习中,我们也了解到,ApplicationContext会有刷新和关闭操作。而在这些动作发生时,我们也希望能有一些事件能够发布出来,所以还需要下面的这两个事件。

package com.akitsuki.springframework.context.event;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 15:15*/
public class ContextRefreshEvent extends ApplicationContextEvent {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ContextRefreshEvent(Object source) {super(source);}
}package com.akitsuki.springframework.context.event;/*** 上下文关闭事件* @author ziling.wang@hand-china.com* @date 2022/12/1 15:13*/
public class ContextClosedEvent extends ApplicationContextEvent {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ContextClosedEvent(Object source) {super(source);}
}

很好理解,分别是针对刷新和关闭动作的事件,继承自 ApplicationContextEvent,意味着我们可以从这些事件中,获取到 ApplicationContext对象。

枪:事件发布者

如果说事件是我们用来发射的子弹,那么事件发布者就是我们的枪。

package com.akitsuki.springframework.context;/*** Application事件的发布者* @author ziling.wang@hand-china.com* @date 2022/12/1 16:14*/
public interface ApplicationEventPublisher {/*** 发布事件* @param applicationEvent 事件*/void publishEvent(ApplicationEvent applicationEvent);
}

可以看到,我们的这把枪,可以用来发射 ApplicationEvent类型的子弹。实际上对于我们上面的实现来说,也就是全部的子弹。因为其他类型的事件,都是继承自 ApplicationEvent

靶子:事件监听器

有了子弹和枪,我们自然还需要一个靶子,也就是用来接受事件的对象。

package com.akitsuki.springframework.context;import java.util.EventListener;/*** Spring的事件监听器* @author ziling.wang@hand-china.com* @date 2022/12/1 15:18*/
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {/***处理事件* @param event 事件*/void onApplicationEvent(E event);
}

可以看到,这里的监听器需要通过泛型来指定自己要监听对象的类型,而Spring又是如何通过泛型,来找到对应的监听器的,会在下面进行介绍。

靶场管理员:事件广播器

我们有各种各样的监听器,也有各种各样的对象。现在我们需要一个类来帮助我们管理它们,就是事件广播器。

package com.akitsuki.springframework.context.event;import com.akitsuki.springframework.context.ApplicationEvent;
import com.akitsuki.springframework.context.ApplicationListener;/*** Application事件广播器* @author ziling.wang@hand-china.com* @date 2022/12/1 15:17*/
public interface ApplicationEventMulticaster {/*** 添加一个监听器* @param listener 监听器*/void addApplicationListener(ApplicationListener<?> listener);/*** 移除一个监听器* @param listener 监听器*/void removeApplicationListener(ApplicationListener<?> listener);/*** 广播事件* @param event 事件*/void multicastEvent(ApplicationEvent event);
}

可以看到,我们可以根据这个广播器,来添加或者移除一个监听器,也可以通过它来进行事件的发送。这些都很好理解。对于添加/移除监听器来说,这些操作属于通用型操作。而对于广播事件而言,我们可能会有各种不同的实现,比如是否用线程、按照什么顺序等等。所以我们需要一个抽象类,来实现这些通用的功能。

package com.akitsuki.springframework.context.event;import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.BeanFactoryAware;
import com.akitsuki.springframework.context.ApplicationEvent;
import com.akitsuki.springframework.context.ApplicationListener;import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;/*** 抽象的application事件广播处理* @author ziling.wang@hand-china.com* @date 2022/12/1 15:23*/
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {private final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeanException {this.beanFactory = beanFactory;}@Override@SuppressWarnings("unchecked")public void addApplicationListener(ApplicationListener<?> listener) {applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);}@Overridepublic void removeApplicationListener(ApplicationListener<?> listener) {applicationListeners.remove(listener);}/*** 获取所有支持此事件的listener* @param event 事件* @return*/protected Collection<ApplicationListener<ApplicationEvent>> getApplicationListeners(ApplicationEvent event) {List<ApplicationListener<ApplicationEvent>> supportListeners = new LinkedList<>();for(ApplicationListener<ApplicationEvent> listener : applicationListeners) {if (supportsEvent(listener, event)) {supportListeners.add(listener);}}return supportListeners;}/*** 判断listener是否支持事件* 主要逻辑为拿到Listener泛型中的类型与事件的Class进行比较* @param listener 监听器* @param event 事件* @return*/@SuppressWarnings("rawtypes")protected boolean supportsEvent(ApplicationListener<ApplicationEvent> listener, ApplicationEvent event) {Class<? extends ApplicationListener> listenerClass = listener.getClass();//如果是被cglib动态代理过的,类名会有$$,所以要取superclass才是真正的类名Class<?> targetClass = listenerClass.getName().contains("$$") ? listenerClass.getSuperclass() : listenerClass;Type genericInterface = targetClass.getGenericInterfaces()[0];Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];String className = actualTypeArgument.getTypeName();Class<?> eventClass;try {eventClass = Class.forName(className);} catch (ClassNotFoundException e) {throw new BeanException("no event class:" + className, e);}return eventClass.isAssignableFrom(event.getClass());}
}

挺复杂的一个抽象类,我们来逐一分析。

首当其冲可以看到,它实现了 BeanFactoryAware接口,从而可以很方便的拿到 BeanFactory。前面学到的知识,在这里就用上了。

接下来我们看到,它用了一个Set来维护所有注册的监听器,对于添加和删除两个功能的实现也很简单,无非是添加/删除到Set中。但是下面有大量的篇幅,用于另外两个方法的实现:获取事件对应的监听器,以及判断监听器是否支持某个事件。这里是重点,也就是我们上面提到过的,如何通过泛型来拿到监听器需要监听的事件。

重点的实现是在 supportsEvent中,通过 getGenericInterfaces方法,拿到监听器的泛型,再判断事件的类型是否属于这个泛型。假设我们的监听器泛型为 ApplicationContextEvent,那么他就可以监听到 ApplicationContextEventContextRefreshEventContextCloseEvent这三种事件。

有了抽象类,我们还需要一个默认的实现,来帮我们填上 multicastEvent的坑。

package com.akitsuki.springframework.context.event;import com.akitsuki.springframework.context.ApplicationEvent;
import com.akitsuki.springframework.context.ApplicationListener;/*** Spring事件广播器普通实现** @author ziling.wang@hand-china.com* @date 2022/12/1 16:21*/
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {@Overridepublic void multicastEvent(ApplicationEvent event) {for (ApplicationListener<ApplicationEvent> listener : getApplicationListeners(event)) {listener.onApplicationEvent(event);}}
}

很简单的一个遍历调用。但这样做实际上并没有实现解耦,因为事件的发布和监听,是在同一个线程中进行的,可以想象,事件在发布后,必须等待监听处理完成才能继续完成其他操作。实际上Spring在这里是用多线程异步来实现的,这样也就真正的完成了解耦。但这里为了简单起见,就这么简单处理了。

到这儿,我们的事件机制其实已经完成的差不多了。但是我们上面定义的容器刷新和关闭的事件,还没有真正的用起来。所以我们还需要进行一些小改造,来让这些事件能够真正发布出去。

在此之前,我们需要扩充一下ApplicationContext接口的功能。之前的接口对于现在的体量来说,功能太过于狭小了一些。

package com.akitsuki.springframework.context;import com.akitsuki.springframework.beans.factory.HierarchicalBeanFactory;
import com.akitsuki.springframework.beans.factory.ListableBeanFactory;
import com.akitsuki.springframework.core.io.ResourceLoader;/*** 上下文接口** @author ziling.wang@hand-china.com* @date 2022/11/10 14:15*/
public interface ApplicationContext extends ListableBeanFactory, HierarchicalBeanFactory, ResourceLoader, ApplicationEventPublisher {}

很简单,只需要多继承一些其他的接口,功能自然就来了。这就是优秀设计的好处。而且我们需要注意到的是,这里也继承了事件发布的接口,意味着它可以实现事件发布功能。接下来让我们看看如何真正发布事件吧。

package com.akitsuki.springframework.context.support;import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.akitsuki.springframework.beans.factory.config.BeanDefinitionRegistryPostProcessor;
import com.akitsuki.springframework.beans.factory.config.BeanFactoryPostProcessor;
import com.akitsuki.springframework.beans.factory.config.BeanPostProcessor;
import com.akitsuki.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.akitsuki.springframework.context.ApplicationEvent;
import com.akitsuki.springframework.context.ApplicationEventPublisher;
import com.akitsuki.springframework.context.ApplicationListener;
import com.akitsuki.springframework.context.ConfigurableApplicationContext;
import com.akitsuki.springframework.context.event.ApplicationEventMulticaster;
import com.akitsuki.springframework.context.event.ContextClosedEvent;
import com.akitsuki.springframework.context.event.ContextRefreshEvent;
import com.akitsuki.springframework.context.event.SimpleApplicationEventMulticaster;
import com.akitsuki.springframework.core.io.DefaultResourceLoader;import java.util.Map;/*** 应用上下文类抽象实现* 继承DefaultResourceLoader是为了处理配置文件资源的加载* 实现了ConfigurableApplicationContext接口的刷新方法** @author ziling.wang@hand-china.com* @date 2022/11/10 15:06*/
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {private ApplicationEventMulticaster applicationEventMulticaster;@Overridepublic void refresh() throws BeanException {//创建beanFactory,加载beanDefinitionrefreshBeanFactory();//获取beanFactoryConfigurableListableBeanFactory beanFactory = getBeanFactory();//添加ApplicationContextAwareProcessorbeanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));//在bean实例化之前,执行BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);//注册BeanPostProcessorregisterBeanPostProcessors(beanFactory);//初始化事件发布者initApplicationEventMulticaster();//注册事件监听器registerListeners();//提前实例化单例bean对象beanFactory.preInstantiateSingletons();//结束刷新finishRefresh();}/*** 初始化事件广播*/private void initApplicationEventMulticaster() {this.applicationEventMulticaster = new SimpleApplicationEventMulticaster();getBeanFactory().addSingleton("applicationEventMulticaster", applicationEventMulticaster);}/*** 注册监听器*/private void registerListeners() {for (ApplicationListener<?> listener : getBeansOfType(ApplicationListener.class).values()) {applicationEventMulticaster.addApplicationListener(listener);}}/*** 结束刷新,这里会发布结束刷新事件*/private void finishRefresh() {publishEvent(new ContextRefreshEvent(this));}@Overridepublic void close() {//发布容器关闭事件publishEvent(new ContextClosedEvent(this));//销毁单例对象getBeanFactory().destroySingletons();}@Overridepublic void publishEvent(ApplicationEvent applicationEvent) {applicationEventMulticaster.multicastEvent(applicationEvent);}
}

这里只列出了必要的部分代码,可以看到,在刷新方法中,加入了广播器、监听器的初始化过程,在刷新结束的时候,会发送一个刷新的事件出去。同样的,在关闭的时候,也会发送一个关闭事件。而且这里也实现了发布事件的方法,也就是调用广播器的 multicatsEvent来进行。

测试

好了,上面写了那么多,我们总算是完成了Spring中的事件机制,下面就让我们亲自体验一番。

首先,我们来写一个自己的事件:

package com.akitsuki.springframework.test.event;import com.akitsuki.springframework.context.ApplicationEvent;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 16:49*/
public class MyEvent extends ApplicationEvent {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public MyEvent(Object source) {super(source);}
}

接下来,需要一个针对于这个事件的监听器

package com.akitsuki.springframework.test.listener;import com.akitsuki.springframework.context.ApplicationListener;
import com.akitsuki.springframework.test.event.MyEvent;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 16:56*/
public class MyEventListener implements ApplicationListener<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {System.out.println("收到MyEvent的信息力!内容:" + event.getSource());}
}

顺便再尝试一下监听容器刷新和关闭

package com.akitsuki.springframework.test.listener;import com.akitsuki.springframework.context.ApplicationListener;
import com.akitsuki.springframework.context.event.ContextRefreshEvent;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 16:58*/
public class ContextRefreshListener implements ApplicationListener<ContextRefreshEvent> {@Overridepublic void onApplicationEvent(ContextRefreshEvent event) {System.out.println("收到容器刷新的消息力!内容:" + event.getSource());}
}package com.akitsuki.springframework.test.listener;import com.akitsuki.springframework.context.ApplicationListener;
import com.akitsuki.springframework.context.event.ContextClosedEvent;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 16:59*/
public class ContextCloseListener implements ApplicationListener<ContextClosedEvent> {@Overridepublic void onApplicationEvent(ContextClosedEvent event) {System.out.println("收到容器关闭的消息力!内容:" + event.getSource());}
}

最后别忘了将这些监听器注册成bean

<?xml version="1.0" encoding="utf-8" ?>
<beans><bean id="myEventListener" class="com.akitsuki.springframework.test.listener.MyEventListener"/><bean id="contextRefreshListener" class="com.akitsuki.springframework.test.listener.ContextRefreshListener"/><bean id="contextCloseListener" class="com.akitsuki.springframework.test.listener.ContextCloseListener"/>
</beans>

这次我们的老朋友钉子户userService系列,终于不见了(笑)

最终测试类,通过虚拟机钩子来注册关闭方法,同时尝试发送我们自己的事件。

package com.akitsuki.springframework.test;import com.akitsuki.springframework.context.ApplicationContext;
import com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext;
import com.akitsuki.springframework.test.event.MyEvent;
import org.junit.Test;/*** @author ziling.wang@hand-china.com* @date 2022/12/1 17:03*/
public class ApiTest {@Testpublic void test() {ClasspathXmlApplicationContext context = new ClasspathXmlApplicationContext("classpath:spring.xml");context.registerShutdownHook();context.publishEvent(new MyEvent("给你看看我的宝贝"));}
}

测试结果

收到容器刷新的消息力!内容:com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext@4cdbe50f
收到MyEvent的信息力!内容:给你看看我的宝贝
收到容器关闭的消息力!内容:com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext@4cdbe50fProcess finished with exit code 0

可以看到,成功的收到了消息,这次的练习也圆满完成了。

相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring,这里对应的代码是mini-spring-10

手写Spring-第十章-让我看看!基于观察者模式的事件机制相关推荐

  1. 手写 Spring 事务、IOC、DI 和 MVC

    Spring AOP 原理 什么是 AOP? AOP 即面向切面编程,利用 AOP 可以对业务进行解耦,提高重用性,提高开发效率 应用场景:日志记录,性能统计,安全控制,事务处理,异常处理 AOP 底 ...

  2. JAVA项目代码手写吗_一个老程序员是如何手写Spring MVC的

    见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十多 ...

  3. 记录一次阿里架构师全程手写Spring MVC

    人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...

  4. 十年java架构师分享:我是这样手写Spring的

    人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...

  5. 从头开始实现一个小型spring框架——手写Spring之集成Tomcat服务器

    手写Spring之集成Tomcat与Servlet 写在前面 一.Web服务模型及servlet 1.1 Web服务器 1.2 请求流程 二.实现 三.小结 写在前面 最近学习了一下spring的相关 ...

  6. 手把手教你手写Spring框架

    手写spring准备工作: 新建一个maven工程: 架构 新建类: package com.spring;public class keweiqinApplicationContext {priva ...

  7. 手写Spring DI依赖注入,嘿,你的益达!

    手写DI 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBean ...

  8. 手写 Spring MVC

    学习自<Spring 5核心原理与30个类手写实战>作者 Tom 老师 手写 Spring MVC 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背 ...

  9. 05. 手写Spring核心框架

    目录 05 手写Spring核心框架 Pt1 手写IoC/DI Pt1.1 流程设计 Pt1.2 基础配置 application.properties pom.xml web.xml Pt1.3 注 ...

最新文章

  1. vue写一个通用的toast弹窗 toast 弹窗 提示
  2. 小明分享|嵌入式LINUX开发日志-错误汇总①
  3. 《Python Cookbook 3rd》笔记(5.3):使用其他分隔符或行终止符打印
  4. java中类作为成员变量类型使用、接口作为成员变量类型使用、接口作为方法的参数或返回值使用
  5. php格式化输出字_PHP 输出格式化字符串
  6. vba 数组赋值_VBA数组与字典解决方案第31讲:VBA数组声明及赋值后的回填方法
  7. 原生 CSS “杀死” 预处理器 Sass!
  8. H5学习从0到1-H5的新特性(1)
  9. npp夜光数据介绍 viirs_基于NPP-VIIRS夜间灯光数据的南宁市GDP空间化研究
  10. python聊天程序程序代码_python聊天程序实例代码分享 -电脑资料
  11. 关于MyEclipse 10 破解程序打开的原因
  12. 加密文件夹密码忘记怎么办?
  13. 良心推荐:最适合玩吃鸡手游的安卓机型有哪些?刺激战场为例
  14. 游戏+AI,你不曾想象的未来
  15. 飞机座舱布局工效综合评价与评价方法初探
  16. A Survey on Big Data Market: Pricing, Trading and Protection
  17. 随鼠标滚轮缩小和放大图片
  18. 数学建模之:匈牙利算法python代码
  19. sd卡 升级 linux,升级树莓派archlinux系统到新sd卡
  20. 010 火狐浏览器插件中,没有Xpath怎么办

热门文章

  1. 独家 | AI赋能新闻全链路,媒体人终将失业?
  2. java之I/O流【一】
  3. Mac应用推荐:iPic 图片上传工具
  4. RGB、YUV420、NV21、I420编码区别
  5. 前端面试之路(一)————易诚互动
  6. python树莓派摄像头_用树莓派 DIY 180度小球追踪摄像头
  7. Java 编写大鱼吃小鱼游戏 窗体程序 完整源码
  8. 【JZOJ 省选模拟】6681.图
  9. VC++玩转炫酷悬浮窗3---GDI+完美实现不规则窗体
  10. 深入了解 Dojo 的 Collections 工具包