목차
환경
SpringApplication 클래스의 정적 실행 메소드
SpringApplication 개체를 생성합니다
첫 번째는 webEnvironment:
그런 다음 초기화 프로그램이 있습니다.
接下来是成员变量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-starter-web을 사용하는 Spring Boot 버전 1.3.3을 기반으로 합니다.

구성이 완료되면 다음과 같이 코드가 작성됩니다.

@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!";
    }

}
로그인 후 복사

코드가 몇 줄밖에 안 되지만 접속 URL의 경로 부분이 "/"이면 이미 완전한 웹 프로그램입니다. , "Welcome!"이라는 문자열이 반환됩니다.

우선 매우 일반적인 Java 프로그램 항목이며, 계약을 준수하는 정적 메인 메소드입니다. 이 메인 메소드에서는 SpringApplication의 static run 메소드가 호출되고, Application 클래스 객체와 메인 메소드의 매개변수 args가 매개변수로 전달된다.

그리고 두 개의 Spring 주석을 사용하는 RootController 클래스가 있습니다. 우리는 이 클래스를 기본 메서드에서 직접 사용하지 않습니다.

SpringApplication 클래스의 정적 실행 메소드

以下代码摘自: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 객체를 생성하고 객체의 실행 메소드를 호출합니다.

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();
}
로그인 후 복사

생성자에서 초기화 메서드를 호출하여 SpringApplication 개체의 멤버 변수 소스, webEnvironment, 초기화 프로그램, 리스너 및 mainApplicationClass를 초기화합니다. 소스 할당은 상대적으로 간단합니다. 이는 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가 부울값임을 알 수 있으며 이 멤버 변수는 현재 애플리케이션이 웹 애플리케이션인지 여부를 나타내는 데 사용됩니다. 따라서 현재 애플리케이션이 웹 애플리케이션인지 확인하는 방법은 WEB_ENVIRONMENT_CLASSES 배열에 포함된 클래스가 클래스 경로에 존재하는지 확인하는 것입니다. 존재하는 경우 현재 프로그램은 웹 애플리케이션이고 그 반대의 경우도 마찬가지입니다.
이 기사의 예에서 webEnvironment의 값은 true입니다.

그런 다음 초기화 프로그램이 있습니다.

ApplicationContextInitializer 유형 개체의 컬렉션인

initializers 멤버 변수입니다. 이름에서 알 수 있듯이 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 유형 객체 목록을 가져오는 것이 핵심임을 알 수 있습니다.
🎜rreee🎜

在该方法中,首先通过调用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 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Windows ISO 파일이 너무 큼 BootCamp 오류 [수정됨] Windows ISO 파일이 너무 큼 BootCamp 오류 [수정됨] Feb 19, 2024 pm 12:30 PM

Mac 컴퓨터에서 BootCampAssistant를 사용할 때 "Windows ISO 파일이 너무 큽니다"라는 오류 메시지가 표시되는 경우 ISO 파일 크기가 BootCampAssistant에서 지원하는 제한을 초과했기 때문일 수 있습니다. 이 문제에 대한 해결책은 다른 도구를 사용하여 ISO 파일 크기를 압축하여 BootCamp Assistant에서 처리할 수 있도록 하는 것입니다. BootCampAssistant는 Mac 컴퓨터에 Windows 운영 체제를 설치하고 실행하기 위해 Apple에서 제공하는 편리한 도구입니다. 사용자가 듀얼 부팅 시스템을 설정하여 시작 시 MacOS 또는 Wind를 사용하도록 쉽게 선택할 수 있습니다.

Spring Boot가 OpenAI를 만났을 때 새로운 프로그래밍 패러다임 Spring Boot가 OpenAI를 만났을 때 새로운 프로그래밍 패러다임 Feb 01, 2024 pm 09:18 PM

2023년에는 AI 기술이 화두가 되면서 다양한 산업, 특히 프로그래밍 분야에 큰 영향을 미치고 있다. 사람들은 AI 기술의 중요성을 점점 더 인식하고 있으며 Spring 커뮤니티도 예외는 아닙니다. GenAI(일반 인공 지능) 기술이 지속적으로 발전함에 따라 AI 기능을 갖춘 애플리케이션 생성을 단순화하는 것이 중요하고 시급해졌습니다. 이러한 배경에서 AI 기능 애플리케이션 개발 프로세스를 단순화하고 간단하고 직관적이며 불필요한 복잡성을 피하는 것을 목표로 하는 "SpringAI"가 등장했습니다. '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년 이상의 역사를 가지고 있으며, Spring Boot 1.0 버전이 출시된 지 10년이 되었습니다. 이제 봄이 왔다는 것에 대해 누구도 이의를 제기할 수 없습니다.

Spring 프로그래밍 방식 트랜잭션의 구현 방법은 무엇입니까? Spring 프로그래밍 방식 트랜잭션의 구현 방법은 무엇입니까? Jan 08, 2024 am 10:23 AM

