Jadual Kandungan
环境
SpringApplication类的静态run方法
构造SpringApplication对象
首先是webEnvironment:
然后是initializers:
接下来是成员变量listeners
最后是mainApplicationClass
SpringApplication对象的run方法
headless模式
SpringApplicationRunListeners
创建并刷新ApplicationContext
先来看看DefaultApplicationArguments吧:
创建并配置ApplicationConext的Environment
打印banner
创建ApplicationContext
Rumah Java javaTutorial 有关Spring Boot的启动流程的相关介绍

有关Spring Boot的启动流程的相关介绍

Aug 10, 2017 pm 02:50 PM
boot spring proses

环境

本文基于Spring Boot版本1.3.3, 使用了spring-boot-starter-web。

配置完成后,编写了代码如下:

@SpringBootApplicationpublic class Application {    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}@RestControllerpublic class RootController {    public static final String PATH_ROOT = "/";    @RequestMapping(PATH_ROOT)    public String welcome() {        return "Welcome!";
    }

}
Salin selepas log masuk

虽然只有几行代码,但是这已经是一个完整的Web程序,当访问url的path部分为"/"时,返回字符串"Welcome!"。

首先是一个非常普通的java程序入口,一个符合约定的静态main方法。在这个main方法中,调用了SpringApplication的静态run方法,并将Application类对象和main方法的参数args作为参数传递了进去。

然后是一个使用了两个Spring注解的RootController类,我们在main方法中,没有直接使用这个类。

SpringApplication类的静态run方法

以下代码摘自:org.springframework.boot.SpringApplicationpublic static ConfigurableApplicationContext run(Object source, String... args) {    return run(new Object[] { source }, args);
}public static ConfigurableApplicationContext run(Object[] sources, String[] args) {    return new SpringApplication(sources).run(args);
}
Salin selepas log masuk

在这个静态方法中,创建SpringApplication对象,并调用该对象的run方法。

构造SpringApplication对象

以下代码摘自:org.springframework.boot.SpringApplicationpublic SpringApplication(Object... sources) {    initialize(sources);
}private void initialize(Object[] sources) {    // 为成员变量sources赋值    if (sources != null && sources.length > 0) {        this.sources.addAll(Arrays.asList(sources));
    }    this.webEnvironment = deduceWebEnvironment();    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    this.mainApplicationClass = deduceMainApplicationClass();
}
Salin selepas log masuk

构造函数中调用initialize方法,初始化SpringApplication对象的成员变量sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的赋值比较简单,就是我们传给SpringApplication.run方法的参数。剩下的几个,我们依次来看看是怎么做的。

首先是webEnvironment:

以下代码摘自:org.springframework.boot.SpringApplicationprivate boolean webEnvironment; 

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",            "org.springframework.web.context.ConfigurableWebApplicationContext" };private void initialize(Object[] sources) {
    ...        // 为成员变量webEnvironment赋值        this.webEnvironment = deduceWebEnvironment();
    ...
}private boolean deduceWebEnvironment() {    for (String className : WEB_ENVIRONMENT_CLASSES) {        if (!ClassUtils.isPresent(className, null)) {            return false;
        }
    }    return true;
}
Salin selepas log masuk

可以看到webEnvironment是一个boolean,该成员变量用来表示当前应用程序是不是一个Web应用程序。那么怎么决定当前应用程序是否Web应用程序呢,是通过在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES这个数组中所包含的类,如果存在那么当前程序即是一个Web应用程序,反之则不然。
在本文的例子中webEnvironment的值为true。

然后是initializers:

initializers成员变量,是一个ApplicationContextInitializer类型对象的集合。 顾名思义,ApplicationContextInitializer是一个可以用来初始化ApplicationContext的接口。

以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationContextInitializer<?>> initializers;private void initialize(Object[] sources) {
    ...    // 为成员变量initializers赋值    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    ...
}public void setInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {    this.initializers = new ArrayList<ApplicationContextInitializer<?>>();    this.initializers.addAll(initializers);
}
Salin selepas log masuk

可以看到,关键是调用getSpringFactoriesInstances(ApplicationContextInitializer.class),来获取ApplicationContextInitializer类型对象的列表。

以下代码摘自:org.springframework.boot.SpringApplicationprivate <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {    return getSpringFactoriesInstances(type, new Class<?>[] {});
}private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);    return instances;
}
Salin selepas log masuk

在该方法中,首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。最后会将创建好的对象列表排序并返回。

以下代码摘自:org.springframework.core.io.support.SpringFactoriesLoaderpublic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }        return result;
    }    catch (IOException ex) {        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
Salin selepas log masuk

可以看到,是从一个名字叫spring.factories的资源文件中,读取key为org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分内容如下:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
Salin selepas log masuk

可以看到,最近的得到的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的名字。

接下来会调用createSpringFactoriesInstances来创建ApplicationContextInitializer实例。

以下代码摘自:org.springframework.boot.SpringApplicationprivate <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());    for (String name : names) {        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
            T instance = (T) constructor.newInstance(args);
            instances.add(instance);
        }        catch (Throwable ex) {            throw new IllegalArgumentException(                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }    return instances;
}
Salin selepas log masuk

