【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结(下)

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结(下)

调用container这个方法后,则会将启动信号扩散至该容器内部的所有组件。会调用【所有】的实现了Lifecycle的组件的start()方法~~~


当然,我们稍作处理,也能让Lifecycle生效。当然,我并不建议这么去做~~~~~~~~~


// 注意,此处的名称必须,必须是lifecycleProcessor  否则没有效果的
// 名称也可以用这个常量AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME
@Component("lifecycleProcessor") 
public class MinivisionDefaultLifecycleProcessor extends DefaultLifecycleProcessor {
    /**
     * 这边重写了onRefresh方法 让其去调用start()方法,而start方法上面我们能看得见,它传的false
     */
    @Override
    public void onRefresh() {
        super.start();
    }
}


还有一个方式就是我们获取到容器后显示的调用start方法。但是这两种方式,我个人都不推荐这么去做~~~


publishEvent(new ContextRefreshedEvent(this)):发布容器刷新的事件


  //由此我们可以看到,ApplicationContext容器也是有发布事件的能力的(事件为:ApplicationEvent或其子类)
  @Override
  public void publishEvent(ApplicationEvent event) {
    publishEvent(event, null);
  }
  // 本方法Spring4.2之后才有  属于发布事件的方法
  // 这个方法更强:它能发布所有的事件,事件源是Object嘛
  protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
    }
    // 最终也会被包装成applicationEvent的事件类型,带有一个Payload而已
    else {
      applicationEvent = new PayloadApplicationEvent<>(this, event);
      if (eventType == null) {
        eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
      }
    }
    // Multicast right now if possible - or lazily once the multicaster is initialized
    // 如果早期事件已经被初始化了,那就先放进早期事件里,否则esle那里,就直接发送事件了
    if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
    } else {
      //拿到多播器,发送这个时间
      getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }
    // Publish event via parent context as well...
    // 这里注意:如果存在父容器,那也给父容器会发送一个事件
    if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
        ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
      } else {
        this.parent.publishEvent(event);
      }
    }
  }


完成了这一步骤后,Spring IOC容器正式启动成功了。

至于catch和finally里的方法,都特别简单,就不再做介绍了~

优雅的关闭Spring容器


关于关闭容器,是讨论得比较少的话题。确实,平时开发中,我们也可以不用太关心容器的关闭,暴力解决就行,因此这里也只简单的说一下,优雅的关闭Spring容器的方式。

先聊聊Runtime类


每个Java应用都有一个属于自己的Runtime类实例,使应用程序能够与其运行的环境相连接,可以通过 getRuntime 方法获取/销毁当前运行状态中的一些内容或者进行内存清理。应用程序不能创建自己Runtime 类实例。


它有一个API如下:Runtime.getRuntime().addShutdownHook(shutdownHook)

在Java应用程序中可以通过添加关闭钩子,实现在程序退出时关闭资源的功能。 使用Runtime.addShutdownHook(Thread hook)向JVM添加关闭钩子。


这句代码可以书写在任何地方,总之就是JVM退出的时候才会去调用这个钩子Thread~


触发场景:


1.程序正常退出


2.使用System.exit()


3.终端使用Ctrl+C触发的中断


4.系统关闭


5.使用Kill pid命令干掉进程(kill -9除外)



以下场景不会触发:


在Eclipse/idea中直接点击Terminate关闭程序时不会触发关闭钩子的;

在Linux下使用kill -9也是不会触发钩子的;

知道了这个钩子后,我们就可以在关闭线程里,做一些优雅的释放资源事情了。


有了以上知识,我们再看看Spring场景下怎么优雅的关闭:


Web应用


如果我们正常的关闭Tomcat,是能够正常的关闭web容器的同时,关闭Spring容器的。因此内部细节,我们此处不关心了


非web应用(比如纯dubbo应用)


