JavaWeb后端开发:原理篇

JavaWeb后端开发原理篇

配置优先级

1.SpringBoot中支持三种格式的配置文件,优先级为(高->低):application.propertiesapplication.ymlapplication.yaml

注意:虽然SpringBoot支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置 (yml是主流)。

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

Java系统属性:-Dserver.port=9000

命令行参数:--server.port=10010

测试优先级:

1.执行maven打包指令package

注意:Springboot项目进行打包时,需要引入插件spring-boot-maven-plugin(基于官网骨架创建项目,会自动添加该插件)。

2.执行java指令,运行jar包。

1
java -Dserver.port=9000 -jar springboot-web-tlias-0.0.1-SNAPSHOT.jar --server.port=10010

优先级排序(低->高):

1
2
3
4
5
application.yaml(忽略)
application.yml
application.properties
java系统属性(-Dxxx=xxx)
命令行参数(--xxx=xxx)

Bean管理

获取Bean

默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过如下方式:

  1. 根据name获取beanObject getBean(String name)
  2. 根据类型获取bean<T> T getBean(Class<T> requiredType)
  3. 根据name获取bean(带类型转换):<T> T getBean(String name, Class<T> requiredType)

注意:上述所说的 【Spring项目启动时,会把其中的bean都创建好】还会受到作用域延迟初始化影响,这里主要针对于默认的单例非延迟加载的bean而言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itheima;

@SpringBootTest
class SpringbootWebTliasApplicationTests {
@Autowired
private ApplicationContext applicationContext;//注入IOC容器对象

@Test
public void testGetBean(){
//根据Bean的名称获取
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");//Bean对象名称默认首字母小写
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);
//上述三个获取的都是同一个BeanIOC容器对象默认只有一个,默认情况下Bean是单例的
}
}
Bean作用域

Spring支持五种作用域,后三种在web环境才生效:

作用域 说明
singleton 容器内同名称的 bean 只有一个实例(单例)(默认)
prototype 每次使用该 bean 时会创建新的实例(非单例)
request 每个请求范围内会创建新的实例(web环境中,了解)
session 每个会话范围内会创建新的实例(web环境中,了解)
application 每个应用范围内会创建新的实例(web环境中,了解)

可以通过@Scope注解来进行配置作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//在DeptController类中写一个无参构造函数
public DeptController(){
System.out.println("调用DeptController构造方法");
}

//在SpringbootWebTliasApplicationTests中进行测试,观察无参构造函数的调用情况
//bean的作用域
@Test
public void testScope(){
for (int i = 0; i < 10; i++) {
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController);
}
}

注意:

  1. 默认singletonbean,在容器启动时被创建(Bean对象在容器启动的时候就实例化了,并将实例化的Bean对象放到IOC容器当中),可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)。在一个类上加上@Lazy注解可以让该Bean对象延迟初始化。
  2. prototypebean,每一次使用该bean的时候都会创建一个新的实例。
  3. 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性。
第三方Bean

1.如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component及衍生注解声明bean的,就需要用到@Bean注解。

1
2
3
4
5
6
<!--dom4j,解析xml文件-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
1
2
3
4
5
6
<!-- 位于src/main/resources/1.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<emp>
<name>Tom</name>
<age>18</age>
</emp>
1
2
3
4
5
6
7
8
9
10
package com.itheima;

@SpringBootApplication
public class SpringbootWebTliasApplication {//在启动类声明第三方Bean
//声明第三方Bean
@Bean //将方法返回值交给IOC容器管理,成为IOC容器的Bean对象
public SAXReader saxReader(){
return new SAXReader();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.itheima;

@SpringBootTest
class SpringbootWebTliasApplicationTests {//在测试类进行测试
@Autowired
private SAXReader saxReader;//注入SAXReader

//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
//SAXReader saxReader = new 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);
}
}

2.若要管理的第三方bean对象,建议对这些bean进行集中分类配置,可以通过@Configuration注解声明一个配置类。

1
//将启动类中的@Bean删除,将其复制到如下类,统一管理第三方Bean对象
1
2
3
4
5
6
7
8
9
10
11
package com.itheima.config;

@Configuration //配置类
public class CommonConfig {
//声明第三方Bean
@Bean //将方法返回值交给IOC容器管理,成为IOC容器的Bean对象
//通过@Bean注解的name/value属性指定bean名称,如果未指定,默认是方法名(不需要首字母大写)
public SAXReader saxReader(){
return new SAXReader();
}
}

注意:

1.通过@Bean注解的namevalue属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。

1
2
3
4
5
@Test
public void testGetBean2(){
Object saxReader = applicationContext.getBean("saxReader");//通过名字可获得上述Bean对象
System.out.println(saxReader);
}

2.如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。

1
2
3
4
5
6
7
@Bean
public SAXReader saxReader(DeptService deptService) {
//自定义对象通过@Autowired进行依赖注入。
//第三方Bean通过指定方法形参即可进行依赖注入,Spring容器会进行自动装配,根据类型去IOC容器中找到Bean对象完成注入操作。
System.out.println(deptService);
return new SAXReader();
}

@Component及衍生注解与@Bean注解使用场景:

  1. 项目中自定义的,使用@Component及其衍生注解。
  2. 项目中引入第三方的,使用@Bean注解。

SpringBoot原理

起步依赖

起步依赖的原理:Maven的依赖传递。

自动配置

SpringBoot的自动配置就是当Spring容器启动后,一些配置类、Bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

【案例】

新建项目itheima-utils,声明了三个类型的Bean对象(这三个Bean对象所在的包为com.example)。

1
2
3
4
5
6
7
8
package com.example;

@Component//使用@Component注解交给IOC容器管理
public class TokenParser {
public void parse(){
System.out.println("TokenParser ... parse ...");
}
}
1
2
3
4
5
6
7
package com.example;

public class HeaderParser {
public void parse(){
System.out.println("HeaderParser ... parse ...");
}
}
1
2
3
4
5
6
7
package com.example;

public class HeaderGenerator {
public void generate(){
System.out.println("HeaderGenerator ... generate ...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example;

@Configuration//配置类
public class HeaderConfig {
@Bean//交给IOC容器管理
public HeaderParser headerParser(){
return new HeaderParser();
}

@Bean//交给IOC容器管理
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}

在项目itheima-utils同一目录下另一个项目springboot-web-test中,引入项目itheima-utils

1
2
3
4
5
6
<!--引入第三方依赖-->
<dependency>
<groupId>com.example</groupId>
<artifactId>itheima-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

然后测试是否引入了项目itheima-utils的三个类型的Bean对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima;

@SpringBootTest
class SpringbootWebTestApplicationTests {
//获取TokenParser
@Test
public void testTokenParser(){
System.out.println(applicationContext.getBean(TokenParser.class));
}

//获取HeaderParser
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}

//获取HeaderGenerator
@Test
public void testHeaderGenerator(){
System.out.println(applicationContext.getBean(HeaderGenerator.class));
}
}

【报错】org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.TokenParser' available

上述三个测试方法都会出现同一个报错:org.springframework.beans.factory.NoSuchBeanDefinitionException。因为启动类的注解@SpringBootApplication进行包扫描时,扫描范围是当前包及其子包,即:com.itheima及其子包,所以无法扫描第三方依赖中的com.example这个包。

自动配置原理

方案一:@ComponentScan组件扫描(使用繁琐,性能低)。

1
2
3
4
5
6
7
@ComponentScan({"com.itheima", "com.example"})
@SpringBootApplication
public class SpringbootWebTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebTestApplication.class, args);
}
}

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

1.导入普通类。

1
2
3
4
5
@Import({TokenParser.class})//导入普通类(普通类上不用加任何注解),交给IOC容器管理
@SpringBootApplication
public class SpringbootWebTestApplication {

}

2.导入配置类。

1
2
3
4
5
@Import({HeaderConfig.class})//导入配置类,交给IOC容器管理
@SpringBootApplication
public class SpringbootWebTestApplication {

}

3.导入ImportSelector接口实现类。

1
2
3
4
5
6
7
8
package com.example;

public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
//返回类名数组,将这些类交给IOC容器管理
}
}
1
2
3
4
5
@Import({MyImportSelector.class})//导入ImportSelector接口实现类
@SpringBootApplication
public class SpringbootWebTestApplication {

}

4.@EnableXxxx注解,封装@Import注解。(让第三方提供要导入的类,更方便,是SpringBoot中采用的方式)

1
2
3
4
5
6
7
package com.example;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//第三方使用@Import导入需要的类
public @interface EnableHeaderConfig {
}
1
2
3
4
5
@EnableHeaderConfig//使用第三方提供的注解导入类
@SpringBootApplication
public class SpringbootWebTestApplication {

}
源码跟踪