所以在我们的例子中,SpringApplication对象的成员变量initalizers就被初始化为,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的对象组成的list。

下图画出了加载的ApplicationContextInitializer,并说明了他们的作用。至于何时应用他们,且听后面慢慢分解。


接下来是成员变量listeners

以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationListener<?>> listeners;private void initialize(Object[] sources) {
    ...    // 为成员变量listeners赋值    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
}public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {    this.listeners = new ArrayList<ApplicationListener<?>>();    this.listeners.addAll(listeners);
}
Salin selepas log masuk

listeners成员变量,是一个ApplicationListener类型对象的集合。可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。

看一下spring.factories中的相关内容:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
Salin selepas log masuk

也就是说,在我们的例子中,listener最终会被初始化为ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener这几个类的对象组成的list。

下图画出了加载的ApplicationListener,并说明了他们的作用。至于他们何时会被触发,等事件出现时,我们再说明。


最后是mainApplicationClass

以下代码摘自:org.springframework.boot.SpringApplicationprivate Class<?> mainApplicationClass;private void initialize(Object[] sources) {
    ...    // 为成员变量mainApplicationClass赋值    this.mainApplicationClass = deduceMainApplicationClass();
    ...
}private Class<?> deduceMainApplicationClass() {    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();        for (StackTraceElement stackTraceElement : stackTrace) {            if ("main".equals(stackTraceElement.getMethodName())) {                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }    catch (ClassNotFoundException ex) {        // Swallow and continue
    }    return null;
}
Salin selepas log masuk

在deduceMainApplicationClass方法中,通过获取当前调用栈,找到入口方法main所在的类,并将其复制给SpringApplication对象的成员变量mainApplicationClass。在我们的例子中mainApplicationClass即是我们自己编写的Application类。

SpringApplication对象的run方法

经过上面的初始化过程,我们已经有了一个SpringApplication对象,根据SpringApplication类的静态run方法一节中的分析,接下来会调用SpringApplication对象的run方法。我们接下来就分析这个对象的run方法。

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }        return context;
    }    catch (Throwable ex) {        handleRunFailure(context, listeners, ex);        throw new IllegalStateException(ex);
    }
}
Salin selepas log masuk
  • 可变个数参数args即是我们整个应用程序的入口main方法的参数,在我们的例子中,参数个数为零。

  • StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。

SpringApplication对象的run方法创建并刷新ApplicationContext,算是开始进入正题了。下面按照执行顺序,介绍该方法所做的工作。

headless模式

以下代码摘自:org.springframework.boot.SpringApplicationprivate static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";private boolean headless = true;public ConfigurableApplicationContext run(String... args) {
    ...    //设置headless模式        configureHeadlessProperty();
    ...
}private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
            SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
Salin selepas log masuk

实际上是就是设置系统属性java.awt.headless,在我们的例子中该属性会被设置为true,因为我们开发的是服务器程序,一般运行在没有显示器和键盘的环境。关于java中的headless模式,更多信息可以参考这里。

SpringApplicationRunListeners

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    ...
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();    /**         * 创建并刷新ApplicationContext         * context = createAndRefreshContext(listeners, applicationArguments);         **/
    listeners.finished(context, null);
    ...
}private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}
Salin selepas log masuk

run方法中,加载了一系列SpringApplicationRunListener对象,在创建和更新ApplicationContext方法前后分别调用了listeners对象的started方法和finished方法, 并在创建和刷新ApplicationContext时,将listeners作为参数传递到了createAndRefreshContext方法中,以便在创建和刷新ApplicationContext的不同阶段,调用listeners的相应方法以执行操作。所以,所谓的SpringApplicationRunListeners实际上就是在SpringApplication对象的run方法执行的不同阶段,去执行一些操作,并且这些操作是可配置的。

同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。那么加载了什么,就可以从spring.factories文件中看到了:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
Salin selepas log masuk

可以看到,在我们的例子中加载的是org.springframework.boot.context.event.EventPublishingRunListener。我们看一看这个SpringApplicationRunListener究竟做了点什么工作了?

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListenerpublic EventPublishingRunListener(SpringApplication application, String[] args) {    this.application = application;    this.args = args;    this.multicaster = new SimpleApplicationEventMulticaster();    for (ApplicationListener<?> listener : application.getListeners()) {        this.multicaster.addApplicationListener(listener);
    }
}@Overridepublic void started() {    publishEvent(new ApplicationStartedEvent(this.application, this.args));
}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {    publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
            environment));
}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {    registerApplicationEventMulticaster(context);
}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {    for (ApplicationListener<?> listener : this.application.getListeners()) {        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        context.addApplicationListener(listener);
    }    publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}@Overridepublic void finished(ConfigurableApplicationContext context, Throwable exception) {    publishEvent(getFinishedEvent(context, exception));
}
Salin selepas log masuk

