天天看点

【转】SpringBoot初体验及原理解析

一、前言

  上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅。SpringBoot是Spring框架对“约定大于配置(Convention over Configuration)”理念的最佳实践。SpringBoot应用本质上就是一个基于Spring框架的应用。我们大多数程序猿已经对Spring特别熟悉了,那随着我们的深入挖掘,会发现SpringBoot中并没有什么新鲜事,如果你不信,那就一起走着瞧呗!

二、SpringBoot初体验

首先,我们按照下图中的步骤生成一个SpringBoot项目:

【转】SpringBoot初体验及原理解析

解压后的项目文件在idea中打开以后,我们会看到如下的项目结构:

  

【转】SpringBoot初体验及原理解析

这时候,我们在

com.hafiz.springbootdemo

包下新建

controller

包,然后再在该包下面新建

DemoController.java

文件,内容如下:

package com.hafiz.springbootdemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author hafiz.zhang
 * @description: first spring boot controller
 * @date Created in 2018/6/3 16:49.
 */
@RestController
public class DemoController {    
    @GetMapping("/hello")    
    public String sayHello(String name) {        
        return "Hello, " + name;
    }
}
           

然后我们运行

SpringbootDemoApplication.java

这个main方法,启动成功后,在控制台中会出现以下日志,证明项目启动成功:

【转】SpringBoot初体验及原理解析

我们从日志中可以看出,已经注册了get方式的

/hello

的路径以及名为

/error

的路径,并且项目在tomat中运行在8080端口,然后我们在浏览器中访问

localhost:8080/hello?name=hafiz.zhang

会看到如下信息:

【转】SpringBoot初体验及原理解析
这样我们就完成了demo项目的开发,先不用这里面到底发生了啥,我就想问问你:是不是很爽?完全没有传统项目的繁琐的xml配置,爽到爆有木有?搭建一个项目骨架只要几秒钟!这就是SpringBoot带给我们的便利~ 就是这么神奇!就是这么牛逼!

三、项目简单解析

首先我们来看,生成好的项目中的

SpringbootDemoApplication.java

文件,内容如下:

package com.hafiz.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootDemoApplication {    
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}
           

代码很简单,但最耀眼的就是

@SpringBootApplication

注解以及在main方法中运行的

SpringAppliation.run()了

,那我们要揭开SpringBoot应用的奥秘,很明显就要拿这二位开刀了!

【转】SpringBoot初体验及原理解析

@SpringBootApplication

注解解析

先看看

@SpringBootApplication

注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {        
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})
public @interface SpringBootApplication {
    ...
}
           

我们看到

@SpringBootApplication

其实是一个复合的注解,起主要作用的就是

@SpringBootConfiguration

@EnableAutoConfiguration

以及

@ComponentScan

 三个注解组成,所以如果我们把SpringBoot启动类改写成如下方式,整个SpringBoot应用依然可以与之前的启动类功能一样:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {        
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})
public class SpringbootDemoApplication {    
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}
           

因为我们每次新建项目时都要写上三个注解来完成配置,这显然太繁琐了,SpringBoot就为我们提供了

@SpringBootApplication

这样一个复合注解来简化我们的操作。简直不能再贴心一点了!

【转】SpringBoot初体验及原理解析

@SpringBootConfiguration

注解解析

接着我们来看

@SpringBootConfiguration

注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
           

我们可以看到,这里面依旧没有什么新东西,它SpringBoot为了区别

@Configuration

而新提供的专属于SpringBoot的注解,功能和

@Configuration

一模一样。而这里的

@Configuration

注解对于我们来说并不陌生,我们在上篇文章中进行了详细的介绍。SpringBoot的启动类标注了这个注解,毫无疑问,它本身也是个IoC容器的配置类。了解到了这里,我们其实可以把SpringBoot的启动类来拆成两个类,拆完以后就非常清楚明了了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {

}
           
public class DemoApplication {    
    public static void main(String[] args) {
        SpringApplication.run(DemoConfiguration.class, args);
    }
}
           

所以呢,启动类DemoApplication其实就是一个标准的Standalone类型的Java程序的main函数启动类,也并没有什么特殊的东西。

【转】SpringBoot初体验及原理解析

@EnableAutoConfiguration

 的神奇之处

看到这货,我们不仅联想出Spring 中很多以“@Enable”开头的注解,比如:

@EnableScheduling

@EnableCaching

以及

@EnableMBeanExport

等,

@EnableAutoConfiguration

注解的理念和工作原理和它们其实一脉相承。简单的来说,就是该注解借助

@Import

注解的支持,Spring的IoC容器收集和注册特定场景相关的Bean定义:

  • @EnableScheduling

    是通过

    @Import

    将Spring调度框架相关的bean都加载到IoC容器。
  • @EnableMBeanExport

    是通过

    @Import

    将JMX相关的bean定义加载到IoC容器。

