当前位置:

首页 > 编程开发 > SpringBoot启动流程SpringApplication源码分析

SpringBoot启动流程SpringApplication源码分析

SpringBoot启动流程源码分析一、入口参数研究和创建对象准备阶段分析以下先看下SpringApplication的run()方法packageorg.springframework.boot;publicConfigurableApplicationContextrun(String...args){//1.计时器StopWatchstopWatch=newStopWatch();stopWatch.start();ConfigurableApplicationContextcontext=null

本文目录

      SpringBoot启动流程源码分析一、入口参数研究和创建对象

      准备阶段分析

      以下先看下SpringApplication的run()方法

      package org.springframework.boot;
      public ConfigurableApplicationContext run(String... args) {
         //1.计时器
         StopWatch stopWatch = new StopWatch();
         stopWatch.start();
         ConfigurableApplicationContext context = null;
         Collection exceptionReporters = new ArrayList<>();
         //2.headless配置
         configureHeadlessProperty();
         //3、获取监听
         SpringApplicationRunListeners listeners = getRunListeners(args);
         listeners.starting();
         try {
         //应用程序启动的参数  
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //4、准备环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //环境创建成功后,配置bean信息,决定是否跳过 BeanInfo 类的扫描,如果设置为 true,则跳过
            configureIgnoreBeanInfo(environment);
            //打印banner信息
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                  new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            //停止计时
            stopWatch.stop();
            //控制是否打印日志的,这里为true,即打印日志
            if (this.logStartupInfo) {
               new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
         }
         catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
         }
         try {
            listeners.running(context);
         }
         catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
         }
         return context;
      }

      我将会根据执行过程逐行进行分析

      1、StopWatch计时器

      此类实则为计时器,如下对具体使用进行分析

      StopWatch stopWatch = new StopWatch();
      //开始计时
      stopWatch.start();
      //停止计时
      stopWatch.stop();

      对于具体打印的上面写的为

      //将当前类传入StartupInfoLogger创建了一个对象
      //然后调用logStarted打印日志
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      //创建一个Log类
      protected Log getApplicationLog() {
         if (this.mainApplicationClass == null) {
            return logger;
         }
         return LogFactory.getLog(this.mainApplicationClass);
      }
      //调用log类的log.info()方法来打印日志
      public void logStarted(Log log, StopWatch stopWatch) {
          if (log.isInfoEnabled()) {
                  log.info(getStartedMessage(stopWatch));
          }
      }
      //打印详细的日志
      private StringBuilder getStartedMessage(StopWatch stopWatch) {
         StringBuilder message = new StringBuilder();
         message.append("Started ");
         message.append(getApplicationName());
         message.append(" in ");
         message.append(stopWatch.getTotalTimeSeconds());
         try {
            double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
            message.append(" seconds (JVM running for " + uptime + ")");
         }
         catch (Throwable ex) {
            // No JVM time available
         }
         return message;
      }

      这里可以看到stopWatch.getTotalTimeSeconds()方法就是来获取实际的计时时间的。再者,通过这几行代码,我们也可以考虑下平常在写代码的时候,有几种日志打印方式?SpringBoot是怎么集成日志框架的?

      2、configureHeadlessProperty()

      private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
      private void configureHeadlessProperty() {
         System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
               System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
      }

      这一部分代码这样理解吧,首先java.awt包提供了用于创建用户界面和绘制图形图像的所有分类,那么 属性SYSTEM_PROPERTY_JAVA_AWT_HEADLESS就一定会和用户界面相关了。 这里将SYSTEM_PROPERTY_JAVA_AWT_HEADLESS设置为true,其实就是表示在缺少显示屏、键盘或者鼠标中的系统配置,如果将其设置为true,那么headless工具包就会被使用。

      3、getRunListeners(args) 获取监听

      总体上可以分这三步

      • 获取一个默认的加载器

      • 根据类型获取spring.factories中符合的类名

      • 创建类实例,返回

      如下将跟下代码

      //获取所有监听
      SpringApplicationRunListeners listeners = getRunListeners(args);
      //启动监听
      listeners.starting();

      跳转进入getRunListeners方法

      private SpringApplicationRunListeners getRunListeners(String[] args) {
         Class[] types = new Class[] { SpringApplication.class, String[].class };
         return new SpringApplicationRunListeners(logger,
               getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
      }
      private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
        //3.1获取类加载器
         ClassLoader classLoader = getClassLoader();
         // Use names and ensure unique to protect against duplicates
         //3.2 根据类型获取spring.factories中符合的类名
         Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
         //3.3 创建类实例
         List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
         //对实例进行排序
         AnnotationAwareOrderComparator.sort(instances);
         return instances;
      }
      SpringApplicationRunListeners类解读

      先看下SpringApplicationRunListeners类

      /**
       * A collection of {@link SpringApplicationRunListener}.
       *
       * @author Phillip Webb
       */
      class SpringApplicationRunListeners {
         private final Log log;
         private final List listeners;
         SpringApplicationRunListeners(Log log, Collection listeners) {
            this.log = log;
            this.listeners = new ArrayList<>(listeners);
         }

      SpringApplicationRunListeners类内部关联了SpringApplicationRunListener的集合,说白了就是用List集合存储了SpringApplicationRunListeners类,那么,我们就需要了解一下这个类是干嘛的

      老规矩,先把源码抬上来

      /**
       *//可以理解为Spring Boot应用的运行时监听器
       * Listener for the {@link SpringApplication} {@code run} method.
       *//SpringApplicationRunListener的构造器参数必须依次为SpringApplication和String[]类型
       * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
       * and should declare a public constructor that accepts a {@link SpringApplication}
       * instance and a {@code String[]} of arguments.
       *//每次运行的时候将会创建一个 SpringApplicationRunListener
        A new
       * {@link SpringApplicationRunListener} instance will be created for each run.
       *
       */
      public interface SpringApplicationRunListener {
         /**
          * Called immediately when the run method has first started. Can be used for very
          * early initialization.
          */
          //Spring应用刚启动
         void starting();
         /**
          * Called once the environment has been prepared, but before the
          * {@link ApplicationContext} has been created.
          * @param environment the environment
          */
          //ConfigurableEnvironment准备妥当,允许将其调整
         void environmentPrepared(ConfigurableEnvironment environment);
         /**
          * Called once the {@link ApplicationContext} has been created and prepared, but
          * before sources have been loaded.
          * @param context the application context
          */
          //ConfigurableApplicationContext准备妥当,允许将其调整
         void contextPrepared(ConfigurableApplicationContext context);
         /**
          * Called once the application context has been loaded but before it has been
          * refreshed.
          * @param context the application context
          */
          //ConfigurableApplicationContext已装载,但是任未启动
         void contextLoaded(ConfigurableApplicationContext context);
         /**
          * The context has been refreshed and the application has started but
          * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
          * ApplicationRunners} have not been called.
          * @param context the application context.
          * @since 2.0.0
          */
          //ConfigurableApplicationContext已启动,此时Spring Bean已初始化完成
         void started(ConfigurableApplicationContext context);
         /**
          * Called immediately before the run method finishes, when the application context has
          * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
          * {@link ApplicationRunner ApplicationRunners} have been called.
          * @param context the application context.
          * @since 2.0.0
          */
          //Spring应用正在运行
         void running(ConfigurableApplicationContext context);
         /**
          * Called when a failure occurs when running the application.
          * @param context the application context or {@code null} if a failure occurred before
          * the context was created
          * @param exception the failure
          * @since 2.0.0
          */
          //Spring应用运行失败
         void failed(ConfigurableApplicationContext context, Throwable exception);
      }

      单纯的看源码,是一个简单的接口,这时候我们可以看下作者给的注释。理解部分就直接加到上面源码中了。

      再看下他的实现类EventPublishingRunListener

      public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
         private final SpringApplication application;
         private final String[] args;
         private final SimpleApplicationEventMulticaster initialMulticaster;
         public EventPublishingRunListener(SpringApplication application, String[] args) {
            this.application = application;
            this.args = args;
            this.initialMulticaster = new SimpleApplicationEventMulticaster();
            for (ApplicationListener listener : application.getListeners()) {
               this.initialMulticaster.addApplicationListener(listener);
            }
         }

      这里我们看到两点:

      • 构造器参数和他实现的接口(上面刚分析了)注释中规定的一致

      • 将SpringApplication中的ApplicationListener实例列表全部添加到了SimpleApplicationEventMulticaster对象中

      SimpleApplicationEventMulticaster是Spring框架的一个监听类,用于发布Spring应用事件。因此EventPublishingRunListener实际充当了Spring Boot事件发布者的角色。

      这里我再跟进源码的时候发现,针对SpringBoot的事件/监听机制内容还是挺多的,我们在充分理解的时候需要先了解Spring的事件/监听机制,后面将两个结合后单独进行对比分析。

      3.1获取类加载器getClassLoader()
      ClassLoader classLoader = getClassLoader();
      public ClassLoader getClassLoader() {
        if (this.resourceLoader != null) {
           return this.resourceLoader.getClassLoader();
        }
        return ClassUtils.getDefaultClassLoader();
      }

      这里的类加载器获取首先是获取resourceLoader的类加载器,获取不到则获取默认的类加载器。 resourceLoader是资源加载器类,有具体的实现类。

      3.2 根据类型获取spring.factories中符合的类名
      SpringFactoriesLoader.loadFactoryNames(type, classLoader)
      public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
      //获取类型名称:org.springframework.context.ApplicationContextInitializer
         String factoryClassName = factoryClass.getName();
         return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
      }

      我们继续对loadSpringFactories追下去

      private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
          //从缓存里面获取
          MultiValueMap result = (MultiValueMap)cache.get(classLoader);
          if (result != null) {
              return result;
          } else {
              try {
              //执行classLoader.getResources("META-INF/spring.factories"),表示通过加载器获取META-INF/spring.factories下的资源
                  Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                  LinkedMultiValueMap result = new LinkedMultiValueMap();
                  while(urls.hasMoreElements()) {
                     //文件地址
                      URL url = (URL)urls.nextElement();
                      //从指定位置加载UrlResource
                      UrlResource resource = new UrlResource(url);
                      //加载里面的属性,属性见下图
                      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                      Iterator var6 = properties.entrySet().iterator();
                      while(var6.hasNext()) {
                          Entry entry = (Entry)var6.next();
                          //获取key值
                          String factoryClassName = ((String)entry.getKey()).trim();
                          //获取value值
                          String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                          int var10 = var9.length;
      //这里是将查询出来的key作为result的key,value转换成字符数组存放到result的value中
                          for(int var11 = 0; var11 < var10; ++var11) {
                              String factoryName = var9[var11];
                              result.add(factoryClassName, factoryName.trim());
                          }
                      }
                  }
                  //将结果集存入缓存中
                  cache.put(classLoader, result);
                  return result;
              } catch (IOException var13) {
                  throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
              }
          }
      }

      SpringBoot启动流程SpringApplication源码分析

      default V getOrDefault(Object key, V defaultValue) {
          V v;
          return (((v = get(key)) != null) || containsKey(key))
              ? v
              : defaultValue;
      }

      这个的意思是如果没有,则获取一个空的list

      3.3创建实例createSpringFactoriesInstances()

      这一步其实就是将上一步从META-INF/spring.factories加载进来的资源进行实例化。

      private  List createSpringFactoriesInstances()(Class type, Class[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set names) {
         List instances = new ArrayList<>(names.size());
         for (String name : names) {
            try {
            //根据类加载器获取指定类
               Class instanceClass = ClassUtils.forName(name, classLoader);
               Assert.isAssignable(type, instanceClass);
               //根据参数获取构造器
               Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
               //根据传入的构造器对象以及构造器所需的参数创建一个实例
               T instance = (T) BeanUtils.instantiateClass(constructor, args);
               //添加实例到集合中
               instances.add(instance);
            }
            catch (Throwable ex) {
               throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
         }
         return instances;
      }

      4、环境准备prepareEnvironment

      prepareEnvironment(listeners, applicationArguments)
      private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
         // Create and configure the environment
         //4.1 创建一个环境
         ConfigurableEnvironment environment = getOrCreateEnvironment();
         //4.2 配置环境
         configureEnvironment(environment, applicationArguments.getSourceArgs());
         //4.3 ConfigurationPropertySourcesPropertySource对象存入到第一位
         ConfigurationPropertySources.attach(environment);
         //listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)
         listeners.environmentPrepared(environment);
         // 将环境绑定到SpringApplication
         bindToSpringApplication(environment);
           // 如果是非web环境,将环境转换成StandardEnvironment
         if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                  deduceEnvironmentClass());
         }
          // 配置PropertySources对它自己的递归依赖
         ConfigurationPropertySources.attach(environment);
         return environment;
      }
      4.1创建一个环境getOrCreateEnvironment
      private ConfigurableEnvironment getOrCreateEnvironment() {
      //有的话,直接返回
        if (this.environment != null) {
           return this.environment;
        }
        //这里我们在上面见到过,通过WebApplicationType.deduceFromClasspath()方法获取的
        switch (this.webApplicationType) {
        case SERVLET:
           return new StandardServletEnvironment();
        case REACTIVE:
           return new StandardReactiveWebEnvironment();
        default:
           return new StandardEnvironment();
        }
      }

      这里创建了一个StandardServletEnvironment实例的环境 systemProperties用来封装了JDK相关的信息 如下图

      SpringBoot启动流程SpringApplication源码分析

      systemEnvironment用来封转环境相关的信息

      SpringBoot启动流程SpringApplication源码分析

      封装的还是挺详细的哈。

      4.2 配置环境
      protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
         if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
         }
         configurePropertySources(environment, args);
         configureProfiles(environment, args);
      }

      setConversionService(ConfigurableConversionService conversionService)方法继承于ConfigurablePropertyResolver接口, 该接口是PropertyResolver类型都将实现的配置接口。提供用于访问和自定义将属性值从一种类型转换为另一种类型时使用的ConversionService的工具。PropertyResolver是用于针对任何底层源解析属性的接口。

      configurePropertySources(environment, args);当前方法主要是将启动命令中的参数和run 方法中的参数封装为PropertySource。

      protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
      //获取所有的属性源,就是获取4.1的ConfigurableEnvironment上获取到的属性
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
           sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        //是否添加命令启动参数,addCommandLineProperties为true,表示需要添加,但是前提是你得配置了参数
        if (this.addCommandLineProperties && args.length > 0) {
           String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
           if (sources.contains(name)) {
              PropertySource source = sources.get(name);
              CompositePropertySource composite = new CompositePropertySource(name);
              composite.addPropertySource(
                    new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
              composite.addPropertySource(source);
              sources.replace(name, composite);
           }
           else {
              sources.addFirst(new SimpleCommandLinePropertySource(args));
           }
        }
      }

      configureProfiles(environment, args);环境配置

      protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
      //获取激活的环境
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        //设置当前的环境
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
      }
      4.3 ConfigurationPropertySourcesPropertySource对象存入
      public static void attach(Environment environment) {
         Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
         //获取所有的属性源,就是获取4.1的ConfigurableEnvironment上获取到的属性
         MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
         //判断是否有 属性 configurationProperties
         PropertySource attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
         if (attached != null && attached.getSource() != sources) {
            sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
            attached = null;
         }
         if (attached == null) {
         // 将sources封装成ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位置
            sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                  new SpringConfigurationPropertySources(sources)));
         }
      }
      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发
      相关文章 更多
      springBoot项目常用目录有哪些
      springBoot项目常用目录有哪些

      springBoot项目常用目录springBoot项目的目录结构及名规范介绍基于SpringBoot开发时的目录结构及命名规范,通过介绍能够为你解决,在实际项目中如何规划目录结构?如何命名目录更规范?各个目录分别代表什么含义?等三个问题。目录说明servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1|-servicex-common//模块2|-servicex-gateway//模块3|-serv

      解析SpringBoot和SpringMVC之间的异同
      解析SpringBoot和SpringMVC之间的异同

      解析SpringBoot和SpringMVC之间的异同SpringBoot和SpringMVC是Java领域中非常重要的开发框架。虽然它们都属于Spring框架的一部分,但是在使用和功能上有一些明显的区别。本文将对SpringBoot和SpringMVC进行比较,解析它们之间的异同。首先,让我们来了解一下SpringBoot。SpringBo

      探索SpringCloud和SpringBoot在微服务领域的不同应用方式
      探索SpringCloud和SpringBoot在微服务领域的不同应用方式

      近年来,随着云计算和分布式架构的兴起,微服务架构的应用越来越广泛。而SpringCloud和SpringBoot作为Java开发中的两个重要框架,对于微服务的实现起到了重要的作用。然而,很多人对于它们在微服务领域的不同应用方式还存在一定的疑惑。本文将从不同的角度探索SpringCloud和SpringBoot在微服务中的应用方式。首先,我们来了解一下Spri

      SpringBoot和SpringMVC有何区别?
      SpringBoot和SpringMVC有何区别?

      SpringBoot与SpringMVC的不同之处在哪里?SpringBoot和SpringMVC是两个非常流行的Java开发框架,用于构建Web应用程序。尽管它们经常分别被使用,但它们之间的不同之处也是很明显的。首先,SpringBoot可以被看作是一个Spring框架的扩展或者增强版。它旨在简化Spring应用程序的初始化和配置过程,以帮助开发人

      比较SpringBoot和SpringMVC的异同
      比较SpringBoot和SpringMVC的异同

      SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

      SpringBoot与SpringMVC的不同点是什么?
      SpringBoot与SpringMVC的不同点是什么?

      SpringBoot和SpringMVC是Java开发中常用的两个框架,它们都是由Spring框架所提供的,但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。一、SpringBoot的特点:简化配置:SpringBoot通过约定优于配置的原则,大大简化了项目的配置过程。它可以自动配置项目所需要的参数,开发人

      比较和选择指南:SpringCloud与SpringBoot的功能比较
      比较和选择指南:SpringCloud与SpringBoot的功能比较

      SpringCloud和SpringBoot是目前Java领域中最热门的开源框架,它们分别提供了一套完善的微服务架构和快速构建应用程序的解决方案。本文将对它们的功能进行比较,并给出选择指南,以帮助读者了解它们的优势和适用场景。SpringBoot是一个用于开发Java应用程序的框架,它提供了一个简化的开发流程,集成了大量常用的功能和组件,减少了开发者的工作量

      SpringCloud与SpringBoot:分析它们之间的相似与不同
      SpringCloud与SpringBoot:分析它们之间的相似与不同

      深入剖析SpringCloud和SpringBoot的异同引言:在构建企业级应用程序时,Spring框架已经成为开发者们的首选。它提供了一种简单而强大的方式来构建独立于平台的应用程序。Spring框架由两个核心模块组成:SpringBoot和SpringCloud。虽然它们都可以帮助开发者快速搭建应用程序,但它们的目标和用途存在一些差异。本文将深入剖析Spr

      从架构角度看SpringCloud与SpringBoot的差异
      从架构角度看SpringCloud与SpringBoot的差异

      从架构角度看SpringCloud与SpringBoot的差异引言:在当今的互联网时代,构建分布式系统已经成为了一种必要的需求。而SpringBoot和SpringCloud正是为了满足这个需求而诞生的。尽管它们都是由Spring框架所提供的解决方案,但从架构角度来看,它们存在着一些重要的差异。本文将从架构的角度出发,对SpringBoot和SpringCl

      Java API 开发中使用 SpringBoot 的利弊分析
      Java API 开发中使用 SpringBoot 的利弊分析

      JavaAPI开发中使用SpringBoot的利弊分析随着互联网的快速发展和技术的不断更新迭代,Java技术在开发中的广泛应用引起了人们的重视。而SpringBoot作为一种微服务架构的解决方案,已经逐渐成为Java开发中的热门选择。那么,在JavaAPI开发中使用SpringBoot,到底有哪些利弊呢?下面来分析一下。利:简化开发

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。