EventPublishingRunListener在对象初始化时,将SpringApplication对象的成员变量listeners全都保存下来,然后在自己的public方法被调用时,发布相应的事件,或执行相应的操作。可以说这个RunListener是在SpringApplication对象的run方法执行到不同的阶段时,发布相应的event给SpringApplication对象的成员变量listeners中记录的事件监听器。

下图画出了SpringApplicationRunListeners相关的类结构,虽然我们的例子中只有一个SpringApplicationRunListener,但在这样的设计下,想要扩展是非常容易的!


接下来,我们看一下在调用listeners的started方法。在我们的例子中,也就是发布了ApplicationStartedEvent时,我们已经加载的事件监听器都做了什么操作。至于其它事件的发布,我们按照代码执行的顺序在后面的章节在介绍。

  • ParentContextCloserApplicationListener不监听ApplicationStartedEvent,没有操作;

  • FileEncodingApplicationListener不监听ApplicationStartedEvent,没有操作;

  • AnsiOutputApplicationListener不监听ApplicationStartedEvent,没有操作;

  • ConfigFileApplicationListener不监听ApplicationStartedEvent,没有操作;

  • DelegatingApplicationListener不监听ApplicationStartedEvent,没有操作;

  • LiquibaseServiceLocatorApplicationListener监听ApplicationStartedEvent,会检查classpath中是否有liquibase.servicelocator.ServiceLocator并做相应操作;

以下代码摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {    if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) {        new LiquibasePresent().replaceServiceLocator();
    }
}
Salin selepas log masuk

我们的例子中,classpath中不存在liquibase,所以不执行任何操作。

  • ClasspathLoggingApplicationListener监听ApplicationStartedEvent,会打印classpath到debug日志;

@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        if (this.logger.isDebugEnabled()) {            this.logger.debug("Application started with classpath: " + getClasspath());
    }
    ...
}private String getClasspath() {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();    if (classLoader instanceof URLClassLoader) {        return Arrays.toString(((URLClassLoader) classLoader).getURLs());
    }    return "unknown";
}
Salin selepas log masuk

因为是debug级别的日志,而SpringBoot的默认日志级别是info级,所以我们在控制台不会看到classpath的输出。

  • LoggingApplicationListener监听ApplicationStartedEvent,会根据classpath中的类情况创建相应的日志系统对象,并执行一些初始化之前的操作;

@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        onApplicationStartedEvent((ApplicationStartedEvent) event);
    }
    ...
}private void onApplicationStartedEvent(ApplicationStartedEvent event) {    this.loggingSystem = LoggingSystem
            .get(event.getSpringApplication().getClassLoader());    this.loggingSystem.beforeInitialize();
}
Salin selepas log masuk

我们的例子中,创建的是org.springframework.boot.logging.logback.LogbackLoggingSystem类的对象,Logback是SpringBoot默认采用的日志系统。下图画出了SpringBoot中的日志系统体系:


好了,ApplicationStartedEvent事件的处理这样就结束了。以后在介绍事件处理的时候,我们只介绍监听该事件的监听器的操作,而不监听的,就不再说明了。

创建并刷新ApplicationContext

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    ...    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);        afterRefresh(context, applicationArguments);
        ...
    }    catch (Throwable ex) {        handleRunFailure(context, listeners, ex);        throw new IllegalStateException(ex);
    }
}
Salin selepas log masuk

首先是创建一个DefaultApplicationArguments对象,之后调用createAndRefreshContext方法创建并刷新一个ApplicationContext,最后调用afterRefresh方法在刷新之后做一些操作。

先来看看DefaultApplicationArguments吧:

以下代码摘自:org.springframework.boot.DefaultApplicationArgumentsDefaultApplicationArguments(String[] args) {
    Assert.notNull(args, "Args must not be null");    this.source = new Source(args);    this.args = args;
}private static class Source extends SimpleCommandLinePropertySource {

    Source(String[] args) {        super(args);
    }
    ...
}

以下代码摘自:org.springframework.core.env.SimpleCommandLinePropertySourcepublic SimpleCommandLinePropertySource(String... args) {    super(new SimpleCommandLineArgsParser().parse(args));
}
Salin selepas log masuk

可以看到是把main函数的args参数当做一个PropertySource来解析。我们的例子中,args的长度为0,所以这里创建的DefaultApplicationArguments也没有实际的内容。

创建并配置ApplicationConext的Environment

以下代码摘自:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment environment;private boolean webEnvironment;private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;    // 创建并配置Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);    if (isWebEnvironment(environment) && !this.webEnvironment) {
        environment = convertToStandardEnvironment(environment);
    }

    ...    return context;
}private ConfigurableEnvironment getOrCreateEnvironment() {    if (this.environment != null) {        return this.environment;
    }    if (this.webEnvironment) {        return new StandardServletEnvironment();
    }    return new StandardEnvironment();
}
Salin selepas log masuk