我们看看AbstractApplicationContext类里其实提供了实现方法:

  @Override
  public void registerShutdownHook() {
    if (this.shutdownHook == null) {
      // No shutdown hook registered yet.
      this.shutdownHook = new Thread() {
        @Override
        public void run() {
          synchronized (startupShutdownMonitor) {
            // 具体的容器管理逻辑
            doClose();
          }
        }
      };
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
  }


因此当我们启动容器的时候,自己调用registerShutdownHook这个方法便可,非常的方便。比如Dubbo框架,其实他就是这么优雅的去关闭容器的。


Spring Boot环境优雅的关闭容器


当把打包好的jar包发布到服务器,并通过java -jar运行,一般要把springboot项目关闭大多数都是先找到项目的pid,然后直接kill pid,不过这种方法在特殊需求场景下不太合适(不安全),同时也不优雅。


备注:我们本地启动,比如在idea一般直接点红色按钮就行,但是在本地嘛,无所谓啦,如果在服务器端,最好还是能够优雅点。


方案一:使用actuator

开启端点:

# 启用shutdown
management.endpoint.shutdown.enabled=true
# 公开所有的端点
management.endpoints.web.exposure.include=*


然后模拟发送一个POST请求:curl -X POST http://host:port/actuator/shutdown 即可优雅的关闭容器了。但是显然,这个危险系数太高了,万一ip和端口被别人知道了呢?线上哎,风险极高有木有~~~~


方案二:加固Endpoints

配置参考如下:

endpoints:
  shutdown:
    enabled: true
    path: /xxx
management:
  security:
    enabled: true
  port: 9001
  address: 127.0.0.1
  context-path: /admin
security:
  basic:
    enabled: true
    path: /admin
  user:
    name: root
    password: 123456


配置完成后,最终的停服命令为: (有了用户名、密码加持,可以说是能够达到生产级别的安全性了)


curl -X POST -u root:123456 http://127.0.0.1:9001/admin/xxx


方案三:注册为Linux系统服务

可以轻松地用 init.d 或 systemd 注册成 Linux/Unix 系统服务,这使得在生产环境中,安装和管理 SpringBoot 应用程序变得非常简单。

怎么把一个Spring Boot服务注册成服务,此处省略



Spring容器的refresh()【创建、刷新】完整总结



1、prepareRefresh()刷新前的预处理;
  0)、this.closed.set(false),this.active.set(true)  设置一些标记位
  1)、initPropertySources()初始化一些属性设置;(交由子类去实现,比如web容器中的 AbstractRefreshableWebApplicationContext 就去初始化了servlet的一些init参数等等)
  2)、getEnvironment().validateRequiredProperties();检验属性的合法等
  3)、earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>();初始化容器,保存一些早期的事件;
2、obtainFreshBeanFactory();获取BeanFactory;
  1)、refreshBeanFactory();抽象方法,子类【AbstractRefreshableApplicationContext】唯一实现的:
      ①、若已经存在beanFactory了,那就做一些清理工作(销毁单例Bean、关闭工厂)
      ②、创建了一个this.beanFactory = new DefaultListableBeanFactory();并且设置id
      ③、把旧的工厂的属性赋值给新创建的工厂:customizeBeanFactory(beanFactory)
      ④、loadBeanDefinitions(beanFactory):加载Bean定义。抽象方法,由子类去决定从哪儿去把Bean定义加载进来,实现有比如:
          XmlWebApplicationContext:专为web设计的从xml文件里加载Bean定义(借助XmlBeanDefinitionReader)
          ClassPathXmlApplicationContext/FileSystemXmlApplicationContext:均由父类AbstractXmlApplicationContext去实现这个方法的,也是借助XmlBeanDefinitionReader
          AnnotationConfigWebApplicationContext:基于注解驱动的容器。(也是当下最流行、最重要的一个实现,前面一篇博文对此有重点分析),借助了AnnotatedBeanDefinitionReader.register()方法加载Bean定义
            (这里面需要注意的是:.register()只是把当前这一个Class对象registry.registerBeanDefinition()了,至于内部的@Bean、@ComponentScan扫描到的,都不是在此处注册的)
          有必要说一句:AnnotationConfigApplicationContext是在非web环境下的容器。它虽然没有实现实现loadBeanDefinitions()抽象方法,是因为它在new对象的时候,已经调用了.register()完成配置Bean定义信息的注册了
  2)、getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象;
  3)、将创建的BeanFactory【DefaultListableBeanFactory】返回;
  =======到这一步截止,BeanFactory已经创建好了(只不过都还是默认配置而已),配置Bean的定义信息也注册好了=======