Spring 프로그래밍 방식 트랜잭션을 구현하는 방법: 1. TransactionCallback 및 TransactionCallbackWithoutResult를 사용합니다. 3. Transactional 주석을 사용합니다. 4. @Transactional과 함께 TransactionTemplate을 사용합니다.

여러 개의 Toutiao 계좌를 개설하는 방법은 무엇입니까? Toutiao 계정을 신청하는 절차는 무엇입니까? 여러 개의 Toutiao 계좌를 개설하는 방법은 무엇입니까? Toutiao 계정을 신청하는 절차는 무엇입니까? Mar 22, 2024 am 11:00 AM

모바일 인터넷의 인기로 인해 Toutiao는 우리나라에서 가장 인기 있는 뉴스 정보 플랫폼 중 하나가 되었습니다. 많은 사용자는 다양한 요구 사항을 충족하기 위해 Toutiao 플랫폼에 여러 계정을 갖고 싶어합니다. 그렇다면 여러 개의 Toutiao 계정을 개설하는 방법은 무엇입니까? 이번 글에서는 터우탸오(Toutiao) 계좌를 여러 개 개설하는 방법과 신청 과정을 자세히 소개하겠습니다. 1. Toutiao 계정을 여러 개 개설하는 방법은 무엇입니까? 여러 개의 Toutiao 계정을 개설하는 방법은 다음과 같습니다. Toutiao 플랫폼에서 사용자는 다양한 휴대폰 번호를 통해 계정을 등록할 수 있습니다. 각 휴대폰 번호는 하나의 Toutiao 계정만 등록할 수 있습니다. 즉, 사용자는 여러 휴대폰 번호를 사용하여 여러 계정을 등록할 수 있습니다. 2. 이메일 등록: 다른 이메일 주소를 사용하여 Toutiao 계정을 등록하세요. 휴대폰 번호 등록과 마찬가지로 각 이메일 주소도 Toutiao 계정을 등록할 수 있습니다. 3. 타사 계정으로 로그인

Spring에서 트랜잭션 격리 수준을 설정하는 방법 Spring에서 트랜잭션 격리 수준을 설정하는 방법 Jan 26, 2024 pm 05:38 PM

Spring에서 트랜잭션 격리 수준을 설정하는 방법: 1. @Transactional 주석을 사용합니다. 2. Spring 구성 파일에서 설정합니다. 3. PlatformTransactionManager를 사용합니다. 4. Java 구성 클래스에서 설정합니다. 자세한 소개: 1. @Transactional 주석을 사용하고, 트랜잭션 관리가 필요한 클래스나 메소드에 @Transactional 주석을 추가하고, 속성에서 격리 수준을 설정합니다. 2. Spring 구성 파일에서 등.

역사상 가장 강력한 조직, Spring에서 가장 많이 사용되는 7가지 Annotation! 역사상 가장 강력한 조직, Spring에서 가장 많이 사용되는 7가지 Annotation! Jul 26, 2023 pm 04:38 PM

기술의 업데이트와 반복으로 Java5.0은 주석을 지원하기 시작했습니다. Java의 선도적인 프레임워크인 Spring은 버전 2.5로 업데이트된 이후 천천히 xml 구성을 포기하기 시작했으며 더 많은 주석이 spring 프레임워크를 제어하는 ​​데 사용됩니다.

Douyin 수면 앵커가 되는 것이 수익성이 있습니까? 수면 라이브 스트리밍의 구체적인 절차는 무엇입니까? Douyin 수면 앵커가 되는 것이 수익성이 있습니까? 수면 라이브 스트리밍의 구체적인 절차는 무엇입니까? Mar 21, 2024 pm 04:41 PM

오늘날 빠르게 변화하는 사회에서 수면의 질 문제는 점점 더 많은 사람들을 괴롭히고 있습니다. 사용자의 수면 품질을 향상시키기 위해 Douyin 플랫폼에 특수 수면 앵커 그룹이 등장했습니다. 라이브 방송을 통해 사용자와 소통하고, 수면 팁을 공유하며, 편안한 음악과 사운드를 제공하여 시청자가 편안하게 잠들 수 있도록 도와줍니다. 그렇다면 이러한 수면 앵커는 수익성이 있습니까? 이 기사에서는 이 문제에 중점을 둘 것입니다. 1. Douyin 수면 앵커는 수익성이 있습니까? Douyin 수면 앵커는 실제로 특정 이익을 얻을 수 있습니다. 첫째, 생방송실 내 팁 기능을 통해 선물과 양도를 받을 수 있으며, 이러한 혜택은 팬 수와 시청자 만족도에 따라 달라집니다. 둘째, Douyin 플랫폼은 생방송의 조회수, 좋아요, 공유 및 기타 데이터를 기반으로 앵커에게 특정 공유를 제공합니다. 일부 수면 앵커는 또한

See all articles