Spring Application的Environment代表着程序运行的环境,主要包含了两种信息,一种是profiles,用来描述哪些bean definitions是可用的;一种是properties,用来描述系统的配置,其来源可能是配置文件、JVM属性文件、操作系统环境变量等等。

首先要调用getOrCreateEnvironment方法获取一个Environment对象。在我们的例子中,执行到此处时,environment成员变量为null,而webEnvironment成员变量的值为true,所以会创建一个StandardServletEnvironment对象并返回。

之后是调用configureEnvironment方法来配置上一步获取的Environment对象,代码如下:

以下代码摘自:org.springframework.boot.SpringApplicationprivate Map<String, Object> defaultProperties;private boolean addCommandLineProperties = true;private Set<String> additionalProfiles = new HashSet<String>();protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {    configurePropertySources(environment, args);    configureProfiles(environment, args);
}protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(                new MapPropertySource("defaultProperties", this.defaultProperties));
    }    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(
                    name + "-" + args.hashCode(), args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }        else {
            sources.addFirst(new SimpleCommandLinePropertySource(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<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
}
Salin selepas log masuk

configureEnvironment方法先是调用configurePropertySources来配置properties,然后调用configureProfiles来配置profiles。

configurePropertySources首先查看SpringApplication对象的成员变量defaultProperties,如果该变量非null且内容非空,则将其加入到Environment的PropertySource列表的最后。然后查看SpringApplication对象的成员变量addCommandLineProperties和main函数的参数args,如果设置了addCommandLineProperties=true,且args个数大于0,那么就构造一个由main函数的参数组成的PropertySource放到Environment的PropertySource列表的最前面(这就能保证,我们通过main函数的参数来做的配置是最优先的,可以覆盖其他配置)。在我们的例子中,由于没有配置defaultProperties且main函数的参数args个数为0,所以这个函数什么也不做。

configureProfiles首先会读取Properties中key为spring.profiles.active的配置项,配置到Environment,然后再将SpringApplication对象的成员变量additionalProfiles加入到Environment的active profiles配置中。在我们的例子中,配置文件里没有spring.profiles.active的配置项,而SpringApplication对象的成员变量additionalProfiles也是一个空的集合,所以这个函数没有配置任何active profile。

到现在,Environment就算是配置完成了。接下来调用SpringApplicationRunListeners类的对象listeners发布ApplicationEnvironmentPreparedEvent事件:

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListener@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {    publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
            environment));
}
Salin selepas log masuk

好,现在来看一看我们加载的ApplicationListener对象都有哪些响应了这个事件,做了什么操作:

  • FileEncodingApplicationListener响应该事件,检查file.encoding配置是否与spring.mandatory_file_encoding一致:

以下代码摘自:org.springframework.boot.context.FileEncodingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
            event.getEnvironment(), "spring.");    if (resolver.containsProperty("mandatoryFileEncoding")) {
        String encoding = System.getProperty("file.encoding");
        String desired = resolver.getProperty("mandatoryFileEncoding");        if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
            logger.error("System property &#39;file.encoding&#39; is currently &#39;" + encoding
                    + "&#39;. It should be &#39;" + desired
                    + "&#39; (as defined in &#39;spring.mandatoryFileEncoding&#39;).");
            logger.error("Environment variable LANG is &#39;" + System.getenv("LANG")
                    + "&#39;. You could use a locale setting that matches encoding=&#39;"
                    + desired + "&#39;.");
            logger.error("Environment variable LC_ALL is &#39;" + System.getenv("LC_ALL")
                    + "&#39;. You could use a locale setting that matches encoding=&#39;"
                    + desired + "&#39;.");            throw new IllegalStateException(                    "The Java Virtual Machine has not been configured to use the "
                            + "desired default character encoding (" + desired
                            + ").");
        }
    }
}
Salin selepas log masuk

在我们的例子中,因为没有spring.mandatory_file_encoding的配置,所以这个响应方法什么都不做。

  • AnsiOutputApplicationListener响应该事件,根据spring.output.ansi.enabled和spring.output.ansi.console-available对AnsiOutput类做相应配置:

以下代码摘自:org.springframework.boot.context.config.AnsiOutputApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
            event.getEnvironment(), "spring.output.ansi.");    if (resolver.containsProperty("enabled")) {
        String enabled = resolver.getProperty("enabled");
        AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase()));
    }    if (resolver.containsProperty("console-available")) {
        AnsiOutput.setConsoleAvailable(
                resolver.getProperty("console-available", Boolean.class));
    }
}
Salin selepas log masuk

我们的例子中,这两项配置都是空的,所以这个响应方法什么都不做。

  • ConfigFileApplicationListener加载该事件,从一些约定的位置加载一些配置文件,而且这些位置是可配置的。

