44 KiB
5. 基于Spring框架的后端应用开发
5.1 Java基础增强(掌握)
5.1.1 Java异常处理
异常
是在程序执行过程中发生的任何非正常情况或问题。它们打断了正常的程序流程,需要特别处理来确保程序的健壮性和可靠性。
java异常类的继承关系:
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 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
——摘自百度百科
元数据: 说明数据的数据
注解定义的语法
public @interface 注解名称 {
// 属性列表;
}
类型
- 元注解:用于注解其他注解的注解,如
@Retention
和@Target
。 - 内置注解:Java 平台提供的注解,如
@Override
、@Deprecated
、@SuppressWarnings
等。 - 自定义注解:我们自己编写的注解。很多第三方框架通过提供自定义注解来简化实现,例如 Spring 框架提供了
@Autowired
、@RequestMapping
等。
使用场景
- 为框架提供元数据:例如 Spring 框架使用注解来标记组件。
- 用于代码生成:例如 Lombok 库使用注解来自动生成模板化的代码。
- 用于编译时处理:例如通过注解来检查代码中的潜在错误。
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。
使用位置
在实际开发中,注解可以出现在类、方法、成员变量、方法参数等多个位置。
级别
注解与类、接口、枚举处于同一级别。注解的本质类似于 @interface
,与 interface
相似,但用于提供元数据。
注解三角
- 定义注解:创建注解。
- 使用注解:在代码中应用注解。
- 读取注解:通过反射或其他机制读取并处理注解。
示例
以下是一些具体的示例来展示如何定义和使用注解:
定义注解
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 ""; // 默认属性值
}
使用注解
public class MyClass {
@MyCustomAnnotation(value = "Hello, World!")
public void myMethod() {
System.out.println("Executing myMethod...");
}
}
读取注解
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
注解,这样可以确保接口只有一个抽象方法。
@FunctionalInterface
public interface SimpleFunctionalInterface {
void doSomething();
}
Lambda 表达式的基本语法
Lambda 表达式的一般语法是:
(parameters) -> expression
或者
(parameters) -> { statements; }
parameters
:这是参数列表,可以是一个或多个参数,参数类型可以省略。->
:这是 Lambda 操作符,将参数部分与 Lambda 体分开。expression
或statements
:这是 Lambda 体,可以是一个表达式或一个代码块。
使用 Lambda 表达式
无参数和无返回值
SimpleFunctionalInterface sfi = () -> System.out.println("Hello Lambda!");
sfi.doSomething(); // 输出 "Hello Lambda!"
一个参数和无返回值
@FunctionalInterface
interface Greeting {
void greet(String name);
}
Greeting greet = (String name) -> System.out.println("Hello, " + name);
greet.greet("Java"); // 输出 "Hello, java"
一个参数和有返回值
@FunctionalInterface
interface Converter {
String convert(String from);
}
Converter converter = (from, to) -> from.toUpperCase();
System.out.println(converter.convert("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修饰的方法)引用
语法: 类名::方法名
注意事项:
- 被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
- 接口的抽象方法没有返回值,引用的方法可以有返回值也可以
- 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!!
- 由于满足抽象参数列表与引用参数列表相同,所以可以写成静态方法引用的格式
public class StringUtils {
public static String concatenate(String from, String to) {
return from + ", " + to;
}
}
Converter concat = StringUtils::concatenate;
System.out.println(concat.convert("Hello", "Lambda")); // 输出 "Hello, Lambda"
实例方法引用
实例方法(非static修饰的方法)引用
语法: 对象名:∶非静态方法名
注意事项:
- 被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
- 接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
- 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!
public class Combiner {
public String combine(String part1, String part2) {
return part1.toUpperCase() + ", " + part2.toUpperCase();
}
}
Combiner combiner = new Combiner();
Converter append = combiner::combie;
System.out.println(append.convert("Hello", "Lambda")); // 输出 "Hello, Lambda"
构造函数引用
构造方法引用
语法: 类名::new
注意事项:
- 被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致
// 假设我们有一个简单的类
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 结合使用。
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
生成的异常。
示例
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 处理集合数据的示例:
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
异常。通过使用泛型,可以在编译阶段检查类型,从而避免这类异常的发生。
泛型的优点
- 类型安全性: 泛型允许在编译时检查类型,避免了运行时出现类型转换异常等问题。
- 代码重用性: 泛型可以让开发者编写通用的代码,提高代码的可重用性和可维护性。
- 性能优化: 泛型可以避免不必要的类型转换,提高程序的运行效率。
泛型的语法
泛型主要包括三个部分:泛型类、泛型接口和泛型方法。
泛型类和泛型接口
泛型类和泛型接口是最常见的泛型形式。我们可以通过在类或接口名后面添加一对尖括号来声明一个或多个类型参数。
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);
}
当我们使用泛型类或泛型接口时,可以使用具体的类型来替换类型参数。
MyClass<String> myClass = new MyClass<>("Hello, World!");
MyInterface<Integer, String> myInterface = new MyInterface<Integer, String>() {
@Override
public void add(Integer key, String value) {
// 实现逻辑
}
};
泛型方法
除了类和接口之外,我们还可以定义泛型方法,这些方法在方法签名前加上一对尖括号,表示该方法有一个或多个类型参数。
public class MyUtils {
public static <T> T getLast(List<T> list) {
if (list.isEmpty()) {
return null;
}
return list.get(list.size() - 1);
}
}
当我们使用泛型方法时,可以显式或隐式地为类型参数传入具体的类型。
List<String> names = List.of("Alice", "Bob", "Charlie");
String last = MyUtils.getLast(names); // 隐式类型推断
String lastExplicit = MyUtils.<String>getLast(names); // 显式指定类型
泛型的应用
泛型是一种非常通用的编程技术,可以用它来编写各种各样的通用数据结构和工具类。
List 和 Map
Java 内置的 List
和 Map
类型都支持泛型,可以用来存储任意类型的对象。
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框架的核心特性
- 依赖注入 (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的示例:
@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:
<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的默认作用域。
示例代码
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
方法的参数来指定作用域。
示例代码
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框架通过依赖注入机制实现了IoC(Inversion of Control,控制反转),即让Spring容器负责管理Bean之间的依赖关系,将依赖对象注入到目标对象中,而不是在对象内部创建这些依赖对象。
5.3.5.2 依赖注入的方式
依赖注入可以通过以下几种方式进行:
- 构造器注入(Constructor Injection)
- 设值注入(Setter Injection)
- 字段注入(Field Injection)
5.3.5.3 构造器注入
定义:构造器注入是指通过构造器参数来注入依赖项。
优点:
- 保证了依赖项在对象创建时就已存在,有利于强制依赖项的存在。
- 更加适合于不可变对象或具有明确依赖关系的对象。
- 便于单元测试,因为可以在构造函数中传递所有必要的依赖项。
示例:
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方法来设置依赖项。
示例:
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 字段注入
定义:字段注入是指直接在字段上使用注解来注入依赖项。
优点:
- 代码简洁,易于编写。
缺点:
- 可能会导致对象的状态不完整,因为依赖项是在对象创建后才注入的。
- 不利于单元测试,因为依赖项不是通过构造函数传递的。
示例:
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
注解定义控制器类。 - 示例:
@Controller public class HelloController { @RequestMapping("/hello") public String hello(Model model) { model.addAttribute("message", "Hello, Spring MVC!"); return "hello"; } }
5.4.3 使用@RequestMapping处理请求
- 定义: 使用
@RequestMapping
注解来映射请求路径。 - 示例:
@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响应。 - 示例:
@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的数据访问。
- 示例:
@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框架,提供了强大的切面编程能力。
- 示例:
@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.6 构建工具
构建工具(Build Tools)是软件开发过程中用于自动化构建、测试和打包软件应用程序的工具。它们通常用于编译源代码、运行测试、生成文档、打包应用程序以及执行其他与构建过程相关的任务。
java项目常用的构建工具有:
Apache Ant
- 是一个基于 Java 的构建工具,使用 XML 文件(build.xml)来描述构建过程。
- 支持跨平台构建,常用于 Java 项目。
Maven
- 是一个项目管理和构建自动化工具,主要针对 Java 项目。
- 使用 POM(Project Object Model)文件来描述项目结构和配置。
- 提供了依赖管理和项目生命周期管理。
Gradle
- 是一个现代的构建自动化系统,适用于 JVM 语言。
- 支持 Groovy 和 Kotlin 脚本,可以替代 Ant 和 Maven。
- 提供了丰富的插件生态系统。
5.6.1 Maven
5.6.1.1 独立使用Maven
安装 Maven
- 下载 Maven: 访问 Maven 官方网站,下载适合您操作系统的二进制包。
- 解压缩: 将下载的文件解压缩到一个合适的目录。
- 设置环境变量:
- Windows 用户需要设置
MAVEN_HOME
环境变量指向 Maven 的安装目录,并将%MAVEN_HOME%\\bin
加入到PATH
环境变量中。 - Linux/macOS 用户可以编辑
.bashrc
或.zshrc
文件,添加export MAVEN_HOME=path/to/maven
并将$MAVEN_HOME/bin
添加到PATH
中。
创建 Maven 项目
- 初始化项目: 使用 Maven 命令行工具创建一个新的项目。例如,使用一个简单的 webapp 模板:
mvn archetype:generate -DgroupId=com.example -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
- 编辑 pom.xml: 修改
pom.xml
文件以包含项目的依赖和其他配置信息。
常用 Maven 命令
- 编译项目:
mvn compile
- 运行测试:
mvn test
- 打包项目:
mvn package
- 安装到本地仓库:
mvn install
- 清理项目:
mvn clean
- 生成站点文档:
mvn site
示例
假设你有一个 Maven 项目,并且希望编译和打包它:
cd path/to/your/project
mvn clean package
mvn package
5.6.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.6.2 Gradle
5.6.2.1 独立使用Gradle
安装 Gradle
- 下载 Gradle: 访问 Gradle 官方网站,下载适合您操作系统的二进制包。
- 解压缩: 将下载的文件解压缩到一个合适的目录。
- 设置环境变量:
- Windows 用户需要设置
GRADLE_HOME
环境变量指向 Gradle 的安装目录,并将%GRADLE_HOME%\\bin
加入到PATH
环境变量中。 - Linux/macOS 用户可以编辑
.bashrc
或.zshrc
文件,添加export GRADLE_HOME=path/to/gradle
并将$GRADLE_HOME/bin
添加到PATH
中。
创建 Gradle 项目
- 初始化项目: 使用 Gradle 命令行工具创建一个新的项目。首先,创建一个项目目录,并在其中创建
build.gradle
文件。 - 编辑 build.gradle: 在
build.gradle
文件中定义项目的构建逻辑。例如,定义依赖关系和插件: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.6.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” 打开。