SpringBoot

Spring

组件代码轻量级,配置重量级。

  • 开始基于xml配置。
  • spring 2.5基于主键扫描
  • spring 3.0基于java 代码配置

还需要显式配置。比如事物。
项目jar包依赖冲突版本等问题。

SpringBoot

springboot 就是spring 做了一些你用spring 需要配置的东西(tomcat spring配置 web.xml 项目结构)

1 概述

  • 最小的阻力下使用spring,简化配置,专注应用程序
    @SpringBootApplication(@Configuration@EnableAutoConfiguration @ComponentScan)
    (1)Indicates a {@link Configuration configuration} class that declares one or more {@link Bean @Bean} methods。
    使用java 配置spring的 java bean,没啥好说的。Indicates that a class declares one or more {@link Bean @Bean} methods may be processed by the Spring container to generate bean definitions service requests for those beans at runtime
    (2)triggers {@link EnableAutoConfiguration auto-configuration}
    开启许多spring的自动配置,生成必须的Bean.推荐注解加载root 包的类上(main 启动类)。Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.
    (3){@link ComponentScan component scanning}
    开启自动配置包组件扫描。

2 源码分析

2.1 初始化、启动

初始化

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
           SpringApplication myApplication = new SpringApplication(MyApplication.class);
           //定制 
           myApplication.run();
    }
}

SpringApplication 有两个构造方法:
Create a new {@link SpringApplication} instance. The application context will load beans from the specified sources (see {@link SpringApplication class-level} The instance can be customized before calling {@link #run(String...)}.

    public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
        this.resourceLoader = resourceLoader;
        initialize(sources);
    }
    public SpringApplication(Object... sources) {
        initialize(sources);
    }

springboot启动过程先要new 一个springboot实例进行初始化配置,
然后调用run 方法。run方法执行前可以做一些定制化的配置比如,添加一些自己的事件监听器(实现 ApplicationListener)实现对ApplicationEvent 的监听处理。
来看一下initialize(sources) 方法。

    private void initialize(Object[] 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();
    }
this.webEnvironment = deduceWebEnvironment();

判断是不是一个web项目主要是看javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext这两个类是不是存在。

setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));

设置初始化器ApplicationContextInitializer,在refresh 之前调用,做一些初始化的工作。 这里读取META-INF/spring.factories下的key为ApplicationContextInitializer的类并实例化。实例化时候通过读取Class 获取构造函数类反射生成。

private <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
                        .getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

初始设置的 ApplicationContextInitializer有

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

设置默认的所有SpringApplication的事件监听器。读取META-INF/spring.factories下的key为ApplicationListener的类并实例化。
有:

org.springframework.boot.ClearCachesApplicationListener,\
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

注意:如果使用spring cloud 有依赖 spring-cloud-context jar 包的话还会读取其META-INF/spring.factories下的key为ApplicationListener的类并实例化,有两个。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.context.restart.RestartListener

其中BootstrapApplicationListener 特别重要,他监听spring boot 的环境准备事件ApplicationEnvironmentPreparedEvent,调用 bootstrapServiceContext ()方法,重新调用springboot dr的run(),插入一些spring cloud相关初始化需要的环境,主要应该是一些相关的配置类

this.mainApplicationClass = deduceMainApplicationClass()

找出启动类。

启动

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;//定义spring容器
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);//获取SpringApplicationRunListener
        listeners.starting();//监听发送ApplicationStartedEvent 事件
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //准备额皮质应用环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            //打印banner信息
            Banner printedBanner = printBanner(environment);
            //创建spring容器
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            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, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

SpringApplicationRunListeners 类里面就一个log 跟一组SpringApplicationRunListener。加载SpringApplicationRunListener跟加载ApplicationContextInitializer和ApplicationListener的过程一样.

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

说下SpringApplicationRunListener:

/**
 * Listener for the {@link SpringApplication} {@code run} method.
 * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
 * and should declare a public constructor that accepts a {@link SpringApplication}
 * instance and a {@code String[]} of arguments. A new
 * {@link SpringApplicationRunListener} instance will be created for each run.
 *
 * @author Phillip Webb
 * @author Dave Syer
 */
public interface SpringApplicationRunListener {

    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     */
    void starting();

    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     * @param environment the environment
     */
    void environmentPrepared(ConfigurableEnvironment environment);

    /**
     * Called once the {@link ApplicationContext} has been created and prepared, but
     * before sources have been loaded.
     * @param context the application context
     */
    void contextPrepared(ConfigurableApplicationContext context);

    /**
     * Called once the application context has been loaded but before it has been
     * refreshed.
     * @param context the application context
     */
    void contextLoaded(ConfigurableApplicationContext context);

    /**
     * Called immediately before the run method finishes.
     * @param context the application context or null if a failure occurred before the
     * context was created
     * @param exception any run exception or null if run completed successfully.
     */
    void finished(ConfigurableApplicationContext context, Throwable exception);

}

SpringApplicationRunListener 监听run 方法的执行过程,一共五个处理方法各监听不用的事件event,对应五个步骤。
(1)starting(run方法执行的时候立马执行,用于最开始的初始化,监听ApplicationStartedEvent 事件)
(2)environmentPrepared(ApplicationContext创建之前,环境信准备之后执行一次,监听ApplicationEnvironmentPreparedEvent)
(3)contextPrepared(ApplicationContext创建之后,source加载之前调用一次)
(4)contextLoaded(ApplicationContext创建并加载之后,在refresh之前调用。监听ApplicationPreparedEvent)
(5)finished(run方法结束之前,监听ApplicationReadyEvent或ApplicationFailedEvent)
SpringApplicationRunListener 只有一个实现类就是刚才说的加载的EventPublishingRunListener。

**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    private final SpringApplication application;

    private final String[] args;

    private final SimpleApplicationEventMulticaster initialMulticaster;

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void starting() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        for (ApplicationListener<?> listener : this.application.getListeners()) {
            if (listener instanceof ApplicationContextAware) {
                ((ApplicationContextAware) listener).setApplicationContext(context);
            }
            context.addApplicationListener(listener);
        }
        this.initialMulticaster.multicastEvent(
                new ApplicationPreparedEvent(this.application, this.args, context));
    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        SpringApplicationEvent event = getFinishedEvent(context, exception);
        if (context != null) {
            // Listeners have been registered to the application context so we should
            // use it at this point if we can
            context.publishEvent(event);
        }
        else {
            if (event instanceof ApplicationFailedEvent) {
                this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
            }
            this.initialMulticaster.multicastEvent(event);
        }
    }

    private SpringApplicationEvent getFinishedEvent(
            ConfigurableApplicationContext context, Throwable exception) {
        if (exception != null) {
            return new ApplicationFailedEvent(this.application, this.args, context,
                    exception);
        }
        return new ApplicationReadyEvent(this.application, this.args, context);
    }

    private static class LoggingErrorHandler implements ErrorHandler {

        private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);

        @Override
        public void handleError(Throwable throwable) {
            logger.warn("Error calling ApplicationEventListener", throwable);
        }

    }

}

EventPublishingRunListener 看名字,事件发布执行监听器。看构造方法,SpringApplication ,参数,一个内部类SimpleApplicationEventMulticaster(应用事件多路广播器)。
过程:
SpringBootApplication run()执行时调用刚刚加载的EventPublishingRunListener中具体的方法(比如 starting()过程),EventPublishingRunListener 通过内部实现的事件广播器SimpleApplicationEventMulticaster将该过程(启动)封装成对应的事件(ApplicationStartedEvent)调用自己的方法multicastEvent 将改事件广播给之前对应的已经注册过的ApplicationListener 。ApplicationListener 拿到各种不同的事件消息进行各自的处理过程onApplicationEvent(E event)。

    @Override
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        invokeListener(listener, event);
                    }
                });
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                listener.onApplicationEvent(event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            try {
                listener.onApplicationEvent(event);
            }
            catch (ClassCastException ex) {
                String msg = ex.getMessage();
                if (msg == null || msg.startsWith(event.getClass().getName())) {
                    // Possibly a lambda-defined listener which we could not resolve the generic event type for
                    Log logger = LogFactory.getLog(getClass());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Non-matching event type for listener: " + listener, ex);
                    }
                }
                else {
                    throw ex;
                }
            }
        }
    }

}