以下代码摘自:org.springframework.boot.context.config.ConfigFileApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }    if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent(event);
    }
}private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),
                event.getSpringApplication());
    }
}List<EnvironmentPostProcessor> loadPostProcessors() {    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,            getClass().getClassLoader());
}


以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
Salin selepas log masuk

可以看到,ConfigFileApplicationListener从META-INF/spring.factories文件中读取EnvironmentPostProcessor配置,加载相应的EnvironmentPostProcessor类的对象,并调用其postProcessEnvironment方法。在我们的例子中,会加载CloudFoundryVcapEnvironmentPostProcessor和SpringApplicationJsonEnvironmentPostProcessor并执行,由于我们的例子中没有CloudFoundry和Json的配置,所以这个响应,不会加载任何的配置文件到Environment中来。

  • DelegatingApplicationListener响应该事件,将配置文件中key为context.listener.classes的配置项,加载在成员变量multicaster中:

以下内容摘自:org.springframework.boot.context.config.DelegatingApplicationListenerprivate static final String PROPERTY_NAME = "context.listener.classes";private SimpleApplicationEventMulticaster multicaster;@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
                ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());        if (delegates.isEmpty()) {            return;
        }        this.multicaster = new SimpleApplicationEventMulticaster();        for (ApplicationListener<ApplicationEvent> listener : delegates) {            this.multicaster.addApplicationListener(listener);
        }
    }    if (this.multicaster != null) {        this.multicaster.multicastEvent(event);
    }
}@SuppressWarnings("unchecked")private List<ApplicationListener<ApplicationEvent>> getListeners(
        ConfigurableEnvironment env) {
    String classNames = env.getProperty(PROPERTY_NAME);
    List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>();    if (StringUtils.hasLength(classNames)) {        for (String className : StringUtils.commaDelimitedListToSet(classNames)) {            try {
                Class<?> clazz = ClassUtils.forName(className,
                        ClassUtils.getDefaultClassLoader());
                Assert.isAssignable(ApplicationListener.class, clazz, "class ["
                        + className + "] must implement ApplicationListener");
                listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
                        .instantiateClass(clazz));
            }            catch (Exception ex) {                throw new ApplicationContextException(                        "Failed to load context listener class [" + className + "]",
                        ex);
            }
        }
    }
    AnnotationAwareOrderComparator.sort(listeners);    return listeners;
}
Salin selepas log masuk

我们的例子中,因为没有key为context.listener.classes的Property,所以不会加载任何listener到该监听器中。

  • LoggingApplicationListener响应该事件,并对在ApplicationStarted时加载的LoggingSystem做一些初始化工作:

以下代码摘自:org.springframework.boot.logging.LoggingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        onApplicationStartedEvent((ApplicationStartedEvent) event);
    }    else if (event instanceof ApplicationEnvironmentPreparedEvent) {        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }    else if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
            .getApplicationContext().getParent() == null) {        onContextClosedEvent();
    }
}private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {    if (this.loggingSystem == null) {        this.loggingSystem = LoggingSystem
                .get(event.getSpringApplication().getClassLoader());
    }    initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}protected void initialize(ConfigurableEnvironment environment,
        ClassLoader classLoader) {
    LogFile logFile = LogFile.get(environment);    setSystemProperties(environment, logFile);    initializeEarlyLoggingLevel(environment);    initializeSystem(environment, this.loggingSystem, logFile);    initializeFinalLoggingLevels(environment, this.loggingSystem);    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
Salin selepas log masuk

在我们的例子中,是对加载的LogbackLoggingSystem做一些初始化工作。关于日志系统更详细的讨论,值得再写一篇文章,就不在这里展开讨论了。

打印banner

以下代码摘自:org.springframework.boot.SpringApplicationprivate Banner banner;private Banner.Mode bannerMode = Banner.Mode.CONSOLE;public static final String BANNER_LOCATION_PROPERTY = "banner.location";public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt";private static final Banner DEFAULT_BANNER = new SpringBootBanner();private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {

    ...    if (this.bannerMode != Banner.Mode.OFF) {        printBanner(environment);
    }
    ...
}protected void printBanner(Environment environment) {
    Banner selectedBanner = selectBanner(environment);    if (this.bannerMode == Banner.Mode.LOG) {        try {
            logger.info(createStringFromBanner(selectedBanner, environment));
        }        catch (UnsupportedEncodingException ex) {
            logger.warn("Failed to create String for banner", ex);
        }
    }    else {
        selectedBanner.printBanner(environment, this.mainApplicationClass,
                System.out);
    }
}private Banner selectBanner(Environment environment) {
    String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
            BANNER_LOCATION_PROPERTY_VALUE);
    ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader());
    Resource resource = resourceLoader.getResource(location);    if (resource.exists()) {        return new ResourceBanner(resource);
    }    if (this.banner != null) {        return this.banner;
    }    return DEFAULT_BANNER;
}private String createStringFromBanner(Banner banner, Environment environment)        throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos));
    String charset = environment.getProperty("banner.charset", "UTF-8");    return baos.toString(charset);
}
Salin selepas log masuk

