目录
一、SpringBoot IoC补充
SpringBoot和Spring的关系:
Spring:是一个针对于JavaSE和JavaEE提供更简单使用,主要提供了IoC(控制反转,用于解耦)和AOP(面向切面编程,用于增强)两大核心思想,可以简化有关一切Java应用的开发与维护工具,是一个全栈式(服务端全栈涵盖了Web层、Service层、Dao层)的框架
SpringBoot:引导开发人员使用Spring的一个脚手架,它是对Spring框架做了再封装,目的不是为了取代Spring,而是为了让开发人员更方便的使用Spring框架
1. 注册bean的方式
1.@Component及其衍生注解与组件扫描
注册bean的注解
注解:就是一个标记、一个记号。它本身没有任何功能,需要其它代码提供功能
在某一个类上,添加以下任何一个注解,就意味着:告诉Spring,这个类你要帮我创建对象,放到IoC容器里去
实际使用中:
-
web层的类:加@Controller
-
service层的类:加@Service
-
dao层的类:加@Repository
-
不在这三层的类:加@Component 在第三方的工具上 @Bean加在方法上
-
一个类要做为配置类:加@Configuration
组件扫描@ComponentScan
如果我们在某个类上加了上边的@Component或衍生注解,还需要由Spring扫描我们项目里所有的类:
-
当Spring发现某个类上有@Component或它的衍生注解,就会创建对象,并把对象放到IoC容器里
如果没有使用SpringBoot框架,而是使用原始的Spring:必须有一个配置类,类上加@ComponentScan("要扫描的包")
-
加了这个注解之后,Spring就会扫描我们指定的这个包里所有的类
如果使用了SpringBoot框架:它在引导类上的那个@SpringBootApplication
,其实已经包含了@ComponentScan
,我们就不需要自己再加@ComponentScan
了
2 @Configuration与@Bean
适合于第三方jar包里的类:
-
我们不能修改类,不可能在类上加@Component
-
如果想要让Spring创建对象放到容器里,就要使用@Configuration和@Bean
用法:
-
创建一个类,类上加@Configuration,就成为一个配置类
-
在配置类里增加一个方法,方法上加@Bean:Spring将会调用这个方法,把方法的返回值放到容器里
示例:要把SAXReader对象放到容器里
-
在pom.xml里添加dom4j的依赖坐标
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
2.创建一个配置类(配置类必须在扫描范围内),类里的方法上使用@Bean注解
@Configuration
public class DemoConfig {
/**
* 把SAXReader对象放到Spring容器里进行管理
*/
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
3.测试:能够从容器里获取到SAXReader对象,说明@Bean注册bean成功了
@SpringBootTest
public class Demo01IocTest {
@Autowired
private SAXReader reader;
@Test
public void test(){
System.out.println("reader = " + reader);
}
}
3 @Import【目前用到比较少】
适合于不在扫描范围内的类,要把对象交给Spring管理。有以下三个类,不在扫描范围内
@Import直接导入类
在引导类或者在配置类上加注解:@Import({类名1.class, 类名2.class, 类名3.class, ....})
例如:
引导类上使用@Import把这些类交给Spring管理
@SpringBootApplication
@Import({Demo01Bean.class, Demo02Bean.class, Demo03Bean.class})
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
测试效果:从容器里可以得到这三个bean对象,说明这三个类对象已经交给Spring管理了
@SpringBootTest
public class Demo01IocTest {
/**
* 注入Spring容器对象
*/
@Autowired
public ApplicationContext app;
@Test
public void testImport(){
//从Spring容器里分别获取bean对象并打印
System.out.println(app.getBean(Demo01Bean.class));
System.out.println(app.getBean(Demo02Bean.class));
System.out.println(app.getBean(Demo02Bean.class));
}
}
@Import导入ImportSelector类
在引导类或配置类上加注解@Import(ImportSelector接口的实现类.class)
。接口实现类里可以选择一批类名,Spring将会把这些类创建对象放到容器里
-
准备一个ImportSelector的实现类。
package com.itheima.importer;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ItcastImporter implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"cn.itcast.bean.Demo01Bean", "cn.itcast.bean.Demo02Bean", "cn.itcast.bean.Demo03Bean"};
}
}
2.在引导类或者配置类上注解
package com.itheima;
import cn.itcast.bean.Demo01Bean;
import cn.itcast.bean.Demo02Bean;
import cn.itcast.bean.Demo03Bean;
import com.itheima.importer.ItcastImporter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@SpringBootApplication
// 添加@Import注解,导入ImportSelector接口的实现类:ItcastImport
@Import(ItcastImporter.class)
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
4 小结
如果有一些类,需要把类的对象交给Spring进行管理,那么:
情况1:是自己写的类
给类上加@Component或衍生注解
把类写到扫描范围内(引导类所在的包下边)
情况2:是第三方的类(jar包里的类)
创建一个配置类,类上加@Configuration
类里加方法,方法上加@Bean:Spring会调用这个方法,把方法的返回值放到容器里
情况3:在扫描范围之外的类
在引导类或者在配置类上加@Import({类名.class, 类名.class, 类名.class, ...})
在引导类或者在配置类上加@Import(ImportSelector接口的实现类.class)
我们需要提前准备一个Java类,实现ImportSelector接口
重写接口的selectImports方法:方法里返回一批类名的数组
Spring将会调用这个方法,把这些类名对应的类,创建对象放到容器里
2. 配置bean的注解
项目启动之后进行初始化,比如数据库加载数据到缓冲中,通常我们会实现一个接口CommandLineRunner,重写run方法进行编辑
1 注解介绍
@Scope:加在bean对象上,用于设置bean对象的作用域
-
如果一个bean对象上没有加此注解:Spring默认以单例模式维护bean对象
从容器里获取这个bean对象,无论获取几次,得到的都是同一个对象
-
如果想要修改bean的作用域,可以给bean对象加注解
@Scope("singleton"):单例的。默认就是单例的
@Scope("prototype"):多例的。从容器里每次获取这个bean对象,Spring都将会创建一个新对象给我们
其它取值:
-
request:一次请求内是同一个对象,不同请求会有新的
-
session:一次会话内是同一个对象,不同会话会有新的
-
application:服务端只要不关闭重启,就是同一个对象
-
-
实际开发中:绝大多数情况下,都使用默认的单例
2 效果演示
@Scope效果
bean对象上加@Scope设置作用域
@Scope("singleton")
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
测试:从容器里多次获取相同的bean,如果是单例,获取的是同一个
@Test
public void testScope(){
//第1次从容器里获取DemoController对象
DemoController demo01 = app.getBean(DemoController.class);
System.out.println("demo01 = " + demo01);
//第2次从容器里获取DemoController对象
DemoController demo02 = app.getBean(DemoController.class);
System.out.println("demo02 = " + demo02);
//如果bean对象上有@Scope("singleton")或没有此注解的默认情况下,结果是true,Spring是以单例模式维护bean对象的
//如果bean对象上有@Scope("prototype"),结果是false,Spring将会以多例形式维护bean对象,每次获取时Spring都会创建新的对象
System.out.println(demo01 == demo02);
}
@PostConstruct和@PreDestroy
作用:
-
@PostConstruct:加在方法上,方法就是在bean对象被创建成功之后执行的方法
-
@PreDestroy:加在方法上,方法就是在bean对象销毁之前执行的方法
注意:
-
应用于非Lazy的单例bean对象上。
如果单例bean上
@Lazy
:-
表示让Spring不要一启动就创建此单例bean对象,而是晚一点,在第一次使用这bean时再创建
-
创建之后,仍然以单例模式维护这个bean对象
-
使用场景:
-
如果服务器一启动,就要做某些事情:在bean里增加一个方法,加
@PostConstruct
当服务器启动时,Springboot将会自动扫描所有的类,找到所有单例bean,马上创建对象放到容器里==>服务器一启动,就创建单例bean对象
-
如果在服务器关闭时,有一些收尾的工作:在bean里增加一个方法,加
@PreDestroy
当服务器关闭时,SpringBoot将会先销毁容器里所有的单例bean对象。在销毁之前,会先调用每个bean对象的销毁方法
@Scope("singleton")
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@PostConstruct
public void init(){
System.out.println("init,@PostConstructor,方法将会在当前bean对象创建成功之后自动执行");
}
@PreDestroy
public void destroy(){
System.out.println("destroy, @PreDestroy,方法将在bean对象销毁之前先执行一次");
}
}
3 小结
如果一个类已经让Spring帮我们创建完bean对象,我们可以告诉Spring如何管理这个bean对象:
如果想要单例bean,每次获取这个bean都是同一个对象:可以什么都不加,或者在bean对象上加@Scope("singleton")
如果想要多例bean,每次获取都要新的bean对象:要在bean上加@Scope("prototype")
如果想让Spring在创建bean对象之后,立即调用某个方法:就在方法上加@PostConstruct
如果想让Spring在销毁bean对象之前,先执行一次某方法:就在方法上加@PreDestroy
3. 依赖注入的注解
二、SpringBoot配置参数
项目里边配置参数
有三种配置文件:
-
application.yaml
-
application.yml
-
application.properties
三种方式的使用:
-
实际开发的时候,选中其中的一种格式使用。不要混用
-
三种混用时的优先级:
application.yaml
<application.yml
<application.properties
项目外修改参数
有两种方式
-
Java属性参数VM Options:在命令行里执行
java
命令时添加的参数。java -D参数名=值 -jar jar包名
-
程序参数Programs arguments:在命令行里执行命令时,给引导类设置的参数。
java -jar jar包名 --参数名=值
两种方式的使用:
-
VM Options:
java -Dserver.port=8084 -jar jar包名称
-
Program Arguments:
java -jar jar包名称 --server.port=8085
-
两种都用的优先级是:VM Options < Program Arguments
项目外修改参数
有两种方式
-
Java属性参数VM Options:在命令行里执行
java
命令时添加的参数。java -D参数名=值 -jar jar包名
-
程序参数Programs arguments:在命令行里执行命令时,给引导类设置的参数。
java -jar jar包名 --参数名=值
两种方式的使用:
-
VM Options:
java -Dserver.port=8084 -jar jar包名称
-
Program Arguments:
java -jar jar包名称 --server.port=8085
-
两种都用的优先级是:VM Options < Program Arguments
三、SpringBoot原理【面试】
准备基础代码
SpringBoot有什么好处:
-
提供了依赖版本锁定:只要项目里导入了父工程坐标,父工程会帮我们锁定一些常用依赖的版本号。依赖冲突的情况会减少
-
提供了起步依赖:一个起步依赖,实际是一批功能相关的依赖的集合体
-
提供了大量的默认配置:很多功能不需要加配置,使用默认值就可以正常运行。下面这个jar包里的这个文件,提供了默认值
约定大于配置
-
提供了自动装配:整合其它框架变得非常轻松。很多时候,只要导入第三方框架的起步依赖,就自动整合好了
-
内置Tomcat
1. SpringBoot父工程坐标
SpringBoot父工程坐标:里边已经提前帮我们锁定了大量常用依赖的版本号。有效减少了依赖冲突的机率
<!--SpringBoot的父工程坐标-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
2. SpringBoot起步依赖
我们在开发中,可能需要导入数十甚至数百个依赖,pom.xml文件可以要写数百行甚至数千行。
SpringBoot提供了起步依赖:一个起步依赖,就是一批相关功能的依赖集合体。
即:导入一个起步依赖,实际上导入了一批依赖包
-
SpringBoot官方提供的起步依赖,名称通常是:spring-boot-starter-xxx
-
第三方技术自己提供的起步依赖,名称通常是:xxx-spring-boot-starter
3. SpringBoot自动装配
1 什么是自动装配
SpringBoot在整合其它框架时,自动装配功能可以有效的减少整合的难度和复杂度。让很多框架与SpringBoot的整合变得极其简单
整合后的效果:
-
只要导入某框架的起步依赖,就可以直接使用这个框架了
-
只要导入某框架的起步依赖,就可以直接从IoC容器里获取框架相关的bean对象了
2 自动装配的效果演示
以druid起步依赖为例:
-
只要pom.xml里添加了druid的起步依赖,SpringBoot就会自动创建druid的连接池
DruidDataSource
对象放到IoC容器里 -
我们可以直接注入使用druid连接池对象,而不用自己创建对象了
直接注入DruidDataSource
对象的示例代码:
package com.itheima;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class DemoDruidStarterTest {
/**注入Druid连接池对象*/
@Autowired
private DruidDataSource dataSource;
@Test
public void test(){
//打印druid连接池对象。如果打印出来不是null,就说明IoC容器里已经有了Druid连接池对象。
//但是这个对象并不是我们创建然后放到容器里的,那么 谁帮我们创建了Druid连接池对象,并放到IoC容器里的呢?SpringBoot
System.out.println("dataSource = " + dataSource);
}
}
3 自动装配的原理
面试常问:SpringBoot工程的启动过程,和自动装配原理
启动过程
-
先运行引导类的main方法,main方法里执行的是:
SpringApplication.run(引导类.class, args)
-
SpringApplication.run(引导类.class, args)
:-
把引导类传递给SpringApplication类
-
SpringApplication创建了IoC容器对象
-
开始解析引导类上注解
@SpringBootApplication
,注解开始生效
-
-
@SpringBootApplication
注解的作用:-
@ComponentScan
:引导类会自动扫描组件。默认扫描的是“引导类所在的包” -
@SpringBootConfiguration
:是个组合注解,实际上引用的是@Configuration
,说明引导类也是个配置类 -
@EnableAutoConfigurutaion
:要开启自动装配功能
-
自动装配原理
-
引导类上
@SpringBootApplication
,实际上引用了@EnableAutoConfiguration
-
@EnableAutoConfiguration
使用的是@Import(AutoConfigurationImportSelector.class)
-
AutoConfigurationImportSelector
是ImportSelector接口的实现类:会重写接口的
selectImports
方法在
selectImports
方法里:-
会扫描所有类路径里的
META-INF/spring.factories
文件 -
读取文件里
...EnableAutoConfiguration=类名1.class,类名2.class,类名3.class,...
-
Spring然后找到这些类名对应的类
-
如果这些类上的
@Conditionalxxxx
注解满足条件,Spring就会创建对象放到IoC容器里
-
-
我们的代码里,就可以直接使用
@Autowired
注入需要用的bean对象了
四、案例-SpringBoot自定义启动器
需求
把阿里云的OSS工具,抽取到一个公用的模块里,把这个模块制作成一个启动器starter
在测试项目里引入starter,测试能否直接注入OSS工具对象,实现上传文件
自定义启动类:
准备资料
阿里云启动器starter工程的pom.xml里需要添加的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
config包下OssAutoConfiguration类:
package cn.itcast.config;
import cn.itcast.property.OssProperties;
import cn.itcast.utils.AliOSSUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
*
**/
@Configuration
@EnableConfigurationProperties(OssProperties.class)
public class OssAutoConfiguration {
@Bean
public AliOSSUtils ossUtils(OssProperties ossProperties){
return new AliOSSUtils(ossProperties);
}
}
property下OssProperties类:
package cn.itcast.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 读取配置文件的信息并且封装为对象
*
**/
@ConfigurationProperties(prefix = "oss")
@Data
public class OssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
utils包下AliOSSUtils类:
package cn.itcast.utils;
import cn.itcast.property.OssProperties;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
*/
@Data
public class AliOSSUtils {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public AliOSSUtils(OssProperties ossProperties){
endpoint = ossProperties.getEndpoint();
accessKeyId = ossProperties.getAccessKeyId();
accessKeySecret = ossProperties.getAccessKeySecret();
bucketName = ossProperties.getBucketName();
}
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 把上传到oss,并返回url路径
return upload(inputStream, originalFilename);
}
/**
* 上传文件到阿里云OSS
* @param inputStream 文件的输入流,用于读取要上传的文件数据
* @param filename 文件原始名称
* @return 文件的url路径
*/
public String upload(InputStream inputStream, String filename) throws IOException {
//重命名文件,避免文件覆盖
filename = UUID.randomUUID() + filename.substring(filename.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的路径返回
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.itcast.config.OssAutoConfiguration
安装到本地仓库
打开maven窗口,执行install命令。把启动器打包后安装到本地maven仓库里
测试工程引用启动器
pom.xml
修改pom.xml,添加启动器的依赖坐标
<!--把aliyun启动器导进来了-->
<dependency>
<groupId>org.example</groupId>
<artifactId>day14-aliyun-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
application.yaml配置阿里云OSS参数
oss:
endpoint: https://oss-cn-beijing.aliyuncs.com
accessKeyId: LTAI5tG3TbA9HLs22KEtimyB
accessKeySecret: 4avUxhaO5KCTl5pqpta3AdU98mT9um
bucketName: itheima-liuyp
使用AliOSSUtils上传文件
package com.itheima;
import cn.itcast.utils.AliOSSUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class AppTest {
@Autowired
private AliOSSUtils aliOSSUtils;
@Test
public void test(){
System.out.println(aliOSSUtils);
}
}