3、prepareBeanFactory(beanFactory);BeanFactory的预准备工作(对BeanFactory进行一些设置);
  1)、设置BeanFactory的类加载器、StandardBeanExpressionResolver、ResourceEditorRegistrar
  2)、添加感知后置处理器BeanPostProcessor【ApplicationContextAwareProcessor】,并设置一些忽略EnvironmentAware、EmbeddedValueResolverAware、xxxxx(因为这个处理器都一把抓了)
  3)、注册【可以解析的(表示虽然不在容器里,但还是可以直接 @Auwowired)】自动装配;我们能直接在任何组件中自动注入(@Autowired):
      BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
  5)、添加BeanPostProcessor【ApplicationListenerDetector】 检测注入进来的Bean是否是监听器
  6)、Detect a LoadTimeWeaver and prepare for weaving, if found.添加编译时的AspectJ支持:LoadTimeWeaverAwareProcessor
    (添加的支持的条件是:beanFactory.containsBean("loadTimeWeaver"))
  7)、给BeanFactory中注册一些能用的组件;
    environment-->【ConfigurableEnvironment】、
    systemProperties-->【Map<String, Object>】、
    systemEnvironment-->【Map<String, Object>】
4、postProcessBeanFactory(beanFactory);BeanFactory准备工作完成后进行的后置处理工作;(由子类完成)
  一般web容器都会对应的实现此方法,比如 AbstractRefreshableWebApplicationContext:
    1)、添加感知BeanPostProcessor【ServletContextAwareProcessor】,支持到了ServletContextAware、ServletConfigAware
    2)、注册scopse:beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());当然还有SCOPE_SESSION、SCOPE_APPLICATION
    3)、向上线一样,注册【可以解析的】自动注入依赖:ServletRequest/ServletResponse/HttpSession/WebRequest
      (备注:此处放进容器的都是xxxObjectFactory类型,所以这是为何@Autowired没有线程安全问题的重要一步)
    4)、registerEnvironmentBeans:注册环境相关的Bean(使用的registerSingleton,是直接以单例Bean放到容器里面了)
      servletContext-->【ServletContext】
      servletConfig-->【ServletConfig】
      contextParameters-->【Map<String, String>】 保存有所有的init初始化参数(getInitParameter)
      contextAttributes-->【Map<String, Object>】 servletContext的所有属性(ServletContext#getAttribute(String))
========以上是BeanFactory的创建及预准备工作,至此准备工作完成了,那么接下来就得利用工厂干点正事了========
5、invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactoryPostProcessor的方法;
  BeanFactoryPostProcessor:BeanFactory的后置处理器。此处调用,现在就表示在BeanFactory标准初始化之后执行的;
  两个接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor(子接口)
  1)、执行BeanFactoryPostProcessor们的方法;
    ===先执行BeanDefinitionRegistryPostProcessor===
    1)、获取所有的BeanDefinitionRegistryPostProcessor;(当然会最先执行我们手动set进去的Processor,但是这个一般都不会有)
    2)、先执行实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor、
      postProcessor.postProcessBeanDefinitionRegistry(registry)
    3)、在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor
    4)、最后执行没有实现任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessors
    (小细节:都会调用getBean(“name”,BeanDefinitionRegistryPostProcessor.class)方法,所以都会先实例化,才去执行的)
    **这里面需要特别的介绍一个处理器:`ConfigurationClassPostProcessor`,它是一个BeanDefinitionRegistryPostProcessor**
    **它会解析完成所有的@Configuration配置类,然后所有@Bean、@ComponentScan等等Bean定义都会搜集进来了,所以这一步是非常的重要的**
    ===再执行BeanFactoryPostProcessor的方法(顺序逻辑同上,略)===
  2)、再次检测一次添加对AspectJ的支持。为何还要检测呢?through an @Bean method registered by ConfigurationClassPostProcessor,这样我们注入了一个切面Bean,就符合条件了嘛