printBanner方法中,首先会调用selectBanner方法得到一个banner对象,然后判断bannerMode的类型,如果是Banner.Mode.LOG,那么将banner对象转换为字符串,打印一条info日志,否则的话,调用banner对象的printbanner方法,将banner打印到标准输出System.out。

在我们的例子中,bannerMode是Banner.Mode.Console,而且也不曾提供过banner.txt这样的资源文件。所以selectBanner方法中得到到便是默认的banner对象,即SpringBootBanner类的对象:

以下代码摘自:org.springframework.boot.SpringBootBannerprivate static final String[] BANNER = { "",        "  .   ____          _            __ _ _",        " /\\\\ / ___&#39;_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",        "( ( )\\___ | &#39;_ | &#39;_| | &#39;_ \\/ _` | \\ \\ \\ \\",        " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",        "  &#39;  |____| .__|_| |_|_| |_\\__, | / / / /",        " =========|_|==============|___/=/_/_/_/" };private static final String SPRING_BOOT = " :: Spring Boot :: ";private static final int STRAP_LINE_SIZE = 42;@Overridepublic void printBanner(Environment environment, Class<?> sourceClass,
        PrintStream printStream) {    for (String line : BANNER) {
        printStream.println(line);
    }
    String version = SpringBootVersion.getVersion();
    version = (version == null ? "" : " (v" + version + ")");
    String padding = "";    while (padding.length() < STRAP_LINE_SIZE
            - (version.length() + SPRING_BOOT.length())) {
        padding += " ";
    }

    printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
            AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));
    printStream.println();
}
Salin selepas log masuk

先打印个Spring的图形,然后打印个Spring Boot的文本,再然后打印一下Spring Boot的版本。会在控制台看到如下输出:

以下内容是程序启动后在console的输出:

  .   ____          _            __ _ _
 /\\ / ___&#39;_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | &#39;_ | &#39;_| | &#39;_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  &#39;  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)
Salin selepas log masuk

我的天。分析启动流程这么久,终于在屏幕有一行输出了,不容易。

创建ApplicationContext

private Class<? extends ConfigurableApplicationContext> applicationContextClass;public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;

    ...

    context = createApplicationContext();
    context.setEnvironment(environment);    postProcessApplicationContext(context);    applyInitializers(context);
    listeners.contextPrepared(context);    if (this.logStartupInfo) {        logStartupInfo(context.getParent() == null);        logStartupProfileInfo(context);
    }

    ...    return context;
}protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;    if (contextClass == null) {        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }        catch (ClassNotFoundException ex) {            throw new IllegalStateException(                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
Salin selepas log masuk

createAndRefreshContext中调用createApplicationContext获取创建ApplicationContext,可以看到,当检测到本次程序是一个web应用程序(成员变量webEnvironment为true)的时候,就加载类DEFAULT_WEB_CONTEXT_CLASS,否则的话加载DEFAULT_CONTEXT_CLASS。我们的例子是一个web应用程序,所以会加载DEFAULT_WEB_CONTEXT_CLASS,也就是org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext。我们先来看一看这个AnnotationConfigEmbeddedWebApplicationContext具体有什么功能。下图画出了它的继承体系。


可以看到我们加载的这个AnnotationConfigEmbeddedWebApplicationContext类,从名字就可以看出来,首先是一个WebApplicationContext实现了WebApplicationContext接口,然后是一个EmbeddedWebApplicationContext,这意味着它会自动创建并初始化一个EmbeddedServletContainer,同时还支持AnnotationConfig,会将使用注解标注的bean注册到ApplicationContext中。更详细的过程,后面在例子中再一一剖析。

可以看到在加载类对象AnnotationConfigEmbeddedWebApplicationContext之后,createApplicationContext方法中紧接着调用BeanUtils的instantiate方法来创建ApplicationContext对象,其代码如下:

以下代码摘自:org.springframework.beans.BeanUtilspublic static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
    Assert.notNull(clazz, "Class must not be null");    if (clazz.isInterface()) {        throw new BeanInstantiationException(clazz, "Specified class is an interface");
    }    try {        return clazz.newInstance();
    }    catch (InstantiationException ex) {        throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
    }    catch (IllegalAccessException ex) {        throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
    }
}
Salin selepas log masuk

通过调用Class对象的newInstance()方法来实例化对象,这等同于直接调用类的空的构造方法,所以我们来看AnnotationConfigEmbeddedWebApplicationContext类的构造方法:

以下代码摘自:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContextpublic AnnotationConfigEmbeddedWebApplicationContext() {    this.reader = new AnnotatedBeanDefinitionReader(this);    this.scanner = new ClassPathBeanDefinitionScanner(this);
}@Overridepublic void setEnvironment(ConfigurableEnvironment environment) {    super.setEnvironment(environment);    this.reader.setEnvironment(environment);    this.scanner.setEnvironment(environment);
}
Salin selepas log masuk

构造方法中初始化了两个成员变量,类型分别为AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加载使用注解的bean定义。

这样ApplicationContext对象就创建出来了,在createAndRefreshContext方法中创建了ApplicationContext对象之后会紧接着调用其setEnvironment将我们之前准备好的Environment对象赋值进去。之后分别调用postProcessApplicationContext和applyInitializers做一些处理和初始化的操作。

先来看看postProcessApplicationContext:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {    if (this.webEnvironment) {        if (context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;            if (this.beanNameGenerator != null) {
                configurableContext.getBeanFactory().registerSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,                        this.beanNameGenerator);
            }
        }
    }    if (this.resourceLoader != null) {        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}
Salin selepas log masuk

如果成员变量beanNameGenerator不为Null,那么为ApplicationContext对象注册beanNameGenerator bean。如果成员变量resourceLoader不为null,则为ApplicationContext对象设置ResourceLoader。我们的例子中,这两个成员变量都为Null,所以什么都不做。

之后是applyInitializers方法:

protected void applyInitializers(ConfigurableApplicationContext context) {    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}public Set<ApplicationContextInitializer<?>> getInitializers() {    return asUnmodifiableOrderedSet(this.initializers);
}private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
    List<E> list = new ArrayList<E>();
    list.addAll(elements);
    Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);    return new LinkedHashSet<E>(list);
}
Salin selepas log masuk

