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

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

Aug 10, 2017 pm 02:50 PM
boot spring 流程

环境

本文基于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!";
    }

}
登录后复制

虽然只有几行代码,但是这已经是一个完整的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);
}
登录后复制

在这个静态方法中,创建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();
}
登录后复制

构造函数中调用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;
}
登录后复制

可以看到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);
}
登录后复制

可以看到,关键是调用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;
}
登录后复制

在该方法中,首先通过调用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);
    }
}
登录后复制

可以看到,是从一个名字叫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
登录后复制

可以看到,最近的得到的,是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;
}
登录后复制

所以在我们的例子中,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);
}
登录后复制

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
登录后复制

也就是说,在我们的例子中,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;
}
登录后复制

在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);
    }
}
登录后复制
  • 可变个数参数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)));
}
登录后复制

实际上是就是设置系统属性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));
}
登录后复制

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
登录后复制

可以看到,在我们的例子中加载的是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));
}
登录后复制

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();
    }
}
登录后复制

我们的例子中,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";
}
登录后复制

因为是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();
}
登录后复制

我们的例子中,创建的是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);
    }
}
登录后复制

首先是创建一个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));
}
登录后复制

可以看到是把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();
}
登录后复制

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()]));
}
登录后复制

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));
}
登录后复制

好,现在来看一看我们加载的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
                            + ").");
        }
    }
}
登录后复制

在我们的例子中,因为没有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));
    }
}
登录后复制

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

  • 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
登录后复制

可以看到,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;
}
登录后复制

我们的例子中,因为没有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);
}
登录后复制

在我们的例子中,是对加载的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);
}
登录后复制

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();
}
登录后复制

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

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

  .   ____          _            __ _ _
 /\\ / ___&#39;_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | &#39;_ | &#39;_| | &#39;_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  &#39;  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)
登录后复制

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

创建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);
}
登录后复制

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);
    }
}
登录后复制

通过调用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);
}
登录后复制

构造方法中初始化了两个成员变量,类型分别为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());
        }
    }
}
登录后复制

如果成员变量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);
}
登录后复制

以上是有关Spring Boot的启动流程的相关介绍的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Windows ISO文件太大BootCamp错误[修复] Windows ISO文件太大BootCamp错误[修复] Feb 19, 2024 pm 12:30 PM

如果在Mac电脑上使用BootCampAssistant时出现"TheWindowsISO文件太大"的错误信息,这可能是由于ISO文件大小超过BootCampAssistant所支持的限制。解决这个问题的方法是使用其他工具来压缩ISO文件大小,以确保其在BootCampAssistant中能够被处理。BootCampAssistant是苹果提供的一种方便的工具,用于在Mac电脑上安装并运行Windows操作系统。它可以帮助用户设置双引导系统,让他们可以轻松地在启动时选择使用MacOS还是Wind

编程新范式,当Spring Boot遇上OpenAI 编程新范式,当Spring Boot遇上OpenAI Feb 01, 2024 pm 09:18 PM

2023年,AI技术已经成为热点话题,对各行业产生了巨大影响,编程领域尤其如此。人们越来越认识到AI技术的重要性,Spring社区也不例外。随着GenAI(GeneralArtificialIntelligence)技术的不断进步,简化具备AI功能的应用程序的创建变得至关重要和迫切。在这个背景下,"SpringAI"应运而生,旨在简化开发AI功能应用程序的过程,使其变得简单直观,避免不必要的复杂性。通过"SpringAI",开发者可以更轻松地构建具备AI功能的应用程序,将其变得更加易于使用和操作

利用Spring Boot以及Spring AI构建生成式人工智能应用 利用Spring Boot以及Spring AI构建生成式人工智能应用 Apr 28, 2024 am 11:46 AM

Spring+AI作为行业领导者,通过其强大、灵活的API和先进的功能,为各种行业提供了领先性的解决方案。在本专题中,我们将深入探讨Spring+AI在各领域的应用示例,每个案例都将展示Spring+AI如何满足特定需求,实现目标,并将这些LESSONSLEARNED扩展到更广泛的应用。希望这个专题能对你有所启发,更深入地理解和利用Spring+AI的无限可能。Spring框架在软件开发领域已经有超过20年的历史,自SpringBoot1.0版本发布以来已有10年。现在,无人会质疑,Spring

spring编程式事务有哪些实现方式 spring编程式事务有哪些实现方式 Jan 08, 2024 am 10:23 AM

spring编程式事务的实现方式:1、使用TransactionTemplate;2、使用TransactionCallback和TransactionCallbackWithoutResult;3、使用Transactional注解;4、使用TransactionTemplate和@Transactional结合使用;5、自定义事务管理器。

怎么开多个头条账号?申请头条号小号的流程是什么? 怎么开多个头条账号?申请头条号小号的流程是什么? Mar 22, 2024 am 11:00 AM

随着移动互联网的普及,今日头条已经成为我国最受欢迎的新闻资讯平台之一。许多用户希望在头条平台上拥有多个账号,以满足不同的需求。那么,如何开多个头条账号呢?本文将详细介绍开设多个头条账号的方法和申请流程。一、怎么开多个头条账号?开设多个头条账号的方法如下:在头条平台上,用户可以通过不同的手机号码注册账号。每个手机号只能注册一个头条账号,这意味着用户可以利用多个手机号注册多个账号。2.邮箱注册:使用不同的邮箱地址注册头条账号。与手机号码注册类似,每个邮箱地址也可以注册一个头条账号。3.第三方账号登录

抖音睡眠主播有收益嘛?睡眠直播的具体流程有哪些? 抖音睡眠主播有收益嘛?睡眠直播的具体流程有哪些? Mar 21, 2024 pm 04:41 PM

在当今这个快节奏的社会,睡眠质量问题困扰着越来越多的人。为了改善用户的睡眠质量,抖音平台上出现了一群特殊的睡眠主播。他们通过直播与用户互动,分享睡眠技巧,提供放松的音乐和声音,帮助观众安然入睡。那么,这些睡眠主播是否有收益呢?本文将围绕这一问题展开探讨。一、抖音睡眠主播有收益嘛?抖音睡眠主播确实能够获得一定的收益。首先,他们可以通过直播间的打赏功能获得礼物和转账,这些收益取决于他们的粉丝数量和观众满意度。其次,抖音平台会根据直播的观看量、点赞量、分享量等数据,给予主播一定的分成。一些睡眠主播还会

Spring 最常用的 7 大类注解,史上最强整理! Spring 最常用的 7 大类注解,史上最强整理! Jul 26, 2023 pm 04:38 PM

随着技术的更新迭代,Java5.0开始支持注解。而作为java中的领军框架spring,自从更新了2.5版本之后也开始慢慢舍弃xml配置,更多使用注解来控制spring框架。

Spring如何设置事务隔离级别 Spring如何设置事务隔离级别 Jan 26, 2024 pm 05:38 PM

Spring设置事务隔离级别的方法:1、使用@Transactional注解;2、在Spring配置文件中设置;3、使用PlatformTransactionManager;4、在Java配置类中设置。详细介绍:1、使用@Transactional注解,在需要进行事务管理的类或方法上添加@Transactional注解,并在属性中设置隔离级别;​2、在Spring配置文件等等。

See all articles