java-web/docs/chapter05.md
2024-10-27 12:07:34 +08:00

1201 lines
47 KiB
Markdown
Raw 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框架的最新版本为5.x系列持续增加了对现代Web开发趋势的支持如微服务架构、云原生应用等。
#### 5.2.2 Spring框架的核心特性
- **依赖注入 (DI)**:
- **定义**: 是一种设计模式,用于实现对象之间的解耦,让对象不需要直接创建依赖对象,而是由容器负责创建并注入。
- **好处**: 降低了组件之间的耦合度,提高了代码的可测试性和可维护性。
- **面向切面编程 (AOP)**:
- **定义**: 一种编程范式,用于将横切关注点(如日志记录、事务管理)从业务逻辑中分离出来。
- **好处**: 通过切面编程,可以在不修改业务逻辑代码的情况下添加或修改功能。
- **模块化**: Spring框架被设计成高度模块化的用户可以根据需要选择使用不同的模块。
- **可扩展性**: Spring框架提供了许多扩展点允许开发者根据需要自定义行为。
- **广泛的应用场景**: 从简单的Web应用到复杂的企业级应用Spring都能提供解决方案。
#### 5.2.3 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.4 Spring的应用场景和优势
- **应用场景**:
- **Web应用**: Spring MVC框架为构建高性能的Web应用提供了基础。
- **企业级应用**: Spring框架适合构建复杂的企业级应用包括业务流程管理、数据处理等。
- **微服务**: Spring Boot和Spring Cloud为构建微服务架构提供了便利。
- **移动应用后端**: Spring框架可以作为移动应用后端的服务端框架。
- **优势**:
- **松耦合**: 通过依赖注入实现了组件之间的松耦合。
- **易于测试**: Spring框架的设计使得单元测试变得简单。
- **广泛的社区支持**: 拥有庞大的开发者社区和丰富的文档资源。
- **丰富的生态**: Spring框架有一整套家族产品几乎覆盖了企业级应用的所有方面。
- **灵活性**: Spring框架非常灵活可以很容易地与其他技术栈集成。
- **性能**: Spring框架经过优化能够高效地运行大规模应用。
- **社区活跃**: Spring框架有一个活跃的社区不断更新以适应新的技术和标准。
#### 5.2.5 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.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。
- **生命周期**:
- **初始化**: 包括默认初始化、自定义初始化等。
- **销毁**: 包括默认销毁、自定义销毁等。
- **注解方式定义Bean的示例**:
```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.");
}
}
```
- **XML配置方式定义Bean**:
```xml
<bean id="myBean" class="com.example.MyBean">
- ```
#### 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 MVC(掌握)
#### 5.4.1 Spring MVC概述
- **定义**: Spring MVC是Spring web框架的一部分基于Model-View-Controller (MVC) 模式,用于构建Web应用。
- **特点**:
- **请求处理**: 通过@Controller注解来定义控制器。
- **视图解析**: 通过ViewResolver来解析视图。
#### 5.4.2 使用@Controller定义控制器
- **定义**: 使用`@Controller`注解定义控制器类。
- **示例**:
```java
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello, Spring MVC!");
return "hello";
}
}
```
#### 5.4.3 使用@RequestMapping处理请求
- **定义**: 使用`@RequestMapping`注解来映射请求路径。
- **示例**:
```java
@Controller
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(@RequestParam String name, Model model) {
model.addAttribute("message", "Hello, " + name + "!");
return "hello";
}
}
```
#### 5.4.4 使用@ResponseBody返回JSON响应
- **定义**: 使用`@ResponseBody`注解来直接返回JSON响应。
- **示例**:
```java
@Controller
public class HelloController {
@RequestMapping(value = "/hello-json", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> helloJson(@RequestParam String name) {
Map<String, String> response = new HashMap<>();
response.put("message", "Hello, " + name + "!");
return response;
}
}
```
#### 5.4.5 使用Spring Data JPA进行数据访问
- **定义**: Spring Data JPA是Spring Data项目的一部分用于简化JPA的数据访问。
- **示例**:
```java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
```
### 5.5 Spring AOP(了解)
#### 5.5.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.5.2 使用AspectJ进行切面编程
- **定义**: AspectJ是一种常用的AOP框架提供了强大的切面编程能力。
- **示例**:
```java
@Component // 将此切面注册为Spring容器中的组件
@Aspect // 标记此类为一个切面
public class LoggingAspect {
/**
* 前置通知Before Advice
* 在所有com.lk.demo包下的类的方法调用之前执行。
*
* @param joinPoint 连接点对象,提供了访问方法调用上下文的方法。
*/
@Before("execution(* com.lk.demo.*.*(..))") // 切点定义
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.6 Spring事务管理(了解)
#### 5.6.1 事务管理的基本概念
在计算机科学中特别是在数据库管理和分布式计算领域事务Transaction是一种基本的操作单位它确保了一系列操作要么全部成功要么全部失败。事务的概念是数据库管理系统DBMS中用来保证数据完整性和一致性的重要机制之一。
事务具有以下几个核心特点通常称为ACID属性
- **原子性 (Atomicity)**: 事务中的所有操作要么全部完成,要么一个也不完成。
- **一致性 (Consistency)**: 事务的执行不会破坏数据的一致性。
- **隔离性 (Isolation)**: 事务之间是隔离的,不会相互影响。
- **持久性 (Durability)**: 一旦事务提交,它的效果是持久的。
#### 5.6.2 Spring事务管理器
- **定义**: Spring提供了多种事务管理器如`DataSourceTransactionManager`。
- **示例**:
```java
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
```
#### 5.6.3 使用@Transactional进行声明式事务管理
- **定义**: 使用@Transactional注解来声明式地管理事务简化了事务控制代码。
- **示例**:
```java
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(User user) {
userRepository.save(user);
}
}
```
#### 5.6.4 使用编程式事务管理
- **定义**: 通过编程的方式显式地管理事务,适用于需要更细粒度控制事务的情况。
- **示例**:
```java
@Service
public class UserService {
private final UserRepository userRepository;
private final PlatformTransactionManager transactionManager;
@Autowired
public UserService(UserRepository userRepository, PlatformTransactionManager transactionManager) {
this.userRepository = userRepository;
this.transactionManager = transactionManager;
}
public void createUser(User user) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
userRepository.save(user);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
```
### 5.7 构建工具
构建工具Build Tools是软件开发过程中用于自动化构建、测试和打包软件应用程序的工具。它们通常用于编译源代码、运行测试、生成文档、打包应用程序以及执行其他与构建过程相关的任务。
java项目常用的构建工具有
**Apache Ant**
- 是一个基于 Java 的构建工具,使用 XML 文件build.xml来描述构建过程。
- 支持跨平台构建,常用于 Java 项目。
**Maven**
- 是一个项目管理和构建自动化工具,主要针对 Java 项目。
- 使用 POMProject Object Model文件来描述项目结构和配置。
- 提供了依赖管理和项目生命周期管理。
**Gradle**
- 是一个现代的构建自动化系统,适用于 JVM 语言。
- 支持 Groovy 和 Kotlin 脚本,可以替代 Ant 和 Maven。
- 提供了丰富的插件生态系统。
好的,以下是针对 Maven 和 Gradle 的具体使用方法的介绍,以完成你的文档的 5.7.1 和 5.7.2 部分。
#### 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” 打开。