@EnableAutoConfiguration

注解也是借助

@Import

将所有复合配置条件的bean定义加载到IoC容器,仅此而已!而

@EnableAutoConfiguration

注解的源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}
           

这其中最关键的就是

@Import(EnableAutoConfigurationImportSelector.class)

了,它借助

EnableAutoConfigurationImportSelector.class

可以帮助SpringBoot应用将所有符合条件的@Configuration配置类都加载到当前SpringBoot创建并使用的IoC容器,就像一个“八爪鱼”一样。  

【转】SpringBoot初体验及原理解析

下面我们给出

EnableAutoConfigurationImportSelector.java

的父类

AutoConfigurationImportSelector.java

的部分源码,来解释和验证上图:

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,BeanFactoryAware, EnvironmentAware, Ordered {    
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {        
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    }    
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {        
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
    }
}
           

以上源码可以看出,

@EnableAutoConfiguration

正是借助

SpringFactoriesLoader

的支持,才能完成如此伟大的壮举!

【转】SpringBoot初体验及原理解析

幕后英雄

SpringFactoriesLoader

详解

SpringFactoriesLoader属于Spring框架专属的一种扩展方案(其功能和使用方式类似于Java的SPI方案:java.util.ServiceLoader),它的主要功能就是从指定的配置文件

META-INF/spring.factories

中加载配置,spring.factories是一个非常经典的java properties文件,内容格式是Key=Value形式,只不过这Key以及Value都非常特殊,为Java类的完整类名(Fully qualified name),比如:

com.hafiz.service.DemoService=com.hafiz.service.impl.DemoServiceImpl,com.hafiz.service.impl.DemoServiceImpl2
           

然后Spring框架就可以根据某个类型作为Key来查找对应的类型名称列表了,SpringFactories源码如下:

public abstract class SpringFactoriesLoader {    
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";    
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
        ...
    }    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }    // ...}
           

对于

@EnableAutoConfiguraion

来说,SpringFactoriesLoader的用途和其本意稍微不同,它本意是为了提供SPI扩展,而在

@EnableAutoConfiguration

这个场景下,它更多的是提供了一种配置查找的功能的支持,也就是根据

@EnableAutoConfiguration

的完整类名

org.springframework.boot.autoconfigure.EnableAutoConfiguration

作为Key来获取一组对应的@Configuration类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
......
           

在SpringBoot的autoconfigure依赖包中的META-INF文件下的spring.factories文件中,我们可以找到以上内容,这就很好的解释了为什么。

总结来说,

@EnableAutoConfiguration

能实现自动配置的原理就是:SpringFactoriesLoader从classpath中搜寻所有META-INF/spring.fatories文件,并将其中Key[

org.springframework.boot.autoconfigure.EnableAutoConfiguration

]对应的Value配置项通过反射的方式实例化为对应的标注了

@Configuration

的JavaConfig形式的IoC容器配置类,然后汇总到当前使用的IoC容器中。

【转】SpringBoot初体验及原理解析

非必须的

@ComponentScan

解析

为什么说这个注解是非必需的呢?因为我们知道作为Spring框架里的老成员,@ComponentScan的功能就是自动扫描并加载复合条件的组件或Bean定义,最终将这些bean定义加载到当前使用的容器中。这个过程,我们可以手工单个进行注册,不是一定要通过这个注解批量扫描和注册,所以说

@ComponentScan

是非必需的。

所以,如果我们当前应用没有任何bean定义需要通过

@ComponentScan

加载到当前SpringBoot应用对应的IoC容器,那么,去掉

@ComponentScan

注解,当前的SpringBoot应用依旧可以完美运行!那是不是到目前为止,依旧没有什么新鲜的事物出现?

【转】SpringBoot初体验及原理解析

关于我们用到的

@RestController

以及

@GetMapping

@RestController

注解也是一个复合注解,没啥新东西,就是自动返回json类型的数据,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {    
    String value() default "";
}
           

@GetMapping

注解也是一个复合注解,依旧没啥新东西,就是用来定义只支持GET请求的URL,源码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    ...
}
           

四、总结

  刚开始没有深入接触SpringBoot的时候,感觉这货真的好神奇,竟然可以省去那么多搭建步骤。这特么简直就是开发人员的福音啊,再回头想想这货为啥会火呢?我们都知道近几年微服务的概念很火,很多企业也开始把自己的项目做成微服务了,那随着项目越来越多,按照我们以前那种方式配置项目,很繁琐,光创建项目就要耗费很大的精力和时间,这个时候拯救世界的SpringBoot出现了,它简化了配置方式,实乃应运而生,顺势而为啊!再不火,没天理不是。下篇文章我们来聊一聊SpringBoot的启动流程。期待的小伙伴,评论扣1.

继续阅读