SpringBoot自动配置原理学习与基于原理自定义aliyun-oss-spring-boot-starter依赖

SpringBoot


自动配置

组件扫描

示例要引入的工具类:

java 复制代码
@Component
public class TokenParser {
    public void parse(){
        System.out.println("TokenParser ... parse ...");
    }
}

测试类方法如下:

java 复制代码
@SpringBootTest
public class AutoConfigurationTests {
    
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testTokenParse(){
        System.out.println(applicationContext.getBean(TokenParser.class));
    }

}
  • 当前测试方法运行时在Spring容器中没有找到com.example.TokenParse类型的bean对象。

    • 因为在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。

    • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。

因此。我们需要在启动类上面加上@ComponentScan来进行组件扫描:

JAVA 复制代码
@ComponentScan({"com.XXX"}) //指定要扫描的包
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

除此之外,还可以使用@Import导入要扫描的类:

  1. 导入普通类
  2. 导入配置类
  3. 导入ImportSelector接口实现类

一、导入普通类示例:

  • 启动类代码块:
JAVA 复制代码
@Import(XXX.class)//导入的类会被Spring加载到IOC容器中
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

二、导入配置类示例:

  • 启动类代码块:
JAVA 复制代码
@Import(XXX.class) //导入配置类
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

三、使用@Import导入ImportSelector接口实现类:

  • 启动类代码块:
JAVA 复制代码
@Import(XXXImportSelector.class) //导入ImportSelector接口实现类
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

第三方提供的组件扫描

以上配置仍比较繁琐,实际上,一般第三方依赖都会给我们@EnableXXX注解

只需要在启动类上加入@EnableXXX注解就可以引入Bean对象

  • 第三方注解:
JAva 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(XXXImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableXXX { 
}
  • 启动类代码块:
Java 复制代码
@EnableXXX  //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

自动配置原理

  • 在启动类上有@SpringBootApplication注解
java 复制代码
@SpringBootApplication
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

@SpringBootApplication注解中其实就封装了配置类注解 @SpringBootConfiguration标识当前类是一个配置类。除此之外还有组件扫描注解 @ComponentScan 来进行组件扫描*(SpringBoot中默认扫描的是启动类所在的当前包及其子包)*。

还有@EnableAutoConfiguration注解,而这个注解又封装了@Import注解来指定一个ImportSelection 接口的实现类,该实现类重写了**selectionImports()**方法,读取当前项目下所有依赖jar包中META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

于是,当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息*(类的全限定名)*封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

值得注意的是,在声明Bean对象时,又会使用@Conditional开头的相关注解来按照条件进行装配。只有满足了一定的条件,才会把bean对象注册到Spring的IOC容器中。


关于@Conditional相关注解

这些注解都是用来判定是否满足条件,if { true }则将对应bean对象注册到Spring IOC容器中。

条件装配注解:
  • @ConditionalOnClass:判断环境中 对应字节码文件,才注册bean到IOC容器。
  • @ConditionalOnMissingBean:判断环境中 没有 对应的bean(类型或名称),才注册bean到IOC容器。
  • @ConditionalOnProperty:判断配置文件中 对应属性和值,才注册bean到IOC容器。

根据自动配置原理手搓一个aliyun-oss-spring-boot-starter上传文件

  • 创建aliyun-oss-spring-boot-starter 模块进行阿里云依赖的统一管理,并且引入aliyun-oss-spring-boot-autoconfigure 这样在使用时只需要引入starter起步依赖即可。不存放任何代码文件。

  • aliyun-oss-spring-boot-starter模块中的pom文件配置:

    springboot会通过依赖传递自动将autoconfigure依赖也一并传递下来。

  • 创建aliyun-oss-spring-boot-autoconfigure 模块,创建AliyunOSSProperties 实体类,AliyunOSSOperator 工具类,AliOSSAutoConfiguration 配置类。除此之外,还要再引入有关于阿里云OSS的各项依赖信息。

AliyunOSSOperator 工具类:

(详细源码置于文末)

值得注意的是,需要设置一个有参构造函数 来将AliyunOSSProperties 对象传递给工具类。

AliyunOSSProperties 实体类:

java 复制代码
@Data
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
    private String region;
}

此时的AliyunOSSProperties类还未通过 @EnableConfigurationProperties 注册并标记为 Spring 组件

因此,要定义一个自动配置类 AliOSSAutoConfiguration 配置类,在该自动配置类当中来声明 bean对象。

AliOSSAutoConfiguration 配置类:

Java 复制代码
@EnableConfigurationProperties(AliyunOSSProperties.class)
@Configuration
public class AliyunOSSAutoConfiguration{

    @Bean
    @ConditionalOnMissingBean
    public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties){
        return new AliyunOSSOperator(aliyunOSSProperties);
    }
}

@EnableConfigurationProperties 注解底层封装了**@Import注解,而前面又了解到 @Import**注解可以导入要扫描的普通类,于是,这里就成功地将AliyunOSSProperties.class引入了IOC容器中声明为Bean对象。


不可忽略的十分重要的最后一步:

aliyun-oss-spring-boot-autoconfigure 模块中的resources下,新建自动配置文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内信息:

imports 复制代码
com.aliyun.oss.AliyunOSSAutoConfiguration

其实就是AliyunOSSAutoConfiguration配置类的全类名

最后就可以创建一个测试工程来检查功能的实现了

需要在工程的pom文件下引入已经aliyun-oss-spring-boot-starter起步依赖:

XML 复制代码
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

并且在yml配置文件中配置好阿里云的配置信息:

YAML 复制代码
aliyun:
  oss:
    endpoint: 填入地址
    bucketName: 仓库名称
    
    #时区信息
    region: cn-beijing

UploadController类中的编码:

Java 复制代码
@RestController
public class UploadController {

    @Autowired
    private AliyunOSSOperator aliyunOSSOperator;

    @PostMapping("/upload")
    public String upload(MultipartFile image) throws Exception {
        //上传文件到阿里云 OSS
        String url = aliyunOSSOperator.upload(image);
        System.out.println(url);
        return url;
    }

}

启动工程,在Apifox中发送请求:

测试成功,返回了url地址信息,完结撒花。

以后在其他项目中直接把依赖引入进来并且配置有关OSS信息就可以直接使用了,虽然写起步依赖挺麻烦的,但是十分方便了未来的复用。

摸清楚spring boot原理对于项目还是很有帮助的。






AliyunOSSOperator 工具类详细源码:

Java 复制代码
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;


public class AliyunOSSOperator{
    /*通过@value注解单个属性注入
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    // 填写Bucket名称,例如examplebucket。
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;
    // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
    @Value("${aliyun.oss.region}")
    private String region;*/

    //@ConfigurationProperties注解注入

    private AliyunOSSProperties aliyunOSSProperties;

    /* 有参构造 */
    public AliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties) {
        this.aliyunOSSProperties = aliyunOSSProperties;
    }

    public String upload(MultipartFile file) throws Exception {
        String endpoint = aliyunOSSProperties.getEndpoint();
        String bucketName = aliyunOSSProperties.getBucketName();
        String region = aliyunOSSProperties.getRegion();

        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();

        //1.获取原始文件名称
        String originalFilename = file.getOriginalFilename();
        //2.获取原始文件名后缀
        String extension = "";
        int lastDotIndex = originalFilename.lastIndexOf('.');
        //2.1 IF语句判断lastDotIndex是否大于0,大于0则截取,避免"."为文件名称开头
        if (lastDotIndex > 0) {
            extension = originalFilename.substring(lastDotIndex);
        }
        //3.生成新文件名
        String newFileName = UUID.randomUUID().toString()+extension;
        //获取当前系统日期的字符串,格式为yyyy/MM
        String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = dir + "/" + newFileName;

        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        //String filePath= "";


        // 创建OSSClient实例。
        // 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName,file.getInputStream());
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //将文件的URL地址作为返回值
        return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName;
    }
}

AliyunOSSProperties 实体类:

Java 复制代码
import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
    private String region;

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    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;
    }

    @Override
    public String toString() {
        return "AliyunOSSProperties{" +
                "endpoint='" + endpoint + '\'' +
                ", bucketName='" + bucketName + '\'' +
                ", region='" + region + '\'' +
                '}';
    }
}

AliOSSAutoConfiguration 配置类:

java 复制代码
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@EnableConfigurationProperties(AliyunOSSProperties.class)
@Configuration
public class AliyunOSSAutoConfiguration{

    @Bean
    @ConditionalOnMissingBean
    public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties){
        return new AliyunOSSOperator(aliyunOSSProperties);
    }
}

aliyun-oss-spring-boot-starter模块中的pom文件配置:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aliyun-oss-spring-boot-autoconfigure</name>
    <description>aliyun-oss-spring-boot-autoconfigure</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.17.4</version>
            <scope>compile</scope>
        </dependency>

        <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>
        <!-- no more than 2.3.3-->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
    </dependencies>


</project>

aliyun-oss-spring-boot-starter模块pom文件:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aliyun-oss-spring-boot-starter</name>
    <description>aliyun-oss-spring-boot-starter</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
相关推荐
雨中飘荡的记忆41 分钟前
设计模式之组合模式
java·设计模式
半路_出家ren43 分钟前
Tomcat下配置woniusales
java·数据库·mysql·网络安全·adb·tomcat·firewalld
tgethe44 分钟前
MybatisPlus基础部分详解(下篇)
java·spring boot·mybatisplus
f***686044 分钟前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
f***45321 小时前
JavaWeb项目打包、部署至Tomcat并启动的全程指南(图文详解)
java·tomcat
m***66731 小时前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
CoderYanger1 小时前
递归、搜索与回溯-穷举vs暴搜vs深搜vs回溯vs剪枝:13.子集
java·算法·leetcode·机器学习·剪枝·1024程序员节
f***6511 小时前
spring 跨域CORS Filter
java·后端·spring
hhwyqwqhhwy1 小时前
linux 设备树内容和plateform_device
java·linux·数据库