java-web/docs/chapter05.md
2024-11-05 09:26:27 +08:00

1218 lines
50 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 5. 基于Spring框架的后端应用开发
### 5.1 Java基础增强(掌握)
#### 5.1.1 Java异常处理
> `异常`是在程序执行过程中发生的任何非正常情况或问题。它们打断了正常的程序流程,需要特别处理来确保程序的健壮性和可靠性。
**java异常类的继承关系**:
![](./resources/exception-hierarchy.png)
`Throwable`类是所有异常的超类,它不能被实例化。`Throwable`有两个重要的子类:`Exception`和`Error`。
- Throwable: 所有错误和异常的超类。
- Error: 表示合理的应用不应该试图捕获的严重问题。例如,内存溢出等。
- Exception: 表示合理的应用可能想要捕获的条件。异常通常由编程错误之外的情况引发。
**非检查异常 (Unchecked Exceptions)**
- 包括Error和RuntimeException及其子类。
- 这些异常通常是由编程错误引起的,如数组索引越界或空指针引用。
- 编译器不会强制要求处理这些异常,因此称为“非检查”。
**检查异常 (Checked Exceptions)**
- 除了Error和RuntimeException之外的所有其他异常。
- 这些异常是程序应该能够预料到并需要处理的,如文件未找到异常。
- 编译器要求要么在调用方法时处理这些异常使用try-catch块要么在方法声明中声明它们可能会抛出这些异常使用throws关键字
**Java异常使用**
**关键词:**
* try: 用于标记可能抛出异常的代码块。
* catch: 用于捕获并处理try块中抛出的异常。
* finally: 不管是否有异常发生finally块中的代码总会被执行。
* throw: 用于抛出一个新的异常实例。
* throws: 用于声明一个方法可能会抛出的异常类型。
**自定义异常类**
创建自定义异常类时可以根据是否需要强制性地进行异常处理来选择继承Exception还是RuntimeException。
- 继承Exception检查异常: 如果希望强制用户处理这个异常可以选择继承Exception。
- 继承RuntimeException非检查异常: 如果异常是由于编程错误引起的或者不需要强制处理可以继承RuntimeException。
### 5.1.2 注解(掌握)
> 注解 (Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
>
> ——摘自百度百科
> 元数据: 说明数据的数据
#### 注解定义的语法
```java
public @interface 注解名称 {
// 属性列表;
}
```
#### 类型
- **元注解**:用于注解其他注解的注解,如 `@Retention``@Target`
- **内置注解**Java 平台提供的注解,如 `@Override`、`@Deprecated`、`@SuppressWarnings` 等。
- **自定义注解**:我们自己编写的注解。很多第三方框架通过提供自定义注解来简化实现,例如 Spring 框架提供了 `@Autowired`、`@RequestMapping` 等。
#### 使用场景
- **为框架提供元数据**:例如 Spring 框架使用注解来标记组件。
- **用于代码生成**:例如 Lombok 库使用注解来自动生成模板化的代码。
- **用于编译时处理**:例如通过注解来检查代码中的潜在错误。
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。
#### 使用位置
在实际开发中,注解可以出现在类、方法、成员变量、方法参数等多个位置。
#### 级别
注解与类、接口、枚举处于同一级别。注解的本质类似于 `@interface`,与 `interface` 相似,但用于提供元数据。
#### 注解三角
- **定义注解**:创建注解。
- **使用注解**:在代码中应用注解。
- **读取注解**:通过反射或其他机制读取并处理注解。
#### 示例
以下是一些具体的示例来展示如何定义和使用注解:
##### 定义注解
```java
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.METHOD) // 注解的目标类型
public @interface MyCustomAnnotation {
String value() default ""; // 默认属性值
}
```
##### 使用注解
```java
public class MyClass {
@MyCustomAnnotation(value = "Hello, World!")
public void myMethod() {
System.out.println("Executing myMethod...");
}
}
```
##### 读取注解
```java
import java.lang.reflect.Method;
public class AnnotationReader {
public static void main(String[] args) throws Exception {
Method method = MyClass.class.getMethod("myMethod");
MyCustomAnnotation annotation = method.getAnnotation(MyCustomAnnotation.class);
if (annotation != null) {
System.out.println("Found annotation with value: " + annotation.value());
}
}
}
```
#### 5.1.3 `Lambda`表达式(掌握)
Lambda 表达式是 Java 8 引入的一个重要特性它允许你以简洁的语法编写匿名函数。Lambda 表达式主要用于实现单个抽象方法的接口这些接口通常被称为函数式接口Functional Interface。以下是 Java 中 Lambda 表达式的简单教程:
##### 函数式接口Functional Interface
函数式接口只有一个抽象方法的接口。为了声明一个函数式接口,你可以使用 `@FunctionalInterface` 注解,这样可以确保接口只有一个抽象方法。
```java
@FunctionalInterface
public interface SimpleFunctionalInterface {
void doSomething();
}
```
##### Lambda 表达式的基本语法
Lambda 表达式的一般语法是:
> (parameters) -> expression
或者
> (parameters) -> { statements; }
- `parameters`:这是参数列表,可以是一个或多个参数,参数类型可以省略。
- `->`:这是 Lambda 操作符,将参数部分与 Lambda 体分开。
- `expression``statements`:这是 Lambda 体,可以是一个表达式或一个代码块。
##### 使用 Lambda 表达式
###### 无参数和无返回值
```java
SimpleFunctionalInterface sfi = () -> System.out.println("Hello Lambda!");
sfi.doSomething(); // 输出 "Hello Lambda!"
```
###### 一个参数和无返回值
```java
@FunctionalInterface
interface Greeting {
void greet(String name);
}
Greeting greet = (String name) -> System.out.println("Hello, " + name);
greet.greet("Java"); // 输出 "Hello, java"
```
###### 一个参数和有返回值
```java
@FunctionalInterface
interface Converter {
String convert(String from);
}
Converter converter = (from, to) -> from.toUpperCase();
System.out.println(converter.convert("Java")); // 输出 "JAVA"
```
###### 多个参数和有返回值
```java
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
Calculator add = (a, b) -> a + b;
System.out.println(add.calculate(5, 10)); // 输出 15
```
##### Lambda 表达式与方法引用
如果 Lambda 表达式实现的是一个已有的方法,你可以使用方法引用来简化代码。
###### 静态方法引用
静态方法/类方法static修饰的方法引用
> 语法: 类名::方法名
**注意事项:**
- 被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
- 接口的抽象方法没有返回值,引用的方法可以有返回值也可以
- 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!!
- 由于满足抽象参数列表与引用参数列表相同,所以可以写成静态方法引用的格式
```java
public class StringUtils {
public static String concatenate(String from, String to) {
return from + ", " + to;
}
}
```
```java
Converter concat = StringUtils::concatenate;
System.out.println(concat.convert("Hello", "Lambda")); // 输出 "Hello, Lambda"
```
###### 实例方法引用
实例方法非static修饰的方法引用
> 语法: 对象名:∶非静态方法名
**注意事项:**
- 被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
- 接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
- 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!
```java
public class Combiner {
public String combine(String part1, String part2) {
return part1.toUpperCase() + ", " + part2.toUpperCase();
}
}
```
```java
Combiner combiner = new Combiner();
Converter append = combiner::combie;
System.out.println(append.convert("Hello", "Lambda")); // 输出 "Hello, Lambda"
```
###### 构造函数引用
构造方法引用
> 语法: 类名::new
**注意事项:**
- 被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致
```java
// 假设我们有一个简单的类
class Example {
public Example() {
}
public String doSomething() {
return "Example do something“;
}
}
// 接口定义需要与Example的构造函数匹配
interface ExampleSupplier {
Example get();
}
public class Main {
public static void main(String[] args) {
// 使用lambda表达式和构造函数引用创建ExampleSupplier实例
ExampleSupplier supplier = Example::new;
// 使用ExampleSupplier实例来创建Example对象
Example example = supplier.get();
example.doSomething();
}
}
```
##### Lambda 表达式与 Stream API
Lambda 表达式在处理集合时非常有用,特别是与 Java 8 引入的 Stream API 结合使用。
```java
List<String> names = Arrays.asList("Kimi", "Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // 输出 "Alice"
```
#### 5.1.4 `Optional` 的介绍和使用(掌握)
#### 定义
`Optional` 是 Java 8 引入的一个容器类,用于封装可能为 `null` 的值。它的主要目的是帮助开发者避免空指针异常,并提供一种更优雅的方式来处理可能不存在的值。
#### 用途
- **封装可能为 `null` 的对象**: `Optional` 提供了一种安全的方式来处理可能为 `null` 的对象,从而避免了空指针异常。
- **提供链式操作**: `Optional` 支持方法链式调用,使得代码更加简洁和可读。
#### 主要方法
- **`Optional.of(T value)`**: 如果 `value` 不为 `null`,则创建一个包含该值的 `Optional` 对象;如果 `value``null`,则抛出 `NullPointerException`
- **`Optional.ofNullable(T value)`**: 如果 `value` 不为 `null`,则创建一个包含该值的 `Optional` 对象;如果 `value``null`,则返回一个空的 `Optional` 对象。
- **`Optional.empty()`**: 返回一个空的 `Optional` 对象。
- **`Optional.ifPresent(Consumer<? super T> action)`**: 如果 `Optional` 包含值,则执行 `action`;否则不执行任何操作。
- **`Optional.isPresent()`**: 判断 `Optional` 是否包含值。
- **`Optional.orElse(T other)`**: 如果 `Optional` 包含值,则返回该值;否则返回 `other`
- **`Optional.orElseGet(Supplier<? extends T> other)`**: 如果 `Optional` 包含值,则返回该值;否则返回由 `Supplier` 提供的值。
- **`Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)`**: 如果 `Optional` 包含值,则返回该值;否则抛出由 `exceptionSupplier` 生成的异常。
#### 示例
```java
import java.util.Optional;
import java.util.Random;
/**
* OptionalExample 类演示了 Java Optional 类的多种用法
*/
public class OptionalExample {
/**
* 获取一个随机名字的 Optional 对象
* 有 50% 的概率返回包含 "John Doe" 的 Optional 对象,否则返回空的 Optional 对象
*
* @return Optional<String> 包含名字的 Optional 对象或空的 Optional 对象
*/
private static Optional<String> getName() {
if (new Random().nextBoolean()) {
return Optional.of("John Doe");
} else {
return Optional.empty();
}
}
public static void main(String[] args) {
// 获取可能为空的名字用Optional包装以安全地处理空值
Optional<String> optional = getName();
// 使用 ifPresent 方法处理 Optional 中的值
optional.ifPresent(System.out::println);
optional.ifPresent(name -> System.out.println("Name: " + name.toLowerCase()));
// 使用 orElse 处理 Optional 为空的情况
String name = optional.orElse("Default Name");
System.out.println("Name: " + name);
// 使用 orElseGet 处理 Optional 为空的情况
name = optional.orElseGet(() -> "Computed Name");
System.out.println("Computed Name: " + name);
// 使用 orElseThrow 处理 Optional 为空的情况
try {
name = optional.orElseThrow(() -> new Exception("Name is missing"));
System.out.println("Name: " + name);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
```
#### 小结
- **避免空指针异常**: 使用 `Optional` 可以有效地避免空指针异常,并提供一种更优雅的方式来处理可能为 `null` 的值。
- **链式操作**: `Optional` 支持方法链式调用,使得处理逻辑更加简洁明了。
- **提供多种处理方式**: `Optional` 提供了多种方法来处理可能为空的情况,如 `ifPresent`、`orElse`、`orElseGet` 和 `orElseThrow` 等。
通过使用 `Optional`,开发者可以写出更加健壮和易于维护的代码。
#### 5.1.5 Stream API 的介绍和使用(了解)
#### 定义
Stream API 是 Java 8 引入的新特性,用于处理集合数据。它提供了一种声明式的、函数式风格的数据处理方式,使得处理集合数据变得更加简洁和高效。
#### 用途
- **数据流处理**: Stream API 提供了一种声明式的处理集合数据的方式,使得开发者可以更专注于描述“做什么”而不是“怎么做”。
- **并行处理**: Stream API 支持并行流处理,可以自动利用多核处理器的优势,提高数据处理的性能。
#### 主要概念
- **源Source**: 数据的来源,通常是集合(如 `List`, `Set`)、数组或 `InputStream` 等。
- **中间操作Intermediate Operations**: 这些操作返回一个新的流,可以链接在一起形成流水线。常见的中间操作包括 `filter`, `map`, `flatMap`, `sorted`, `peek` 等。
- **终止操作Terminal Operations**: 这些操作产生结果或副作用,如计算总和、平均值、查找最大值或最小值等。常见的终止操作包括 `forEach`, `reduce`, `collect`, `count`, `max`, `min`, `anyMatch`, `allMatch`, `noneMatch`, `findFirst`, `findAny` 等。
#### 示例
以下是一个使用 Stream API 处理集合数据的示例:
```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
// 创建一个整数列表
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算偶数之和
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sum of even numbers: " + sum); // 输出 6
// 打印偶数
System.out.println("Even numbers:");
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println); // 输出 2 和 4
// 获取偶数的平方列表
List<Integer> squaresOfEvens = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares of even numbers: " + squaresOfEvens); // 输出 [4, 16]
// 计算偶数的最大值
Integer maxEven = numbers.stream()
.filter(n -> n % 2 == 0)
.max(Integer::compare)
.orElse(null);
System.out.println("Maximum even number: " + maxEven); // 输出 4
// 并行处理
long parallelSum = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
System.out.println("Parallel sum of even numbers: " + parallelSum); // 输出 6
}
}
```
#### 小结
- **声明式编程**: Stream API 采用声明式的编程风格,使得代码更加简洁易懂。
- **函数式风格**: Stream API 支持函数式编程特性,可以方便地组合多个操作。
- **并行处理**: Stream API 支持并行处理,可以充分利用多核处理器的优势来加速数据处理。
- **链式操作**: Stream API 支持链式调用,使得多个操作可以串联起来形成数据处理流水线。
通过使用 Stream API开发者可以写出更加高效、简洁且易于维护的代码。这对于处理大量数据集尤其有用同时也提高了代码的可读性和可维护性。
#### 5.1.6 泛型(掌握)
#### 什么是泛型?
Java 中的泛型是一种通用的编程技术,它允许我们在编写代码时更加灵活、安全和高效。泛型的主要目的是解决 Java 中类型安全的问题。在 Java 的早期版本中,所有的集合类都是基于 `Object` 类型实现的,这意味着可以将任意类型的对象添加到集合中。然而,在取出元素时需要进行显式类型转换,如果转换错误,就会抛出 `ClassCastException` 异常。通过使用泛型,可以在编译阶段检查类型,从而避免这类异常的发生。
#### 泛型的优点
- **类型安全性**: 泛型允许在编译时检查类型,避免了运行时出现类型转换异常等问题。
- **代码重用性**: 泛型可以让开发者编写通用的代码,提高代码的可重用性和可维护性。
- **性能优化**: 泛型可以避免不必要的类型转换,提高程序的运行效率。
#### 泛型的语法
泛型主要包括三个部分:泛型类、泛型接口和泛型方法。
##### 泛型类和泛型接口
泛型类和泛型接口是最常见的泛型形式。我们可以通过在类或接口名后面添加一对尖括号来声明一个或多个类型参数。
```java
public class MyClass<T> {
private T data;
public MyClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
public interface MyInterface<K, V> {
void add(K key, V value);
}
```
当我们使用泛型类或泛型接口时,可以使用具体的类型来替换类型参数。
```java
MyClass<String> myClass = new MyClass<>("Hello, World!");
MyInterface<Integer, String> myInterface = new MyInterface<Integer, String>() {
@Override
public void add(Integer key, String value) {
// 实现逻辑
}
};
```
##### 泛型方法
除了类和接口之外,我们还可以定义泛型方法,这些方法在方法签名前加上一对尖括号,表示该方法有一个或多个类型参数。
```java
public class MyUtils {
public static <T> T getLast(List<T> list) {
if (list.isEmpty()) {
return null;
}
return list.get(list.size() - 1);
}
}
```
当我们使用泛型方法时,可以显式或隐式地为类型参数传入具体的类型。
```java
List<String> names = List.of("Alice", "Bob", "Charlie");
String last = MyUtils.getLast(names); // 隐式类型推断
String lastExplicit = MyUtils.<String>getLast(names); // 显式指定类型
```
#### 泛型的应用
泛型是一种非常通用的编程技术,可以用它来编写各种各样的通用数据结构和工具类。
##### List 和 Map
Java 内置的 `List``Map` 类型都支持泛型,可以用来存储任意类型的对象。
```java
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("Alice", 90);
scoreMap.put("Bob", 80);
```
### 5.2 Spring框架简介(了解)
#### 5.2.1 Spring框架的历史和发展
- **起源**: Spring framework最初由Rod Johnson在2003年开始编写并于2004年发布了第一个版本1.0。
- **目标**: 创立Spring framework的主要目的是解决企业级Java应用中的复杂问题特别是解决依赖管理和面向切面编程AOP的问题。
- **版本演进**:
- **1.x**: 初期版本主要关注于依赖注入和面向切面编程。
- **2.x**: 添加了对Web应用程序的支持包括Spring MVC框架。
- **3.x**: 引入了对Java 5特性的支持如注解和泛型并且增强了对RESTful Web服务的支持。
- **4.x**: 增强了对Java 8的支持包括Lambda表达式和Stream API。
- **5.x**: 重点改进了对反应式编程模型的支持引入了Spring WebFlux框架。
- **最新版本**: 截至2024年Spring框架的最新版本为6.x系列持续增加了对现代Web开发趋势的支持如微服务架构、云原生应用等。
#### 5.2.2 Spring的家族框架
- **Spring Boot**: 提供了一个快速搭建Spring应用的方式通过自动化配置、依赖管理和启动器简化了开发过程。
- **Spring Cloud**: 用于构建分布式系统和服务网格,提供了一系列工具和服务,如服务注册与发现、配置中心、熔断器等。
- **Spring Data**: 提供了一套数据访问抽象层,简化了数据访问层的开发工作,支持各种数据库技术。
- **Spring Security**: 为Spring应用提供了全面的安全保护包括认证、授权等功能。
- **Spring Integration**: 提供了基于消息驱动的企业集成模式的支持,适用于构建复杂的集成系统。
- **Spring Batch**: 用于批量处理任务,支持大规模数据处理作业。
- **Spring WebFlux**: 用于构建非阻塞的、事件驱动的Web应用和服务器端应用支持反应式编程模型。
- **Spring Web**: 提供了构建Web应用程序所需的支持包括HTTP消息处理、多部分文件上传等功能以及与Spring MVC的紧密集成。
#### 5.2.3 Spring的应用场景和优势
- **应用场景**:
- **Web应用**: Spring MVC框架为构建高性能的Web应用提供了基础。
- **企业级应用**: Spring框架适合构建复杂的企业级应用包括业务流程管理、数据处理等。
- **微服务**: Spring Boot和Spring Cloud为构建微服务架构提供了便利。
- **移动应用后端**: Spring框架可以作为移动应用后端的服务端框架。
- **优势**:
- **松耦合**: 通过依赖注入实现了组件之间的松耦合。
- **易于测试**: Spring框架的设计使得单元测试变得简单。
- **广泛的社区支持**: 拥有庞大的开发者社区和丰富的文档资源。
- **丰富的生态**: Spring框架有一整套家族产品几乎覆盖了企业级应用的所有方面。
- **灵活性**: Spring框架非常灵活可以很容易地与其他技术栈集成。
- **性能**: Spring框架经过优化能够高效地运行大规模应用。
- **社区活跃**: Spring框架有一个活跃的社区不断更新以适应新的技术和标准。
#### 5.2.4 Spring Web
- **概述**: Spring Web 是 Spring Framework 的一部分它为构建Web应用程序提供了必要的支持。Spring Web 包含了多个子模块旨在提供一套完整的Web开发解决方案。
- **子模块**:
- **Spring Web**: 为Web开发提供了一些基本的支持比如HTTP消息转换、多部分文件上传、Web应用上下文等。
- **Spring Web MVC**: 一个基于Model-View-Controller (MVC) 模式的Web框架用于构建动态Web应用程序。
- **Spring Web Flows**: 一个用于构建复杂的Web应用程序的工作流框架。
- **Spring WebSocket**: 提供了WebSocket的支持用于实现全双工通信。
- **Spring Web Services**: 提供了构建和消费Web服务的支持。
- **关键特性**:
- **HTTP Message Converters**: 提供了HTTP消息转换器用于序列化和反序列化HTTP请求和响应。
- **MultiPart File Upload**: 支持多部分文件上传功能。
- **Web Application Context**: 提供了Web应用程序上下文的支持包括初始化监听器等。
- **MVC Framework**: Spring MVC 提供了一个基于MVC模式的Web框架用于构建动态Web应用程序。
- **File Upload Support**: 支持多部分文件上传,用于处理文件上传请求。
- **Web Services Support**: 支持构建和消费Web服务包括SOAP和RESTful服务。
- **WebSocket Support**: 提供了WebSocket的支持用于实现实时双向通信。
#### 5.2.5 Spring Boot
Spring Boot 是 Spring Framework 的一个子项目,旨在简化 Spring 应用的开发。一些关键特性:
- **自动配置Auto Configuration**
Spring Boot 的自动配置能够根据类路径中的依赖、定义的 Bean 以及各种配置属性,自动配置 Spring 应用的基础设施和功能。
- **内嵌服务器**
Spring Boot 提供了内嵌的 Tomcat、Jetty 和 Undertow 服务器,这样开发者无需外部配置服务器即可运行 Web 应用。
- **起步依赖Starters**
Spring Boot 提供了一系列 "起步依赖",这些依赖将常用的库和配置打包在一起,简化了项目的依赖管理。例如,`spring-boot-starter-web` 包含了开发 Web 应用所需的所有依赖,如 Spring MVC、内嵌的 Tomcat 等.
- **产品级特性**
强大的日志管理功能、健康检查和监控、安全管理、配置管理
- **集成第三方库和框架**
Spring Boot 提供了丰富的集成选项,使得开发者可以轻松地将第三方库和框架集成到应用程序中。
### 5.3 Spring IoC(掌握)
#### 5.3.1 IoC
**定义** IoC是一种设计模式它通过反转对象之间的依赖关系来降低代码的耦合度。在Spring框架中IoC通常被称为“依赖注入”Dependency Injection, DI
**目的** 通过将对象的创建和依赖关系管理交给外部容器在这里是Spring容器使得对象本身不再负责自身的依赖关系从而提高代码的可测试性、可维护性和灵活性。
#### 5.3.2 Spring IoC容器
Spring IoC容器负责管理对象的生命周期、依赖关系以及配置信息。Spring提供了多种容器实现包括BeanFactory和ApplicationContext。 被IoC容器管理的对象称为Bean。
#### 5.3.3 Spring Bean的定义和生命周期
通过使用框架提供的注解或者XML配置Spring可以轻松地定义和管理Bean。
在Spring框架中有多种注解可以用来定义一个类为Spring Bean。以下是一些常用的注解
**@Component** - 这是一个通用的注解用于标记任何轻量级的、可由Spring IoC容器管理的组件。
**@Service** - 通常用于业务层组件,表示服务逻辑的实现。
**@Repository** - 用于数据访问层组件比如DAOData Access Object用于处理数据存储相关的逻辑。
**@Controller** - 用于表现层组件特别是用在Web应用中作为控制器。
**@Configuration** - 标记配置类,这类类中的方法可以使用@Bean注解来定义和初始化bean。
**@RestController** - 组合了@Controller和@ResponseBody两个注解用于构建RESTful Web服务。
**@Bean** - 虽然不是直接用来标注类的,但是当与@Configuration一起使用时可以在配置类的方法上声明以创建和初始化bean。
通过使用上述注解之一Spring框架会在启动时自动扫描这些带有注解的类并将它们注册为Spring容器中的bean。这样就可以利用Spring的依赖注入Dependency Injection, DI和其他特性来管理和使用这些bean了。
- **注解方式定义Bean的示例**:
```java
@Component
public class MyBean {
}
```
```java
@Component
public class MyService {
}
```
```java
@RestController
public class MyController {
}
```
- **XML配置方式定义Bean**:
```xml
<bean id="myBean" class="com.example.MyBean"/>
```
- **生命周期**:
- **初始化**: 包括默认初始化、自定义初始化等。
- **销毁**: 包括默认销毁、自定义销毁等。
```java
@Component
public class MyBean {
public MyBean() {
System.out.println("MyBean created.");
}
@PostConstruct
public void init() {
System.out.println("MyBean initialized.");
}
@PreDestroy
public void destroy() {
System.out.println("MyBean destroyed.");
}
}
```
#### 5.3.4 Bean的作用域
在Spring框架中默认情况下如果没有显式指定作用域Bean的作用域是Singleton但是可以通过不同的方式来指定Bean的作用域
**作用域**:
- **Singleton**: 单例模式每个Spring容器中只有一个实例
- **Prototype**: 原型模式每次请求都会创建一个新的实例
- **Request**: 每次HTTP请求都会创建一个新的实例
- **Session**: 每个HTTP Session都会创建一个新的实例
##### 通过`@Scope`注解指定作用域
`@Scope`注解可以应用于类级别以指定Bean的作用域这是最直接的方法来改变Bean的默认作用域
**示例代码**
```java
package com.lk.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype") // 设置作用域为Prototype
public class PrototypeBean {
public PrototypeBean() {
System.out.println("Creating PrototypeBean");
}
}
@Component
@Scope("request") // 设置作用域为Request
public class RequestScopedBean {
public RequestScopedBean() {
System.out.println("Creating RequestScopedBean");
}
}
```
##### 通过`@Bean`方法指定作用域
如果你在`@Configuration`类中定义Bean可以通过`@Bean`方法的参数来指定作用域。
**示例代码**
```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean
@Scope("prototype") // 设置作用域为Prototype
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
@Bean
@Scope("request") // 设置作用域为Request
public RequestScopedBean requestScopedBean() {
return new RequestScopedBean();
}
}
```
#### 5.3.5 Bean的依赖注入
##### 5.3.5.1 依赖注入
依赖注入是一种设计模式它允许对象在其创建过程中或之后接受其依赖项。Spring框架通过依赖注入机制实现了IoCInversion of Control控制反转即让Spring容器负责管理Bean之间的依赖关系将依赖对象注入到目标对象中而不是在对象内部创建这些依赖对象。
##### 5.3.5.2 依赖注入的方式
依赖注入可以通过以下几种方式进行:
- **构造器注入Constructor Injection**
- **设值注入Setter Injection**
- **字段注入Field Injection**
##### 5.3.5.3 构造器注入
**定义**:构造器注入是指通过构造器参数来注入依赖项。
**优点**
- 保证了依赖项在对象创建时就已存在,有利于强制依赖项的存在。
- 更加适合于不可变对象或具有明确依赖关系的对象。
- 便于单元测试,因为可以在构造函数中传递所有必要的依赖项。
**示例**
```java
import org.springframework.stereotype.Service;
@Service
public class MyService {
private final MyRepository repository;
public MyService(MyRepository repository) {
this.repository = repository;
}
public void doSomething() {
// 使用repository
}
}
```
#### 5.3.5.4 设值注入
**定义**设值注入是指通过setter方法来注入依赖项。
**优点**
- 提供了更大的灵活性,可以在对象创建后动态地更改依赖项。
- 对于可选依赖项特别有用。
**缺点**
- 可能会导致对象的状态不完整因为在对象创建后可能没有调用setter方法来设置依赖项。
**示例**
```java
package com.lk.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyService {
private MyRepository repository;
@Autowired
public void setRepository(MyRepository repository) {
this.repository = repository;
}
public void doSomething() {
// 使用repository
}
}
```
#### 5.3.5.5 字段注入
**定义**:字段注入是指直接在字段上使用注解来注入依赖项。
**优点**
- 代码简洁,易于编写。
**缺点**
- 可能会导致对象的状态不完整,因为依赖项是在对象创建后才注入的。
- 不利于单元测试,因为依赖项不是通过构造函数传递的。
**示例**
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private MyRepository repository;
public void doSomething() {
// 使用repository
}
}
```
##### 5.3.5.6 依赖注入的注解
除了上述提到的注解之外,还有一些其他的注解可以帮助进行依赖注入:
- **@Autowired**:自动装配依赖项。如果一个类只有一个构造器,则可以省略此注解。
- **@Qualifier**用于消除模糊依赖项指定要注入的具体Bean。
- **@Primary**当存在多个候选Bean时优先考虑带有`@Primary`注解的Bean。
- **@Inject**JSR-330标准注解用于依赖注入可以与Spring集成使用。
- **@Resource**JSR-250标准注解通常用于按名称注入Bean。
### 5.4 Spring AOP(了解)
#### 5.4.1 AOP的基本概念
- **概念**:
面向切面编程Aspect-Oriented Programming, AOP是一种编程范式旨在通过将 **横切关注点(cross-cutting concerns)** 从核心业务逻辑中解耦出来实现对系统行为的统一、模块化的管理。横切关注点是指那些贯穿于整个系统、与业务逻辑紧密相关但又相对独立的功能需求如日志记录、事务管理、权限检查、性能监控等。在传统的面向对象编程OOP模型中这些需求往往以分散、重复的方式嵌入各个业务模块中导致代码复杂度上升、模块间耦合度增大影响系统的可读性、可维护性和可扩展性。
> 注:横切关注点指的是与多个模块相关的关注点. 横切关注点原则是软件设计中的一个重要原则,它指的是将那些与多个模块相关的关注点(如日志记录、错误处理、安全性等)从模块中分离出来,放到一个独立的模块中,以避免这些关注点影响到其他模块的核心功能。
- **用途**:
- **日志记录**: 在方法执行前后记录日志。
- **事务管理**: 管理方法的事务边界。
- **安全控制**: 控制方法的访问权限。
- **术语**
- **切点 (Pointcut)**: 切点是用来匹配连接点的谓语表达式AOP根据切点匹配到在哪些连接点上执行Advice。
- **连接点 (Joinpoint)**: 切面与逻辑代码的连接点在SpringBoot中的连接点是方法的执行。切点与连接点的关系就像正则表达式与符合正则表达式的值的关系二者是两个不同维度的东西。
- **通知 (Advice)**: 在特定连接点join point处由切面aspect执行的操作。不同的通知类型包括“环绕”around、“前置”before和“后置”after通知。许多AOP框架包括Spring都将通知建模为拦截器并在连接点周围维持一个拦截器链。
- **切面 (Aspect)**: 一个跨越多个类的关注点的模块化。事务管理是J2EE应用中一个很好的横切关注点的例子。在Spring AOP中切面是使用普通的类基于模式的方法或使用@Aspect注解的普通类@AspectJ风格来实现的。
#### 5.4.2 使用AspectJ进行切面编程
- **定义**: AspectJ是一种常用的AOP框架提供了强大的切面编程能力。
- **示例**:
```java
@Component // 将此切面注册为Spring容器中的组件
@Aspect // 标记此类为一个切面
public class LoggingAspect {
/**
* 前置通知Before Advice
* 在所有com.lk.demo包下的类的方法调用之前执行。
*
* @param joinPoint 连接点对象,提供了访问方法调用上下文的方法。
*/
@Before("execution(* com.lk.demo.*.*(..))") // 切点定义 execution : 表示方法执行时的连接点。
public void logBefore(JoinPoint joinPoint) {
// 连接点:在方法调用之前
System.out.println("Executing: " + joinPoint.getSignature());
}
/**
* 环绕通知Around Advice
* 在所有com.lk.demo.MyService类的方法调用前后执行。
*
* @param joinPoint 连接点对象,提供了访问方法调用上下文的方法。
* @return 方法的返回结果。
* @throws Throwable 如果方法抛出了异常。
*/
@Around("execution(* com.lk.demo.MyService.*(..))") // 切点定义
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 连接点:在方法调用之前
System.out.println("Before method call...");
try {
// Proceed to the actual method invocation
// 执行连接点上的方法
Object result = joinPoint.proceed();
// 连接点:在方法调用之后
System.out.println("After method call...");
return result;
} catch (Exception e) {
// 连接点:在方法抛出异常时
System.out.println("Exception thrown: " + e.getMessage());
throw e;
}
}
}
```
### 5.5 Spring Boot入门(掌握)
#### 5.5.1 快速创建Spring boot项目
- 使用官方在线工具[start.spring.io](https://start.spring.io)创建初始项目
- 将生成的zip文件在本机解压
- 在idea中打开项目
- 修改maven仓库地址,使用国内源加快下载速度在build.gralde文件中添加如下配置:
```groovy
repositories {
mavenLocal()
maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
```
- 刷新gradle,下载依赖包
- 运行项目
#### 5.5.2 创建控制器
**示例**:
```java
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String hello() {
return "Hello World";
}
@GetMapping("/json")
public Map<String,String> json() {
Map<String,String> data = new HashMap<>();
data.put("code","200");
data.put("message","Hello World");
return data;
}
}
```
#### 5.5.3 控制器中常用注解
- **@RestController注解**
> @RestController注用于标识一个类是一个RESTful风格的控制器Controller。它结合了@Controller和@ResponseBody注解的功能使得编写RESTful风格的控制器更加简单和方便。作用如下
> - 将类标记为RESTful控制器方便Spring Boot框架自动扫描和注册。
> - 将所有方法的返回值自动转换为JSON格式并通过HTTP响应返回给客户端。
> - 简化了编写RESTful控制器的过程不需要再使用@Controller和@ResponseBody注解。
- **@RequestMapping注解**
> @RequestMapping注解用于映射HTTP请求到控制器的处理方法上。它可以应用在类或方法上用于指定处理请求的路径和请求方法。
> 应用在类上,@RequestMapping注解表示这个类中的所有处理方法都共享相同的请求路径前缀
> 在方法级别上,@RequestMapping注解用于指定处理请求的具体路径和请求方法。
- **http method对应的注解应用在方法上**
> **@GetMapping**用于处理HTTP GET请求。它可以接受一个URI模式参数用于指定处理的请求路径。当客户端发送GET请求到对应的URI时Spring Boot将会通过该注解所标注的方法来处理请求并返回相应的数据。
> **@PostMapping**则是专门处理HTTP POST请求的注解它是@RequestMapping(method = RequestMethod.POST)的缩写形式。与@GetMapping类似它接受一个URI模式参数用于指定处理的请求路径。客户端向指定的URI发送POST请求时Spring Boot将会根据该注解所标注的方法来处理请求并返回相应结果。
> **@PutMapping**注解用于处理HTTP PUT请求将请求映射到具体的处理方法中。它是一个组合注解相当于@RequestMapping(method = RequestMethod.PUT)的快捷方式。通过该注解可以指定请求的URI、请求参数、响应类型等信息当客户端向指定URI发送PUT请求时Spring Boot会自动找到对应的处理方法进行处理并返回响应数据。
> @DeleteMapping则用于处理HTTP DELETE请求它也是一个组合注解相当于@RequestMapping(method = RequestMethod.DELETE)。它可以将HTTP DELETE请求映射到Spring Boot中的消费者方法实现删除特定资源的功能。类似于@PutMapping@DeleteMapping也支持设置请求的URI、请求参数、响应类型等信息
- **获得Request请求参数的注解**
@PathVariable、@RequestBody和@RequestParam是三个常用的注解它们分别用于获取并处理HTTP请求中的路径变量、请求体和请求参数。
> **@PathVariable**注解用于处理URI路径中的占位符参数即将URI中的某个部分作为参数传递给对应的控制器方法。例如/users/{id}表示URI路径中的id部分是一个路径变量我们可以在控制器方法中使用@PathVariable("id")注解来获取该值,并进行相应的业务操作。
> **@RequestBody**注解用于获取HTTP请求的请求体通常用于接收JSON格式的数据。当客户端向后端发送POST请求时使用该注解可以将请求体中的数据绑定到Java对象上从而方便地进行数据处理。
> **@RequestParam**注解用于获取HTTP请求中的查询参数例如?name=John&age=20。使用该注解可以将查询参数值绑定到对应的控制器方法的参数中并进行相应的业务操作。需要注意的是@RequestParam注解默认情况下是必需的即如果没有传递对应的查询参数则会抛出异常。如果想要将其设置为可选的可以在注解中添加required=false属性。
### 5.6 REST
RESTRepresentational State Transfer表述性状态转移是一种设计风格而不是一种具体的协议或标准。它主要用于设计网络应用程序特别是那些通过HTTP协议进行通信的应用程序。RESTful架构有以下几个核心特点
- **资源Resources**
- 资源是API的核心概念通常是名词表示应用程序中的实体。例如用户、订单、产品等。
- 每个资源都有一个唯一的URI。
- **HTTP 方法HTTP Methods**
- **GET**:获取资源。
- **POST**:创建新资源或触发某个操作。
- **PUT**:更新现有资源。
- **DELETE**:删除资源。
- **PATCH**:部分更新资源。
- **状态码Status Codes**
- HTTP状态码用于指示请求的结果。常见的状态码包括
- **200 OK**:请求成功。
- **201 Created**:资源已创建。
- **204 No Content**:请求成功,但没有返回内容。
- **400 Bad Request**:请求无效。
- **401 Unauthorized**:未授权。
- **403 Forbidden**:禁止访问。
- **404 Not Found**:资源未找到。
- **500 Internal Server Error**:服务器内部错误。
- **内容协商Content Negotiation**
- 客户端可以通过Accept头指定期望的响应格式如JSON、XML等。
- 服务器根据客户端的请求返回适当格式的数据。
### 5.7 构建工具
构建工具Build Tools是软件开发过程中用于自动化构建、测试和打包软件应用程序的工具。它们通常用于编译源代码、运行测试、生成文档、打包应用程序以及执行其他与构建过程相关的任务。
java项目常用的构建工具有
**Apache Ant**
- 是一个基于 Java 的构建工具,使用 XML 文件build.xml来描述构建过程。
- 支持跨平台构建,常用于 Java 项目。
**Maven**
- 是一个项目管理和构建自动化工具,主要针对 Java 项目。
- 使用 POMProject Object Model文件来描述项目结构和配置。
- 提供了依赖管理和项目生命周期管理。
**Gradle**
- 是一个现代的构建自动化系统,适用于 JVM 语言。
- 支持 Groovy 和 Kotlin 脚本,可以替代 Ant 和 Maven。
- 提供了丰富的插件生态系统。
#### 5.7.1 Maven
##### 5.7.1.1 独立使用Maven
##### 安装 Maven
1. **下载 Maven**: 访问 [Maven 官方网站](https://maven.apache.org/download.cgi),下载适合您操作系统的二进制包。
2. **解压缩**: 将下载的文件解压缩到一个合适的目录。
3. **设置环境变量**:
- Windows 用户需要设置 `MAVEN_HOME` 环境变量指向 Maven 的安装目录,并将 `%MAVEN_HOME%\\bin` 加入到 `PATH` 环境变量中。
- Linux/macOS 用户可以编辑 `.bashrc``.zshrc` 文件,添加 `export MAVEN_HOME=path/to/maven` 并将 `$MAVEN_HOME/bin` 添加到 `PATH` 中。
##### 创建 Maven 项目
1. **初始化项目**: 使用 Maven 命令行工具创建一个新的项目。例如,使用一个简单的 webapp 模板:
```shell
mvn archetype:generate -DgroupId=com.example -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
```
2. **编辑 pom.xml**: 修改 `pom.xml` 文件以包含项目的依赖和其他配置信息。
##### 常用 Maven 命令
- **编译项目**: `mvn compile`
- **运行测试**: `mvn test`
- **打包项目**: `mvn package`
- **安装到本地仓库**: `mvn install`
- **清理项目**: `mvn clean`
- **生成站点文档**: `mvn site`
##### 示例
假设你有一个 Maven 项目,并且希望编译和打包它:
```shell
cd path/to/your/project
mvn clean package
mvn package
```
##### 5.7.1.2 在IDEA使用Maven
**导入 Maven 项目**
* 打开 IntelliJ IDEA
* 新建或打开项目:选择 “Open” 或 “Import Project”浏览到包含 pom.xml 文件的目录,并选择它。
* 导入项目IDEA 会检测到这是一个 Maven 项目,并提示你导入该项目。
**创建新的 Maven 项目**
* 创建项目:选择 “File” > “New” > “Project”。
* 选择 Maven在新窗口中选择 “Maven” 作为项目类型。
* 配置项目:填写必要的信息,如 Group Id、Artifact Id 等,并选择一个模板或空项目。
**使用 Maven 插件**
IntelliJ IDEA 自带 Maven 插件,可以使用 Maven 插件来运行测试、打包项目、生成站点等。
> **访问 Maven 工具窗口:** 在 IntelliJ IDEA 的右侧栏中找到 “Maven” 工具窗口。 如果没有显示,可以通过 “View” > “Tool Windows” > “Maven” 打开。
#### 5.7.2 Gradle
##### 5.7.2.1 独立使用Gradle
##### 安装 Gradle
1. **下载 Gradle**: 访问 [Gradle 官方网站](https://gradle.org/releases/),下载适合您操作系统的二进制包。
2. **解压缩**: 将下载的文件解压缩到一个合适的目录。
3. **设置环境变量**:
- Windows 用户需要设置 `GRADLE_HOME` 环境变量指向 Gradle 的安装目录,并将 `%GRADLE_HOME%\\bin` 加入到 `PATH` 环境变量中。
- Linux/macOS 用户可以编辑 `.bashrc``.zshrc` 文件,添加 `export GRADLE_HOME=path/to/gradle` 并将 `$GRADLE_HOME/bin` 添加到 `PATH` 中。
##### 创建 Gradle 项目
1. **初始化项目**: 使用 Gradle 命令行工具创建一个新的项目。首先,创建一个项目目录,并在其中创建 `build.gradle` 文件。
2. **编辑 build.gradle**: 在 `build.gradle` 文件中定义项目的构建逻辑。例如,定义依赖关系和插件:
```groovy
plugins {
id 'java'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.jetbrains:annotations:23.0.1'
testImplementation 'junit:junit:4.12'
}
application {
mainClass = 'com.example.MainApp'
}
```
##### 常用 Gradle 命令
- **编译项目**: `./gradlew build` Linux/macOS
- `gradlew.bat build` Windows
- **运行测试**: `./gradlew test` Linux/macOS
- `gradlew.bat test` Windows
- **打包项目**: `./gradlew jar` Linux/macOS
- `gradlew.bat jar` Windows
- **清理项目**: `./gradlew clean` Linux/macOS
- `gradlew.bat clean` Windows
##### 5.7.2.2 IDEA中使用Gradle
在 IntelliJ IDEA 中使用 Gradle 同样可以提高开发效率,因为 IDEA 提供了对 Gradle 的深度集成支持。以下是详细的步骤说明如何在 IntelliJ IDEA 中使用 Gradle
##### 5.7.2.3 在 IntelliJ IDEA 中使用 Gradle
#### 创建新的 Gradle 项目
- **启动 IntelliJ IDEA**:打开 IntelliJ IDEA。
- **创建新项目**:选择 “File” > “New” > “Project”。
- **选择 Gradle**:在新窗口中选择 “Gradle” 作为项目类型。
- **配置项目**填写必要的信息如项目名称、位置、Gradle 版本等。如果需要,可以指定一个模板或空项目。
- **完成创建**:点击 “Finish” 来创建 Gradle 项目。
#### 导入现有的 Gradle 项目
- **打开 IntelliJ IDEA**:启动 IntelliJ IDEA 应用程序。
- **打开项目**:选择 “Open” 或 “Import Project”浏览到包含 `build.gradle` 文件的目录,并选择它。
- **导入项目**IDEA 会检测到这是一个 Gradle 项目,并提示你导入该项目。选择 “Import project from external model” 并点击 “OK”。
#### 使用 IDEA的gradle插件
IDEA 内置了对 Gradle 的支持,使得开发者可以直接从 IDE 内部执行 Gradle 任务,监控构建过程,并管理项目的构建配置。
> **访问 Gradle 工具窗口:** 在 IntelliJ IDEA 的右侧栏中找到 “Gradle” 工具窗口。 如果没有显示,可以通过 “View” > “Tool Windows” > “Gradle” 打开。