SpringBoot原理与Bean管理
配置优先级
在SpringBoot项目中。支持三种格式的配置文件,分别是
-
application.yaml
-
application.yml
-
application.properties
他们的优先级由低到高。
SpringBoot除了支持配置文件属性配置,还支持java系统属性和命令行参数的方式进行属性配置。
当我们即使用配置文件,又使用系统属性和命令行参数时,他们的优先级为。
Bean管理
获取bean
@Autowired
private ApplicationContext applicationContext;
//获取bean对象
@Test
public void testGetBean(){
int i = 0;
//根据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);
}
运行结果如下
com.itheima.controller.DeptController@1c7f6e96
com.itheima.controller.DeptController@1c7f6e96
com.itheima.controller.DeptController@1c7f6e96
可以发现,创建的三个DeptController
是同一个对象,这个现象是由bean的作用域引起的。
bean作用域
我们可以通过@Scope
对象进行作用域的配置,一般默认的和是第一种,也就造成了刚才代码的DeptController
是同一个对象,当我们在DeptController加上@Scope("prototype")
注解,再次运行测试程序。
DeptController constructor ....
com.itheima.controller.DeptController@36df4c26
DeptController constructor ....
com.itheima.controller.DeptController@76828577
DeptController constructor ....
com.itheima.controller.DeptController@38732372
可以看到这是三个不同的对象。
第三方的bean
在之前的的学习的时候,当我们需要把对象交给IOC容器去进行管理的时候,可以直接在对象的上方加上:
@Component
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。@RestController
:用于标注控制层组件(如struts中的action)是@ResponseBody
和@Controller
的合集。@Service
:一般用于修饰service层的组件。@Mapper
:一般用于修饰dao层的组件。
但是我们如果想让IOC容器去管理第三方的bean对象(不是自定义的),那么就无法使用@Component
注解,这个时候就可以用到@Bean
注解。
@Bean
:产生一个bean,并交给spring管理。
若要管理第三方的bean对象,建议对这些bean对象进行集中分类配置,可以通过 @Configuration
注解声明一个配置类。
@Configuration
表明一个类中声明一个和多个 @Bean 标记的方法,并且这些方法被 Spring 容器管理用于生成 Bean 定义以及在运行时这些 Bean 的服务请求。
例子如下
//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
这里我们通过dom4j中的SAXReader
类解析xml文件,在这里,我们之间创建了SAXReader
的对象。但当我们想要用IOC容器去管理SAXReader
就可以去定义一个配置类,并在类上加入@Configuration
注解,然后再在方法上加入@Bean
注解。
@Configuration
public class CommonConfig {
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
然后我们更改上面的解析xml文件的代码。
@Autowired
SAXReader saxReader;
//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
再次运行程序,可以看到正确解析。
SpringBoot原理
Spring
起步依赖
基于Maven的依赖传递。
自动配置
SpringBoot简化了开发流程,但是它是如何实现的呢,我们可以自己定义一个包,当作是外部依赖,然后让我们的项目引入这个依赖。
<dependency>
<groupId>com.example</groupId>
<artifactId>itheima-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
但是如果我们想在测试类里面从IOC容器中获取引用进来的工具类中的对象的时候,却发生了错误。
解决方案一
使用@ComponentScan
组件扫描。
@ComponentScan
:组件扫描,可自动发现和装配一些Bean。
我们在@ComponentScan
后面传入要扫描的包名。
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@ComponentScan({"com.example","com.itheima"})
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
再次运行测试程序
成功运行。虽然解决了问题,但是解决方式并不优雅。
解决方案二
@Import
导入。使用@Import
导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种。
- 导入普通类
- 导入配置类
- 导入
ImportSelector
接口实现类 @EnableXxxx
注解,封装@Import
注解
导入普通类
运行成功
导入配置类
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 19667
*/
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
HeadParser和HeadGenerator均运行成功
导入ImportSelector
接口实现类
这里我们实现了ImportSelector
接口,并且返回了一个字符串,封装类名。
package com.example;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author 19667
*/
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
}
}
运行成功
@EnableXxxx
注解,封装@Import
注解
通常由导入的类提供。
package com.example;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig {
}
加上注解
均运行成功(图懒得放了)
自动配置源码跟踪。
在配置中有很多的全类名,但是并不是全部都注册为IOC容器的Bean,源码中用到了@Conditional
注解。
- **
@Conditional
**
自定义starter
在之前的部门和员工管理系统中,我们使用了阿里云OSS上传了员工的图像,在那个项目中,我们先是引入了三个依赖。
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
然后再自己制作了两个工具类。
package com.itheima.utils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* {@code @Author} 19667
* {@code @create} 2024/7/31 15:34
*/
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
package com.itheima.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
* @author 19667
*/
@Component
public class AliOSSUtils {
@Autowired
AliOSSProperties aliOSSProperties;
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String endpoint = aliOSSProperties.getEndpoint();
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的路径返回
}
}
最后在配置文件中配置阿里云OSS配置连接信息。
#阿里云连接信息
aliyun:
oss:
endpoint: https://oss-cn-guangzhou.aliyuncs.com
accessKeyId: *******
accessKeySecret: *******
bucketName: *******
虽然是实现了上传图片的功能,但是还是比较繁琐的,当别的项目也想使用阿里OSS上传图片的时候,又要再来一次之前的操作,非常的不方便,所以就到了自定义starter出马的时候了。
我们在这里直接新建两个模块,并精简一下。
其中的aliyun-oss-spring-boot-starter
模块我们之保留xml文件和iml文件,如果模块创建的时候没有iml文件,那么就需要将模块在终端中打开,并执行 mvn idea:module
命令。最后再在xml文件中引入aliyun-oss-spring-boot-starter
依赖就行了。
对于aliyun-oss-spring-boot-autoconfigure
模块,我们也进行适当的精简,然后把之前项目的两个工具类粘贴过来。这两个工具类我们不能直接用,要做一点修改。
package com.aliyun.oss;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
* @author 19667
*/
public class AliOSSUtils {
AliOSSProperties aliOSSProperties;
public AliOSSProperties getAliOSSProperties() {
return aliOSSProperties;
}
public void setAliOSSProperties(AliOSSProperties aliOSSProperties) {
this.aliOSSProperties = aliOSSProperties;
}
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String endpoint = aliOSSProperties.getEndpoint();
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的路径返回
}
}
首先,我们移除了@Componet
注解,因为我们不会使用@ComponentScan
组件扫描来把类添加到IOC容器中。再移除了@Autowired
注解,然后添加了Getter and Setter方法
package com.aliyun.oss;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@code @Author} 19667
* {@code @create} 2024/7/31 15:34
*/
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
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;
}
}
对于这个配置类,我移除了@Data @Component
注解,然后再添加了Getter and Setter方法。
接下来,增加一个com.aliyun.oss.AliOSSAutoConfiguration
的类。
package com.aliyun.oss;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/3 16:42
*/
@Configuration
@EnableConfigurationProperties(AliOSSProperties.class)
public class AliOSSAutoConfiguration {
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}
这个类的作用主要是把AliOSSUtils
交给IOC容器管理,然后导入配置文件。
最后,我们创建META-INF/目录,再创建一个org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件 ,并在文件中加入com.aliyun.oss.AliOSSAutoConfiguration
就行了。
测试
我们在其他项目中引入aliyun-oss-spring-boot-starter
依赖
可以看到,通过依赖传递,aliyun-oss-spring-boot-autoconfigure
也被引入。
然后我们编写测试程序。
package com.itheima.controller;
import com.aliyun.oss.AliOSSUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/upload")
public String upload(MultipartFile image) throws Exception {
//上传文件到阿里云 OSS
String url = aliOSSUtils.upload(image);
return url;
}
}
启动项目,并使用postman进行测试。
成功获得返回的url
JavaWeb总结