6、registerBeanPostProcessors(beanFactory);注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】
    **不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的**
    BeanPostProcessor:BeanPostProcessor是一个工厂钩子,允许Spring框架在新创建Bean实例时对其进行定制化修改,比如填充Bean、创建代理、解析Bean内部的注解等等。。。
    DestructionAwareBeanPostProcessor:Bean销毁时候
    InstantiationAwareBeanPostProcessor:Bean初始化的时候
    SmartInstantiationAwareBeanPostProcessor:初始化增强版本:增加了一个对Bean类型预测的回调(一般是Spring内部使用,调用者还是使用InstantiationAwareBeanPostProcessor就好)
    MergedBeanDefinitionPostProcessor:合并处理Bean定义的时候的回调【该类型的处理器保存在名为internalPostProcessors的List中】、
    1)、获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级
    2)、先注册PriorityOrdered优先级接口的BeanPostProcessor;
      把每一个BeanPostProcessor;添加到BeanFactory中
      beanFactory.addBeanPostProcessor(postProcessor);
    3)、再注册Ordered接口的、最后注册没有实现任何优先级接口的、最终注册MergedBeanDefinitionPostProcessor
      (此处细节:BeanPostProcessor本身也是一个Bean,其注册之前一定先实例化,而且是分批实例化和注册。
      另外还有一个非常非常重要的一点就是阶段顺序问题:
      我们可以把BeanPostProcessor的实例化与注册分为四个阶段:
          第一阶段applicationContext内置阶段、
          第二阶段priorityOrdered阶段、
          第三阶段Ordered阶段、
          第四阶段nonOrdered阶段
      因为是分批注册,所以我们同阶段是不能拦截到同阶段的BeanPostProcessor的实例化的。举例子:
      PriorityOrdered的只能被内置阶段的比如:ApplicationContextAwareProcessor(可以注入啦)/ApplicationListenerDetector(可以接受事件啦)这种拦截
      而:Ordered就可以被 内置的、PriorityOrdered都拦截到了
      。。。 以此类推。。。
      所以我们的BeanPostProcessor是可以@Autowired 比如Service、Dao来做一些事的。单思,但是一定要【注意避免BeanPostProcessor启动时的“误伤”陷阱】,什么意思?大概解释一下如下:
        可能由于你的Processor依赖于某个@Bean,从而让它提前实例化了,然后就很可能错过了后面一些BeanPostProcessor的处理,造成“误伤”
        (SpringBoot中使用Shiro、Spring-Cache的时候,使用不当会出现这样的问题)
    6)、这一步非常有意思:moving it to the end of the processor chain。它的又注册了一次,作用是把这个探测器移动到处理器的底部,最后一个(显然,最后一个是为了不要放过任何Bean)
      (小细节:可能有小伙伴疑问,这里也是new出来,这这样容器内不就有两个探测器对象了吗?气其实不然,ApplicationListenerDetector它重写了hashCode方法,且只和应用applicationContext有关)
      return ObjectUtils.nullSafeHashCode(this.applicationContext);所以对它执行remove的时候,会被当作同一个对象处理,能把老的移除成功添加新的的
7、initMessageSource();初始化MessageSource组件(做国际化功能;消息绑定,消息解析)
    1)、看容器中是否有id为messageSource的,类型是MessageSource的组件
      如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource;
        MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取;
    2)、把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource
    beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource)
8、initApplicationEventMulticaster();初始化事件派发器;
    1)、从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster;
    2)、如果上一步没有配置;创建一个SimpleApplicationEventMulticaster,将创建的ApplicationEventMulticaster添加到BeanFactory中
9、onRefresh();留给子容器(子类) 容器刷新的时候做些事
    AbstractRefreshableWebApplicationContext:this.themeSource = UiApplicationContextUtils.initThemeSource(this);
10、registerListeners();把容器中将所有项目里面的ApplicationListener注册进来;
    1、拿到容器里所有的Bean定义的名字,类型为ApplicationListener,然后添加进来
      getApplicationEventMulticaster().addApplicationListener(listener);
    2、派发之前步骤产生的事件(早期事件)
    (细节:此处只是把Bean的名字放进去,Bean还没有实例化哦~~~~)