Atas ialah kandungan terperinci 有关Spring Boot的启动流程的相关介绍. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Tutorial Java
1657
14
Tutorial PHP
1257
29
Tutorial C#
1230
24
Fail ISO Windows terlalu besar ralat BootCamp [Tetap] Fail ISO Windows terlalu besar ralat BootCamp [Tetap] Feb 19, 2024 pm 12:30 PM

Jika anda mendapat mesej ralat "Fail ISO Windows terlalu besar" apabila menggunakan BootCampAssistant pada komputer Mac, ini mungkin kerana saiz fail ISO melebihi had yang disokong oleh BootCampAssistant. Penyelesaian kepada masalah ini ialah menggunakan alat lain untuk memampatkan saiz fail ISO untuk memastikan ia boleh diproses dalam Pembantu BootCamp. BootCampAssistant ialah alat mudah yang disediakan oleh Apple untuk memasang dan menjalankan sistem pengendalian Windows pada komputer Mac. Ia membantu pengguna menyediakan sistem dwi-but, membolehkan mereka dengan mudah memilih untuk menggunakan MacOS atau Wind pada permulaan

Paradigma pengaturcaraan baharu, apabila Spring Boot bertemu OpenAI Paradigma pengaturcaraan baharu, apabila Spring Boot bertemu OpenAI Feb 01, 2024 pm 09:18 PM

Pada tahun 2023, teknologi AI telah menjadi topik hangat dan memberi impak besar kepada pelbagai industri, terutamanya dalam bidang pengaturcaraan. Orang ramai semakin menyedari kepentingan teknologi AI, dan komuniti Spring tidak terkecuali. Dengan kemajuan berterusan teknologi GenAI (General Artificial Intelligence), ia menjadi penting dan mendesak untuk memudahkan penciptaan aplikasi dengan fungsi AI. Dengan latar belakang ini, "SpringAI" muncul, bertujuan untuk memudahkan proses membangunkan aplikasi berfungsi AI, menjadikannya mudah dan intuitif serta mengelakkan kerumitan yang tidak perlu. Melalui "SpringAI", pembangun boleh membina aplikasi dengan lebih mudah dengan fungsi AI, menjadikannya lebih mudah untuk digunakan dan dikendalikan.

Gunakan Spring Boot dan Spring AI untuk membina aplikasi kecerdasan buatan generatif Gunakan Spring Boot dan Spring AI untuk membina aplikasi kecerdasan buatan generatif Apr 28, 2024 am 11:46 AM

Sebagai peneraju industri, Spring+AI menyediakan penyelesaian terkemuka untuk pelbagai industri melalui API yang berkuasa, fleksibel dan fungsi lanjutannya. Dalam topik ini, kami akan menyelidiki contoh aplikasi Spring+AI dalam pelbagai bidang Setiap kes akan menunjukkan cara Spring+AI memenuhi keperluan khusus, mencapai matlamat dan meluaskan LESSONSLEARNED ini kepada rangkaian aplikasi yang lebih luas. Saya harap topik ini dapat memberi inspirasi kepada anda untuk memahami dan menggunakan kemungkinan Spring+AI yang tidak terhingga dengan lebih mendalam. Rangka kerja Spring mempunyai sejarah lebih daripada 20 tahun dalam bidang pembangunan perisian, dan sudah 10 tahun sejak versi Spring Boot 1.0 dikeluarkan. Sekarang, tiada siapa boleh mempertikaikan Spring itu