继续看
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
创建应用的环境信息,如果是web 环境 获取StandardServletEnvironment 其他的获取StandardEnvironment

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);//发送ApplicationEnvironmentPreparedEvent 事件
        if (isWebEnvironment(environment) && !this.webEnvironment) {
            environment = convertToStandardEnvironment(environment);
        }
        return environment;
    }

创建spring 容器 context = createApplicationContext();


    /**
     * Strategy method used to create the {@link ApplicationContext}. By default this
     * method will respect any explicitly set application context or application context
     * class before falling back to a suitable default.
     * @return the application context (not yet refreshed)
     * @see #setApplicationContextClass(Class)
     */
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
        //如果是web项目创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
        
//否则创建的是org.springframework.context.annotation.AnnotationConfigApplicationContex    
            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);
    }

接下来是
prepareContext(context, environment, listeners, applicationArguments,printedBanner);

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
      //spring 容器创建之后的回调处理
        postProcessApplicationContext(context);
        //在 spring 容器刷新之前对注册过的 ApplicationContextInitializer  初始化 
        //实现 ApplicationContextInitializer的初始化器调用 void initialize(C applicationContext);  做相应额处理逻辑
        applyInitializers(context);
    //spring boot run 监听器 发送spring 容器创建消息
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
    // 在容器refresh 前 spring boot run 监听器 发送 ApplicationPreparedEvent事件给相应的监听器执行  
        listeners.contextLoaded(context);
    }

接下来 刷新容器
refreshContext(context);
afterRefresh(context, applicationArguments);

    /**
     * Called after the context has been refreshed.
     * @param context the application context
     * @param args the application arguments
     */
    protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
        callRunners(context, args);
    }

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

调用Spring容器中的ApplicationRunner和CommandLineRunner接口的实现类


/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see CommandLineRunner
 */
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}

发送 finshed 至此容器初始化完成,各种监听器和初始化器也做了相应的工作。
listeners.finished(context, null);

run过程

1、找出所有的SpringApplicationRunListener并封装到SpringApplicationRunListeners中,用于监听run方法的执行(一共五个步骤)。监听的过程中会封装成对应的事件并通过内部实现的多路广播器广播出去让初始化过程中注册到应用程序中的监听器(springapplicationlistener)进行监听,做相应的操作。
2、构造Spring容器(ApplicationContext),并返回 创建Spring容器的判断是否是web环境,是的话构造AnnotationConfigEmbeddedWebApplicationContext,否则构造AnnotationConfigApplicationContext
初始化过程中产生的初始化器在这个时候开始工作
Spring容器的刷新(完成bean的解析、各种processor接口的执行、条件注解的解析等等)
3、从Spring容器中找出ApplicationRunner和CommandLineRunner接口的实现类并排序后依次执行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 前言:学习SpringBoot不应该直接就是开始使用SpringBoot,如果直接拿来用肯定会有很多人不是很明白特...
    CoderZS阅读 74,642评论 10 217
  • springboot 概述 SpringBoot能够快速开发,简化部署,适用于微服务 参考嘟嘟大神SpringBo...
    一纸砚白阅读 5,233评论 2 20
  • SpringBoot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spr...
    谁在烽烟彼岸阅读 2,230评论 1 1
  • 高考很苦,准备高考的日子很苦,高考很快,两天时间六份试卷很快,这些都足以让我感慨让我难忘,但我最难忘的,不是高考路...
    无聊人628阅读 206评论 0 0