11、finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean;这应该是最核心的一步了
  1)、为容器初始化ConversionService(容器若没有就不用初始化了,依然采用getBean()初始化的) 提供转换服务
  2)、若没有设置值解析器,那就注册一个默认的值解析器(lambda表示的匿名处理)
  3)、实例化LoadTimeWeaverAware(若存在)
  4)、清空临时类加载器:beanFactory.setTempClassLoader(null)
  5)、缓存(快照)下当前所有的Bean定义信息 beanFactory.freezeConfiguration();
  ==== 更精确的是说是根据Bean的定义信息:beanDefinitionNames来实例化、初始化剩余的Bean ====
  6)、beanFactory.preInstantiateSingletons();初始化后剩下的单实例bean(过程这里就不详说了)
12、finishRefresh();完成BeanFactory的初始化创建工作;IOC容器就创建完成
    0)、clearResourceCaches(); (Spring5.0才有)
    1)、initLifecycleProcessor();初始化和生命周期有关的后置处理器;从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有new DefaultLifecycleProcessor();
      关于Lifecycle接口的使用,也专门讲解过,这里不聊了
    2)、getLifecycleProcessor().onRefresh();  相当于上面刚注册,下面就调用了
    3)、publishEvent(new ContextRefreshedEvent(this));发布容器刷新完成事件;
    4)、liveBeansView.registerApplicationContext(this); 和MBean相关,略
  ======总结===========
  1)、Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息(可以有N种方式);
    1)、xml注册bean;<bean>
    2)、注解注册Bean;@Service、@Component、@Bean、xxx
  2)、Spring容器会合适的时机创建这些Bean
    1)、用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中;
    2)、统一创建剩下所有的bean的时候;finishBeanFactoryInitialization();
  3)、后置处理器;BeanPostProcessor
    1)、每一个bean创建完成,都会使用各种后置处理器进行处理;来增强bean的功能;
      AutowiredAnnotationBeanPostProcessor:处理自动注入
      AnnotationAwareAspectJAutoProxyCreator:来做AOP功能;
      xxx....
      增强的功能注解:
      AsyncAnnotationBeanPostProcessor
      ....
  4)、事件驱动模型;
    ApplicationListener;事件监听;
    ApplicationEventMulticaster;事件派发:



有了这份完整的总结描述,能让自己抽出来(不用太关心细节)从外层(更宏观的)看Spring 容器的初始化过程。希望能帮助到大家更好的理解~


总结


IoC的启动包括BeanDefinition的Resource定位、注入和注册三个基本过程。


第一个过程是Resource定位过程。这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成。

第二个过程是BeanDefinition的载入。这个载入过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。

第三个过程是向IoC容器注册这些BeanDefinition的过程。在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。


相关文章
|
25天前
|
Kubernetes 监控 Cloud Native
|
21天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
12天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
25 0
|
1月前
|
前端开发 Docker 容器
主机host服务器和Docker容器之间的文件互传方法汇总
Docker 成为前端工具,可实现跨设备兼容。本文介绍主机与 Docker 容器/镜像间文件传输的三种方法:1. 构建镜像时使用 `COPY` 或 `ADD` 指令;2. 启动容器时使用 `-v` 挂载卷;3. 运行时使用 `docker cp` 命令。每种方法适用于不同场景,如静态文件打包、开发时文件同步及临时文件传输。注意权限问题、容器停止后的文件传输及性能影响。
131 0
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
8天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
33 2
|
18天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
14天前
|
关系型数据库 MySQL API
|
7天前
|
缓存 监控 开发者
掌握Docker容器化技术:提升开发效率的利器
在现代软件开发中,Docker容器化技术成为提升开发效率和应用部署灵活性的重要工具。本文介绍Docker的基本概念,并分享Dockerfile最佳实践、容器网络配置、环境变量和秘密管理、容器监控与日志管理、Docker Compose以及CI/CD集成等技巧,帮助开发者更高效地利用Docker。
|
8天前
|
监控 持续交付 Docker
Docker 容器化部署在微服务架构中的应用有哪些?
Docker 容器化部署在微服务架构中的应用有哪些?