技术

对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go细节 codereview mat使用 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 MVCC 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes自动扩容缩容 神经网络模型优化 直觉上理解机器学习 knative入门 如何学习机器学习 神经网络系列笔记 TIDB源码分析 《阿里巴巴云原生实践15讲》笔记 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全机制 jvm crash分析 Kubernetes监控 Kubernetes 控制器模型 Prometheus 学习 容器日志采集 容器狂占cpu怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 编程语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 硬件对软件设计的影响 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 学习并发 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型 java exception Linux IO学习 network channel network byte buffer 测试环境docker化实践 netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Macvlan Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习——整体介绍 zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty(六)netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 Redis概述 机器学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 异构数据库表在线同步 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM内存与执行 git spring rmi和thrift maven/ant/gradle使用 再看tcp 缓存系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Kubernetes存储 Kubernetes 其它特性 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 CoreOS 使用 Go学习 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

标签


回头看Spring IOC

2015年06月15日

前言

Spring 容器具象化一点就是 从xml、配置类、依赖jar 等处 通过 BeanDefinitionRegistry.registerBeanDefinition 向容器注入Bean信息,然后通过BeanFactory.getBean 应用在各个位置。

谈元编程与表达能力中,笔者收获了对runtime的一个理解:当相应的行为在当前对象上没有被找到时,运行时会提供一个改变当前对象行为的入口(比如synchronized关键字,jvm 会针对性的做一些处理)。从这个视角看,Spring 也可以认为是 java 的一个runtime,通过标注@Autowire等,ApplicationContext 获取的bean 拥有 bean代码本身看不到的能力

容器解决了什么问题?

DI 容器要解决的问题是什么呢?它解决的是组件创建和组装的问题,但是为什么这是一个需要解决的问题呢?软件设计需要有一个分解的过程,所以,它必然还要面对一个组装的过程,也就是把分解出来的各个组件组装到一起,完成所需要的功能。

假设我们有一个文章服务(ArticleService)提供根据标题查询文章的功能。当然,数据是需要持久化的,所以,这里还有一个 ArticleRepository,用来与持久化数据打交道。

class ArticleService {
  //提供根据标题查询文章的服务
  Article findByTitle(final String title) {
    ...
  }
}

在 ArticleService 处理业务的过程中,需要用到 ArticleRepository 辅助它完成功能,也就是说,ArticleService 要依赖于 ArticleRepository。这时你该怎么做呢?一个直接的做法就是在 ArticleService 中增加一个字段表示 ArticleRepository。

class ArticleService {
  private ArticleRepository repository;
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

这个字段怎么初始化呢?程序员一般最直接的反应就是直接创建这个对象。

class ArticleService {
  private ArticleRepository repository = new DBArticleRepository();
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

在真实的项目中,由于资源所限,我们一般不会在应用中任意打开数据库连接,而是会选择共享数据库连接。所以,DBArticleRepository 需要一个数据库连接(Connection)的参数。

class ArticlService {
  private ArticleRepository repository;
  public ArticlService(final Connection connection) {
    this.repository = new DBArticleRepository(connection);
  }
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

一旦开始准备测试,你就会发现,要让 ArticleService 跑起来,那就得让 ArticleRepository 也跑起来;要让 ArticleRepository 跑起来,那就得准备数据库连接。是不是觉得太麻烦?然后,真正开始写测试时,你才发现,要测试,你还要在数据库里准备各种数据。

问题出在哪儿呢?其实就在你创建对象的那一刻,问题就出现了。当我们创建一个对象时,就必须要有一个具体的实现类,对应到我们这里,就是那个 DBArticleRepository。虽然我们的 ArticleService 写得很干净,其他部分根本不依赖于 DBArticleRepository,只在构造函数里依赖了,但依赖就是依赖。与此同时,由于要构造 DBArticleRepository 的缘故,我们这里还引入了 Connection 这个类,这个类只与 DBArticleRepository 的构造有关系,与我们这个 ArticleService 的业务逻辑一点关系都没有。你看到了,只是因为引入了一个具体的实现,我们就需要把它周边配套的东西全部引入进来,而这一切与这个类本身的业务逻辑没有任何关系。这还只是最简单的场景,在真实的项目中,构建一个对象可能还会牵扯到更多的内容:

  1. 根据不同的参数,创建不同的实现类对象,你可能需要用到工厂模式。
  2. 为了了解方法的执行时间,需要给被依赖的对象加上监控。
  3. 依赖的对象来自于某个框架,你自己都不知道具体的实现类是什么。

既然直接构造存在这么多的问题,那么最简单的办法就是把创建的过程拿出去,只留下与字段关联的过程:

class ArticleService {
    private ArticleRepository repository;
    public ArticleService(final ArticleRepository repository) {
        this.repository = repository;
    }
    public Article findByTitle(final String title) {
        // 做参数校验
        return this.repository.findByTitle(title);
    }
}

现在,对象的创建已经分离了出去,但还是要要有一个地方完成这个工作,最简单的解决方案自然是,把所有的对象创建和组装在一个地方完成:

...
ArticleRepository repository = new DBArticleRepository(connection);
AriticleService service = new ArticleService(repository);
...

相比于业务逻辑,组装过程并没有什么复杂的部分。一般而言,纯粹是一个又一个对象的创建以及传参的过程,这部分的代码看上去会非常的无聊。虽然很无聊,但这一部分代码很重要,最好的解决方案就是有一个框架把它解决掉。在 Java 世界里,这种组装一堆对象的东西一般被称为“容器”,我们也用这个名字。

Container container = new Container();
container.bind(Connection.class).to(connection);
container.bind(ArticleReposistory.class).to(DBArticleRepository.class);
container.bind(ArticleService.class).to(ArticleService.class)
ArticleService service = container.getInstance(ArticleService.class);

一个容器就此诞生。因为它解决的是依赖的问题,把被依赖的对象像药水一样,注入到了目标对象中,所以,它得名“依赖注入”(Dependency Injection,简称 DI)。这个容器也就被称为 DI 容器了。在没有 DI 容器之前,那是怎样的一个蛮荒时代啊!有了 DI 容器之后呢?你的代码就只剩下关联的代码,对象的创建和组装都由 DI 容器完成了。甚至在不经意间,你有了一个还算不错的设计:至少你做到了面向接口编程,它的实现是可以替换的,它还是可测试的。

Inversion of control In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code. traditional control flow 是从开始到结束都是自己“写代码”,IoC 中control flow的发起是由一个framework 触发的。类只是干自己的活儿——“填代码”,然后ioc在需要的时候调用。

IOC设计模式的两个重要支持:

  1. 对象间依赖关系的建立和应用系统的运行状态没有很强的关联性,因此对象的依赖关系可以在启动时建立好,ioc容器(负责建立对象的依赖关系)不会对应用系统有很强的侵入性。
  2. 面向对象系统中,除了一部分对象是数据对象外,其他很大一部分是用来处理数据的,这些对象并不经常发生变化,在系统中以单件的形式起作用就可以满足应用的需求。

ioc 的接口定义

什么是ioc容器?BeanFactory是最简单的ioc容器,看了BeanFactory接口方法,也许会更直观(主要包括获取bean、查询bean的特性等方法)。

interface BeanFactory{
    FACTORY_BEAN_PREFIX
    object getBean(String)
    T getBean(String,Class<T>)
    T getBean(Class<T>)
    boolean containsBean(String)
    boolean isSingleton(String)
    boolean isPrototype(String)  
    boolean isTypeMatch(String,Class<T>)   
    class<?> getType(String)
    String[] getAliases    
}

BeanFactory

属于spring-beans包

The root interface for accessing a Spring bean container.This is the basic client view of a bean container;The point of this approach is that the BeanFactory is a central registry of application components, and centralizes configuration of application components (no more do individual objects need to read properties files,for example).

Note that it is generally better to rely on Dependency Injection(“push” configuration) to configure application objects through setters or constructors, rather than use any form of “pull” configuration like a BeanFactory lookup. Spring’s Dependency Injection functionality is implemented using this BeanFactory interface and its subinterfaces.

  1. 作为一个BeanFactory,要能够getBean、createBean、autowireBean、根据各种Bean的信息检索list bean、支持父子关系,这些能力被分散在各个接口中
  2. AbstractBeanFactory 负责实现BeanFactory,同时留了一些抽象方法交给子类实现
  3. 如何对 BeanFactory 施加影响?BeanPostProcessor,Factory hook that allows for custom modification of new bean instances,e.g. checking for marker interfaces or wrapping them with proxies.

ApplicationContext

属于spring-context包

AbstractRefreshableApplicationContext 虽然实现了BeanFactory接口,但其实是组合了一个beanFactory,这是讨论BeanFactory 和 ApplicationContext 差异时最核心的一点。 也正因此,ApplicationContext is a complete superset of the BeanFactory.

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    private DefaultListableBeanFactory beanFactory;
    public final ConfigurableListableBeanFactory getBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                        "call 'refresh' before accessing beans via the ApplicationContext");
            }
            return this.beanFactory;
        }
    }
}

In contrast to a plain BeanFactory, an ApplicationContext is supposed to detect special beans(BeanFactoryPostProcessor/BeanPostProcessor/ApplicationListener)defined in its internal bean factory

