在之前的基于Spring Boot的案例中,我们学习的重点是其在实际开发中的应用,而本章节我们着重学习springboot的底层原理,增强对框架的认识。
一、配置优先级
在之前,我们学习了springboot项目的三种常见的配置文件,他们分别是 application.properties application.yml application.yaml,那如果在一个项目当中这三个配置文件同时存在,优先以哪一份为主?经过实践,结论如下:
除了这三种配置之外,还有:
这时直接在idea当中设置的配置属性,但是如果我们的项目已经打包了,我们该如何去修改这个属性呢?
关于打包,这里需要注意的是:
小结一下上述配置的优先级:
二、Bean管理
(一)获取Bean对象
代码示例::
/**
* 根据IOC容器来获取Bean对象
*/
@Autowired
private ApplicationContext applicationContext;//IOC容器
@Test
public void getBean(){
//根据name获取Bean对象
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);
//根据Bean的类型来获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
//根据bean的名称和类型来获取
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
特别注意 :注入ApplicationContext的时候一定不要导错包!!!
(二)Bean的作用域
也就是说,默认的bean在容器被启动的时候就会被创建并交给IOC容器来管理,但是通过@Lazy注解可以延迟初始化,延迟到我们第一次使用这个Bean对象的时候才去创建它的实例,之后使用的时候也都是这个单例对象,不会创建新的。但是可以通过@Scope来设置作用域,prototype的bean在每次使用的时候都会创建一个新的bean。
(三)第三方bean
接下来,以我们之前解析xml文件的依赖为例,我们创建一个类来引用我们对应的依赖,而不是每次都要new一个对象。代码如下:
@Configuration//配置类
public class TestConfig {
//声名第三方Bean
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
之后我们需要使用的时候就可以直接用 @Autowired 注入就可以啦。
如果我们在创建一个依赖的bean对象时需要使用到其他的bean对象,我们可以直接将他们以参数的形式传递,方法执行时会自动加载对应的bean。例如:
@Configuration//配置类
public class TestConfig {
//声名第三方Bean
@Bean()
public SAXReader saxReader(DeptController deptController){
System.out.println(deptController);
return new SAXReader();
}
}
三、spring boot原理
(一)起步依赖
在我们之前学习的spring框架中,会出现一些问题,比如说依赖的配置较为繁琐。如下图,我们的起步依赖的配置较为繁琐,需要导入多个依赖。
而在springboot框架就对spring的这个问题做出了优化,但是注意不是替代,springboot底层实际还是spring的。springboot之所以 可以优化这些配置,与两个功能密切相关,分别是 起步依赖,自动配置。
(二)自动配置
下面,我们展示之前案例项目运行时的bean对象,我们会发现一个问题,我们并没有手动地导入这个gson的依赖,但是它却成为了我们IOC容器的bean对象,这就是我们springboot的自动配置原理。
接下来我们回到引入第三方的bean,当我们引入第三bean的时候,如果只是在pom.xml文件当中
导入对应的依赖,其实是无法直接使用依赖当中的方法的,比如下:
此时如果我们在测试类当中测试第三方bean的HeaderConfig,就会发现编译器报了一个错,这个错是:NoSuchBeanDefinitionException,找不到对应的bean对象,这其实与IOC容器的组件扫描有关,在启动类当中对应的 @SpringBootApplication 注解默认扫描的是当前包及其子包,所以无法扫描到第三方bean的类,故无法使用。关于这个有以下解决方案:
@Import({TokenParser.class}) //导入普通类
@Import({HeaderConfig.class}) //导入配置类
@Import({MyImportSelector.class}) //导入ImportSelector的接口实现类
关于第四种方案,需要在第三方的Bean实现一些对应的要求:
定义一个接口,封装要导入的类,同时这个导入的类也指定了所需要的配置类
第三方的Bean做好这些操作之后,我们就可以在启动类当中使用 @EnableHeaderConfig 注解直接导入我们所需要的依赖并支持扫描,非常的方便,优雅。(注:这里的注解名字是以上述的案例为例,具体的接口名字可以自己指定)
(三)源码分析
a、源码
要研究springboot底层关于自动配置的原理,其实我们只需要研究启动类 的@SpringBootApplication 注解即可,因为这个注解就包含了自动配置二点原理包括组件的扫描。
进入的 @SpringBootApplication 的源码之后我们先分析它封装的多个注解:
原理的分析:
b、@Conditional
在上述的源码分析中我们发现,自动配置类除了加上 @Bean注解以外,还会加上 @Conditional 注解,这是一个自动装配注解,接下来我们深入讲解这个注解。
关于这三个常见注解的解释如下:
第二个注解主要用于声名一些默认的bean,当用户没有指定这个bean时,就用我们提供的默认的bean。第三个注解关系到项目中的application.properties的配置文件以及其对应的值
学习了@Conditional这个注解,现在返回去对springboot的自动配置原理进行重新理解,会有意外的收获。
c、自定义starter
在自定义starter之前我们先了解一下关于完成第三放自动配置的依赖所需要的功能模块。
接下时我们自定义starter这个案例的需求分析:
根据上述的需要创建对应的模块之后的结构见下方:
1、接下来我们展示对应的代码:
AliyunOSSAutoConfiguration:
/**
* 自动配置类
*/
@Configuration
@EnableConfigurationProperties(AliOSSProperties.class) //通过这个注解,现在IOC容器当中已经有了这个bean
public class AliOSSAutoConfiguration {
@Bean //通过参数的传递来声名我们所需要的bean,spring会根据参数的类型自动装配
public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}
这是这块代码在编写时所出现的问题,因为我们是对之前aliyun实体类代码的改造,此时spring并不能扫描到这个类,所以我们删除了@Component的注解,并改造了代码。同时我将AliOSSProperties作为方法的参数传递使用,因为这里的类并不会被spring所扫描,无法加载到IOC容器当中,也就无法直接注入使用。
AliOSSProperties:
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endPoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getEndPoint() {
return endPoint;
}
public void setEndPoint(String endPoint) {
this.endPoint = endPoint;
}
}
AliOSSUtils:
public class AliOSSUtils {
private AliOSSProperties aliOSSProperties;
public AliOSSProperties getAliOSSProperties() {
return aliOSSProperties;
}
public void setAliOSSProperties(AliOSSProperties aliOSSProperties) {
this.aliOSSProperties = aliOSSProperties;
}
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
//获取实体类的属性
String endpoint = aliOSSProperties.getEndPoint();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String accessKeyId = aliOSSProperties.getAccessKeyId();
String bucketName = aliOSSProperties.getBucketName();
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
这里的主要改造是,删掉了@Autowired 的注解,改用传统的get/set方法来获取对应的属性和赋值。
2、之后,在Resources这个目录下创建多级文件
接下来在这个目录下,创建自动配置类 org.springframework.boot.autoconfigure.AutoConfiguration.imports 。需要注意的是,这个文件里面写的是自动配置类的全类名
com.aliyun.oss.AliOSSAutoConfiguration
3、在 aliyun-oss-spring-boot-starter 这个模块中的 pom.xml文件引入对应的依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
这样子我们就手动实现了起步依赖的配置,其主要涉及 的其实还是springboot的自动配置原理,这个自定义starter的案例主要是加深对springboot自动配置原理的理解,可以回过头在对其源码进行阅读,相信会有意想不到的收获。
四、后端开发小结
所学习的知识点汇总:
不同知识点所归于的不同框架模块:
到此,这一章就结束啦,我们继续加油!!!