Apakah kaedah pelaksanaan transaksi program musim bunga? Apakah kaedah pelaksanaan transaksi program musim bunga? Jan 08, 2024 am 10:23 AM

Cara melaksanakan transaksi terprogram musim bunga: 1. Gunakan TransactionTemplate 2. Gunakan TransactionCallback dan TransactionCallbackWithoutResult 3. Gunakan anotasi Transaksional;

Bagaimana untuk membuka berbilang akaun Toutiao? Apakah proses untuk memohon akaun Toutiao? Bagaimana untuk membuka berbilang akaun Toutiao? Apakah proses untuk memohon akaun Toutiao? Mar 22, 2024 am 11:00 AM

Dengan populariti Internet mudah alih, Toutiao telah menjadi salah satu platform maklumat berita paling popular di negara saya. Ramai pengguna berharap untuk mempunyai berbilang akaun pada platform Toutiao untuk memenuhi keperluan yang berbeza. Jadi, bagaimana untuk membuka berbilang akaun Toutiao? Artikel ini akan memperkenalkan secara terperinci kaedah dan proses permohonan membuka berbilang akaun Toutiao. 1. Bagaimana untuk membuka berbilang akaun Toutiao? Kaedah membuka berbilang akaun Toutiao adalah seperti berikut: Pada platform Toutiao, pengguna boleh mendaftar akaun melalui nombor telefon mudah alih yang berbeza. Setiap nombor telefon mudah alih hanya boleh mendaftar satu akaun Toutiao, yang bermaksud pengguna boleh menggunakan berbilang nombor telefon mudah alih untuk mendaftar berbilang akaun. 2. Pendaftaran e-mel: Gunakan alamat e-mel yang berbeza untuk mendaftar akaun Toutiao. Sama seperti pendaftaran nombor telefon mudah alih, setiap alamat e-mel juga boleh mendaftar akaun Toutiao. 3. Log masuk dengan akaun pihak ketiga

Cara menetapkan tahap pengasingan transaksi dalam Spring Cara menetapkan tahap pengasingan transaksi dalam Spring Jan 26, 2024 pm 05:38 PM

Cara menetapkan tahap pengasingan transaksi dalam Spring: 1. Gunakan anotasi @Transactional 2. Tetapkan dalam fail konfigurasi Spring 3. Gunakan PlatformTransactionManager 4. Tetapkan dalam kelas konfigurasi Java. Pengenalan terperinci: 1. Gunakan anotasi @Transactional, tambah anotasi @Transactional pada kelas atau kaedah yang memerlukan pengurusan transaksi dan tetapkan tahap pengasingan dalam atribut 2. Dalam fail konfigurasi Spring, dsb.

7 anotasi yang paling biasa digunakan dalam Spring, organisasi paling berkuasa dalam sejarah! 7 anotasi yang paling biasa digunakan dalam Spring, organisasi paling berkuasa dalam sejarah! Jul 26, 2023 pm 04:38 PM

Dengan kemas kini dan lelaran teknologi, Java5.0 mula menyokong anotasi. Sebagai rangka kerja terkemuka di Java, spring perlahan-lahan mula meninggalkan konfigurasi xml sejak ia dikemas kini kepada versi 2.5, dan lebih banyak anotasi digunakan untuk mengawal rangka kerja spring.

Adakah sauh tidur Douyin menguntungkan? Apakah prosedur khusus untuk penstriman langsung tidur? Adakah sauh tidur Douyin menguntungkan? Apakah prosedur khusus untuk penstriman langsung tidur? Mar 21, 2024 pm 04:41 PM

Dalam masyarakat yang serba pantas hari ini, masalah kualiti tidur melanda semakin ramai orang. Untuk meningkatkan kualiti tidur pengguna, sekumpulan sauh tidur khas telah muncul di platform Douyin. Mereka berinteraksi dengan pengguna melalui siaran langsung, berkongsi petua tidur dan menyediakan muzik dan bunyi yang menenangkan untuk membantu penonton tidur dengan tenang. Jadi, adakah sauh tidur ini menguntungkan? Artikel ini akan memberi tumpuan kepada isu ini. 1. Adakah sauh tidur Douyin menguntungkan? Douyin sleep anchor memang boleh mendapat keuntungan tertentu. Pertama, mereka boleh menerima hadiah dan pemindahan melalui fungsi pemberian tip dalam bilik siaran langsung, dan faedah ini bergantung pada bilangan peminat dan kepuasan penonton mereka. Kedua, platform Douyin akan memberikan sauh bahagian tertentu berdasarkan bilangan tontonan, suka, perkongsian dan data lain siaran langsung. Beberapa sauh tidur juga akan

See all articles