@SpringBootApplication:该注解标识在SpringBoot工程引导类上,是SpringBoot中最最最重要的注解。该注解由三个部分组成:

  1. @SpringBootConfiguration:该注解与@Configuration注解作用相同,用来声明当前也是一个配置类。所以可以在启动类中声明第三方Bean,因为启动类也是一个配置类。

    @indexed用来加速应用启动。

  2. @ComponentScan组件扫描,默认扫描当前引导类所在包及其子包。

  3. @EnableAutoConfigurationSpringBoot实现自动化配置的核心注解

spring.factoriesSpringBoot早期自动配置加载的文件,在SpringBoot2.7.x版本之后,提供了全新的自动配置文件org.springframework.boot.autoconfigure.AutoConfiguration.imports,会兼容SpringBoot文件。

@Conditional

作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的Bean对象到Spring IOC容器中。

位置:方法、类。

@Conditional本身是一个父注解,派生出大量的子注解:

1.@ConditionalOnClass:判断环境中是否有对应字节码文件,才注册beanIOC容器。

1
2
3
4
5
6
@Bean
@ConditionalOnClass(name = "io.jsonwebtoken.Jwts")
//Spring容器启动时自动判断当前环境中是否存在指定的Jwts这个类,存在这个类才会将如下Bean对象注册到IOC容器中
public HeaderParser headerParser(){
return new HeaderParser();
}

如果引入了jjwt依赖,则可以测试成功(上述@Test测试方法),否则测试失败。

1
2
3
4
5
6
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>

2.@ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册beanIOC容器。

1
2
3
4
5
6
@ConditionalOnMissingBean 
//不存在该类型的Bean,才会将该Bean加入IOC容器中 --- 指定类型(value属性)或名称(name属性)。
//应用:设置默认的Bean对象。用户如果自定义了该类型Bean,就使用用户自定义的,默认的不会生效。用户没有自定义,则使用默认的Bean。
public HeaderParser headerParser(){
return new HeaderParser();
}

3.@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册beanIOC容器。

1
2
3
4
5
@ConditionalOnProperty(name = "name", havingValue = "itheima")
//配置文件中存在指定的属性与值,才会将该Bean加入IOC容器中。
public HeaderParser headerParser(){
return new HeaderParser();
}

application.properties文件中,加入如下属性值:

1
name=itheima

如果是application.yml文件,则为:

1
name: itheima
案例(自定义starter)

在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBootstarter

需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSUtils的自动配置。

目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunOSSUtils直接使用即可。

步骤:

1.创建aliyun-oss-spring-boot-starter模块。该模块下只有aliyun-oss-spring-boot-starter.imlpom.xml两个文件。

pom.xml文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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.3.5</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>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

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

</project>

如果没有iml文件,点击两下Ctrl键,在如下页面运行mvn idea:module命令,即可生成aliyun-oss-spring-boot-starter.iml文件。

2.创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块。

aliyun-oss-spring-boot-autoconfigure模块中的定义自动配置功能,并定义自动配置文件META-INF/spring/xxxx.imports

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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.3.5</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>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!--SpringWeb开发依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.4</version>
</dependency>

<!--阿里云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>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>

</dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.aliyun.oss;

import org.springframework.boot.context.properties.ConfigurationProperties;

@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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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 工具类
*/
public class AliOSSUtils {
/* @Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;*/


private AliOSSProperties aliOSSProperties;

public AliOSSProperties getAliOSSProperties() {
return aliOSSProperties;
}

public void setAliOSSProperties(AliOSSProperties aliOSSProperties) {
this.aliOSSProperties = aliOSSProperties;
}

/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
//获取阿里云OSS参数
String endpoint = aliOSSProperties.getEndpoint();
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
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的路径返回
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.aliyun.oss;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(AliOSSProperties.class)//底层是@Import注解,只能用于@Bean方法或@Configuration类上
//通过该注解将AliOSSProperties类导入IOC容器中,成为IOC容器的Bean对象
public class AliOSSAutoConfiguration {
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties) {
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}

org.springframework.boot.autoconfigure.AutoConfiguration.imports文件内容:

1
com.aliyun.oss.AliOSSAutoConfiguration

在测试项目springboot-web-testpom.xml文件中引入依赖:

1
2
3
4
5
6
<!--自定义阿里云上传文件-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.itheima.controller;

@RestController
public class UploadController {//测试阿里云上传文件
@Autowired
private AliOSSUtils aliOSSUtils;

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

测试链接:使用Postman进行测试,接口(POST):http://localhost:8080/upload

Web后端开发总结


JavaWeb后端开发:原理篇
http://surourou8.github.io/2024/10/29/JavaWeb后端开发:原理篇/
作者
Su Rourou
发布于
2024年10月29日
许可协议