public abstract class AbstractApplicationContext{
    public void refresh(){
        ...
        prepareBeanFactory(beanFactory);
        ...
        registerBeanPostProcessors(beanFactory);
        ...
    }
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        ...
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        ...
    }
}

beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));ApplicationContextAwareProcessor是一个BeanPostProcessor,其作用就是当发现 一个类实现了ApplicationContextAware等接口时,为该类注入ApplicationContext 成员。

public static void registerBeanPostProcessors(...){
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    ...
    for (String ppName : postProcessorNames) {
        BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
        priorityOrderedPostProcessors.add(pp);
    }
    ...
    registerBeanPostProcessors(beanFactory,priorityOrderedPostProcessors);
}

BeanFactory VS ApplicationContext

ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created.Plain bean factories allow for programmatic registration of post-processors,applying to all beans created through this factory.

可以说,BeanFactory 基本实现了一个bean container 需要的所有功能,但其一些特性要通过programmatic来支持,ApplicationContext在简单容器BeanFactory的基础上,增加了许多面向框架的特性。《Spring技术内幕》中参照XmlBeanFactory的实现,以编程方式使用DefaultListableBeanFactory,从中我们可以看到Ioc容器使用的一些基本过程。

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

简单来说,reader.loadBeanDefinitions(res);将类信息从beans.xml 读取到DefaultListableBeanFactory及其父类的各种map中。然后factory.getBean时即可做出对应的反应。

ioc启动与停止

一个容器的功能:构建和管理Bean, 分割在启动和getBean 两个部分,启动时候没有创建Bean 对象。所以BeanFactoryPostProcessor 工作在ioc 启动阶段,BeanPostProcessor 工作在getBean 阶段。

容器启动

AbstractApplicationContext.refresh(): Load or refresh the persistent representation of the configuration such as xml. ApplicationContext 支持的很多特性都可以在个启动方法里找到迹象。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // Prepare the bean factory for use in this context. 加入了Bean依赖以及非Bean依赖(比如Environment)
        prepareBeanFactory(beanFactory);
        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Initialize message source for this context.
            initMessageSource();
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();
            // Initialize other special beans in specific context subclasses.
            onRefresh();
            // Check for listener beans and register them.
            registerListeners();
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
            // Last step: publish corresponding event.
            finishRefresh();
        }catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

其中obtainFreshBeanFactory ==> refreshBeanFactory

// AbstractRefreshableApplicationContext.java
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

refreshBeanFactory 中beanFactory 的refresh 更纯粹一点:从xml、注解 等处构造BeanDefinitions 信息。ApplicationContext refresh 杂活比较多,还要管 Listeners、MessageSource、Event ,给beanFactory 塞一些特殊的类实例或逻辑。

停止逻辑

public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
        // If we registered a JVM shutdown hook, we don't need it anymore now:We've already explicitly closed the context.
        if (this.shutdownHook != null) {
            try {
                Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
            }catch (IllegalStateException ex) {
                // ignore - VM is already shutting down
            }
        }
    }
}

ioc 中的对象

来源 Spring Bean对象 生命周期管理 配置元信息
是否lazyload/autowiring等
使用场景
Spring BeanDefinition 依赖查找、依赖注入
单体对象 依赖查找、依赖注入
Resolvable Dependency 依赖注入

bean在不同阶段的表现形式

  表现形式 与jvm类比
配置文件
@Configuration注释的class等
<bean class=""></bean> java代码
ioc初始化 BeanDefinition class二进制 ==> 内存中的Class对象
getBean Object 堆中的对象实例

Bean工厂的养料——BeanDefinition

在BeanFactory 可以getBean之前,必须要先初始化,从各种源(比如xml配置文件)中加载bean信息

jvm 基于Class 对象将对象实例化,想new 一个对象,得先有Class 对象,或者使用classLoader 加载,或者动态生成。spring 容器类似,想getBean 对象bean 实例, 就是先register 对应的BeanDefinition,任何来源的bean 通过BeanDefinitionRegistry.registerBeanDefinition 都可以纳入到IOC 的管理。

getBean

Spring的Bean生命周期,11 张高清流程图及代码,深度解析BeanFactory.getBean("beanid")得到bean的实例

ioc在实例化bean的过程中,还夹了不少“私货”/“钩子”:

内建对象Environment

Interface representing the environment in which the current application is running.Models two key aspects of the application environment: profiles and properties. Environment代表着程序的运行环境,主要包含了两种信息,一种是profiles,用来描述哪些bean definitions 是可用的;一种是properties,用来描述系统的配置,其来源可能是配置文件、jvm属性文件、操作系统环境变量等。

并不是所有的Bean 都会被纳入到ioc管理,A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile annotation for syntax details.

Properties play an important role in almost all applications,and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects,Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

其它