开发基础知识 - Spring基本介绍,特性与原理
Background
单例模式
单例模式是一种创建对象的设计模式,它确保一个类在整个程序运行过程中,只会被创建一个实例,并提供一个全局访问点。比如一个Printer类,这个类的功能可以被多个服务使用,因为printer实例不保存状态,只提供打印服务,我们不想每次使用时就创建一个新的printer实例,因为这样浪费内存,也容易管理混乱。
示例单例模式的代码:
1 |
|
但是以上代码不符合单例模式的需求:
- 多线程环境下可能会出现两个线程同时判断
instance == null
为 true, 创建两个实例,违反单例模式的设定。 instance = new Printer()
可能会被编译器重排成如下顺序:- 分配内存空间
- 把内存地址赋给 instance(instance 不再为 null)
- 调用构造函数初始化对象
如果第二个线程看到instance != null
,但其实这个实例还没有被初始化,那么第二个线程会直接使用没有初始化的对象,导致空指针等问题。因此要使用volatile
防止指令重排。
正确的单例模式代码:
1 |
|
Dependency Injection
依赖注入是由外部系统将对象所依赖的其他对象传入(注入)它的过程。
如果不使用依赖注入,比如我的代码中,Car 依赖于具体的 Engine 实现,两者耦合度高:
1 |
|
在这个代码中,Car 类自己创建了 Engine 对象 new Engine()
。
- 如果要换成 TurboEngine,需要修改 Car 的源码。
- 不方便测试(比如不能用一个假的 Engine)
使用依赖注入:
1 |
|
Spring Framework
Spring 框架的核心目标就是解耦和自动化。Spring负责类的创建、注入、销毁等。
Spring代码示例
代码结构
1 |
|
- DemoApplication.java:应用入口
1 |
|
- User.java:实体类
1 |
|
UserRepository.java:仓库接口
1
2
3
4
5
6
7package com.example.demo.repository;
import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}UserService.java:服务类(业务逻辑)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.example.demo.service;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserRepository repo;
@Autowired
public UserService(UserRepository repo) {
this.repo = repo;
}
public List<User> getAllUsers() {
return repo.findAll();
}
public User createUser(User user) {
return repo.save(user);
}
}UserController.java:控制器(REST 接口)
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
28package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService service;
@Autowired
public UserController(UserService service) {
this.service = service;
}
@GetMapping
public List<User> getUsers() {
return service.getAllUsers();
}
@PostMapping
public User addUser(@RequestBody User user) {
return service.createUser(user);
}
}
如果没有Spring
1 |
|
- 手动创建对象,项目初始化的时候很复杂
- 手动传依赖(UserService依赖UserRepository)
- 管理对象的销毁等
- 如果多个类依赖UserRepository,依赖关系复杂难以管理
会导致代码耦合度高,测试困难等。Spring可以帮你:
- 自动创建对象,你不用写 new。
- 自动注入依赖
- 控制对象的生命周期(什么时候创建、什么时候销毁)
- 帮你管理作用域:是单例?还是每次请求一个新的?都可以
IoC
控制反转(Inversion of Control),将对象的创建和依赖关系的维护从代码中移除,交给一个专门的容器(Spring 框架中的IoC 容器) 来管理。
在 Spring 应用 中,通常 Controller、Service 和 DAO(Repository) 都是由 Spring 容器管理的 Bean,这样可以充分利用 Spring 的 IoC(控制反转)和依赖注入(DI)机制,使得各层之间的依赖关系由 Spring 自动管理,减少手动创建对象的代码,提高可维护性和解耦性。
Bean
Bean是一个由Spring IoC容器管理的对象。这个对象可以是任何Java类的实例。Spring通过“控制反转(IoC)”的机制,把对象的创建权和管理权交给了容器。使用者不再手动 new 对象,而是让Spring把准备好的对象(Bean)交给使用者使用。这样可以使代码更清晰,专注于业务逻辑;更容易替换实现、做单元测试。
声明类为Beans的方法
- 使用注解 @Component
1
2
3
4
5
6
7
8import org.springframework.stereotype.Component;
@Component
public class MyService {
public void doSomething() {
System.out.println("Doing something...");
}
}
- @Component:通用组件
- @Service:表示业务逻辑层
- @Repository:表示数据访问层
- @Controller:表示Web控制器
- 使用 @Bean 注解
当你希望手动控制某个 Bean 的创建过程时,用这个方式。
1 |
|
Spring 会执行这个方法,获取返回的对象,把它注册成一个 Bean。
自动依赖注入
Spring 会自动找到类型是 Engine 的 Bean,注入给 Car。
构造器注入
1
2
3
4
5
6
7
8
9@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}Setter 注入
1 |
|
- 字段注入
1 |
|
AOP
AOP(Aspect-Oriented Programming)面向切面编程。在传统的面向对象编程中,比如我们在写一个网站的业务逻辑时,比如处理订单、用户登录等,我们经常还需要加上日志记录、权限校验、事务管理等功能。这些功能虽然必须有,但它们跟业务逻辑(比如下订单)并没有直接关系。
比如我有一个订单服务:
1 |
|
Spring AOP 是 Spring 框架提供的一种实现 AOP 编程的方法,它通过代理对象把横切逻辑“织入”到业务逻辑中,不需要你改业务代码。Spring 根据配置创建一个代理对象,每当你调用业务方法(如 orderService.createOrder())时,代理对象会先执行切面逻辑(如日志),再执行真正的业务逻辑。
1 |
|
这样,当我调用 orderService 的代码时,比如 orderService.createOrder()
,控制台会打印:
1 |
|
Spring的原理
反射
反射是 Java 提供的一种机制,可以在程序运行时(不是写代码时),动态地获取类的信息(比如类的名字、方法、变量、构造函数等),甚至动态地调用对象的方法或访问它的属性。详细的反射介绍:反射。
IoC原理
回顾IoC主要是将对象的创建和依赖管理的责任从程序员手中“反转”给一个容器来管理,从而降低耦合度。在Spring启动时,会进行以下处理:
- 当 Spring 启动时,会扫描指定的包或目录(通常通过 @ComponentScan 注解指定),查找类上标注的注解(如 @Component、@Service、@Repository、@Controller 等)
- 所有标注了这些注解的类会被注册为 Spring 容器中的 Bean。
- 在创建 Bean 时,如果该 Bean 有依赖的其他 Bean(如通过 @Autowired 注解标注),Spring 会自动注入这些依赖对象。
AOP原理
动态代理
实例化一个接口时:
静态代理:你写了一个类去实现接口,写死了逻辑,编译器在编译期生成字节码。
动态代理:你在运行时才“生成一个类”(不是写出来的源码,而是直接生成字节码并加载),这个类会去实现接口,并在方法调用时,把实际处理交给 InvocationHandler。
如何让Java自动生成一个类来实现一个Interface?
1 |
|
以上例子,使用了动态代理的方法,创建了一个对象,实现了 UserService 接口。
实际输出:
1 |
|
AOP的实现使用了动态代理,其中代理对象在运行时动态地拦截方法调用并进行增强操作。
大体流程:
Spring启动时,会扫描标注了 @Aspect 的类,这些 @Aspect 类内部会定义切入点(@Pointcut)和通知(@Before、@After、@Around 等)。一旦 Spring 扫描到 @Aspect 注解的类,它就会在目标类上创建一个代理对象(根据注解动态生成代理对象)。Spring 会用代理对象替换原始目标对象,放入 Beans 来管理。代理对象的 invoke 方法会被 Spring 重写。在该方法中,Spring 会根据注解里的定义执行哪些横切逻辑。此后所有的方法调用都走代理逻辑,从而实现 AOP。
解决循环依赖
循环依赖是指 多个 Bean 之间的依赖形成了一个闭环,例如:A 依赖 B,B 依赖 A。如果 Spring 直接实例化 A,它发现 A 需要 B,于是去创建 B,但 B 又需要 A,形成了死锁,导致程序无法继续。为了解决循环依赖,Spring 在创建 Bean 时,使用了三级缓存机制:
- 一级缓存:用来存放已经创建的对象,通常是完全初始化的 Bean 实例。
- 二级缓存:用来存放已经实例化但是还没完全初始化的Bean(还未进行注入操作)。
- 三级缓存:这个缓存用来存放 Bean 工厂,它是用来获取 Bean 的实例。