SpringBoot原理与bean的管理

SpringBoot原理与Bean管理

配置优先级

在SpringBoot项目中。支持三种格式的配置文件,分别是

  • application.yaml

  • application.yml

  • application.properties

他们的优先级由低到高。

SpringBoot除了支持配置文件属性配置,还支持java系统属性和命令行参数的方式进行属性配置。

image-20240803093051925

当我们即使用配置文件,又使用系统属性和命令行参数时,他们的优先级为。

image-20240803093220050

Bean管理

获取bean

image-20240803104234980

@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作用域

image-20240803104719621

我们可以通过@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

可以看到这是三个不同的对象。

image-20240803105135937

第三方的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);
    }

再次运行程序,可以看到正确解析。image-20240803111648060

image-20240803111708747

SpringBoot原理

Spring

image-20240803112253332

起步依赖

image-20240803112257832

基于Maven的依赖传递。

自动配置

image-20240803124559947

SpringBoot简化了开发流程,但是它是如何实现的呢,我们可以自己定义一个包,当作是外部依赖,然后让我们的项目引入这个依赖。

image-20240803125119446

<dependency>
        <groupId>com.example</groupId>
        <artifactId>itheima-utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
</dependency>

但是如果我们想在测试类里面从IOC容器中获取引用进来的工具类中的对象的时候,却发生了错误。

image-20240803132307512

解决方案一

使用@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);
    }

}

再次运行测试程序

image-20240803132348660

成功运行。虽然解决了问题,但是解决方式并不优雅。

解决方案二

@Import导入。使用@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种。

  • 导入普通类
  • 导入配置类
  • 导入ImportSelector接口实现类
  • @EnableXxxx注解,封装@Import注解

导入普通类

image-20240803133318865

运行成功

image-20240803133401960

导入配置类

image-20240803133614293

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均运行成功

image-20240803133806723

image-20240803133826024

导入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"};
    }
}

运行成功

image-20240803134218796

image-20240803134238860

@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 {
}

加上注解

image-20240803134537091

均运行成功(图懒得放了)

自动配置源码跟踪。

image-20240803140401166

image-20240803140409507

在配置中有很多的全类名,但是并不是全部都注册为IOC容器的Bean,源码中用到了@Conditional注解。

  • **@Conditional **

image-20240803140744701

image-20240803153951896

image-20240803153959456

自定义starter

image-20240803171345936

image-20240803171405081

在之前的部门和员工管理系统中,我们使用了阿里云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出马的时候了。

我们在这里直接新建两个模块,并精简一下。

image-20240803172738906

其中的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依赖

image-20240803174743645

可以看到,通过依赖传递,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进行测试。

image-20240803175007538

成功获得返回的url

JavaWeb总结

image-20240803175445440

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值