开发基础知识 - Spring基本介绍,特性与原理

Background

单例模式

单例模式是一种创建对象的设计模式,它确保一个类在整个程序运行过程中,只会被创建一个实例,并提供一个全局访问点。比如一个Printer类,这个类的功能可以被多个服务使用,因为printer实例不保存状态,只提供打印服务,我们不想每次使用时就创建一个新的printer实例,因为这样浪费内存,也容易管理混乱。

示例单例模式的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Printer {

private static Printer instance; // 1. 类的静态变量保存实例

private Printer() {
// 2. 私有构造函数,别人不能 new
}

public static Printer getInstance() {
if (instance == null) { // 3. 第一次调用时才创建
instance = new Printer();
}
return instance;
}
}

Printer p = Printer.getInstance(); // 得到单例

但是以上代码不符合单例模式的需求:

  • 多线程环境下可能会出现两个线程同时判断 instance == null 为 true, 创建两个实例,违反单例模式的设定。
  • instance = new Printer()可能会被编译器重排成如下顺序:
    • 分配内存空间
    • 把内存地址赋给 instance(instance 不再为 null)
    • 调用构造函数初始化对象
      如果第二个线程看到instance != null,但其实这个实例还没有被初始化,那么第二个线程会直接使用没有初始化的对象,导致空指针等问题。因此要使用 volatile 防止指令重排。

正确的单例模式代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Printer {
// 确保线程可见性以及禁止指令重排
private static volatile Printer instance;
// private构造函数,防止外部实例化(防止外部使用new Printer())
private Printer() {}

public static Printer getInstance() {
if (instance == null) { // 第一次检查,如果 instance 已经有了,就直接返回,省去加锁的成本
synchronized (Printer.class) { // 确保同一时间只有一个线程能进入
if (instance == null) { // 第二次检查(可能两个线程同时通过了第一层的 if (instance == null))
instance = new Printer();
}
}
}
return instance;
}
}

Dependency Injection

依赖注入是由外部系统将对象所依赖的其他对象传入(注入)它的过程。

如果不使用依赖注入,比如我的代码中,Car 依赖于具体的 Engine 实现,两者耦合度高:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Engine.java
public class Engine {
public void start() {
System.out.println("Engine started.");
}
}

// Car.java
public class Car {
private Engine engine = new Engine(); // 手动创建依赖

public void drive() {
engine.start();
System.out.println("Car is driving...");
}
}

// Main.java
public class Main {
public static void main(String[] args) {
Car car = new Car(); // 手动创建 Car 实例
car.drive();
}
}

在这个代码中,Car 类自己创建了 Engine 对象 new Engine()

  • 如果要换成 TurboEngine,需要修改 Car 的源码。
  • 不方便测试(比如不能用一个假的 Engine)

使用依赖注入:

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
public interface Engine {
void start();
}

// 实现不同类型的Engine
public class GasEngine implements Engine {
public void start() {
System.out.println("Gas engine started.");
}
}

public class ElectricEngine implements Engine {
public void start() {
System.out.println("Electric engine started.");
}
}

// Car 接受一个 Engine 参数
public class Car {
private Engine engine;

// 构造函数注入(最常见方式)
public Car(Engine engine) {
this.engine = engine;
}

public void start() {
engine.start();
System.out.println("Car is driving...");
}
}

// 使用
public class Main {
public static void main(String[] args) {
// 注入不同的引擎类型
Engine gasEngine = new GasEngine();
Car car1 = new Car(gasEngine);
car1.start(); // 输出:Gas engine started.

Engine electricEngine = new ElectricEngine();
Car car2 = new Car(electricEngine);
car2.start(); // 输出:Electric engine started.
}
}

Spring Framework

Spring 框架的核心目标就是解耦和自动化。Spring负责类的创建、注入、销毁等。

Spring代码示例

代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
spring-demo/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/demo/
│ │ ├── DemoApplication.java // 主程序入口
│ │ ├── controller/UserController.java// 控制器
│ │ ├── service/UserService.java // 业务逻辑
│ │ ├── model/User.java // 数据模型
│ │ └── repository/UserRepository.java// 数据仓库
│ └── resources/
│ └── application.properties // 配置文件
├── pom.xml // Maven 构建文件
  • DemoApplication.java:应用入口
1
2
3
4
5
6
7
8
9
10
11
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
  • User.java:实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo.model;

import jakarta.persistence.*;

@Entity
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;

// Getter 和 Setter
}
  • UserRepository.java:仓库接口

    1
    2
    3
    4
    5
    6
    7
    package 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
    25
    package 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
    28
    package 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 service = new UserService(new UserRepository());
  • 手动创建对象,项目初始化的时候很复杂
  • 手动传依赖(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的方法

  1. 使用注解 @Component
    1
    2
    3
    4
    5
    6
    7
    8
    import org.springframework.stereotype.Component;

    @Component
    public class MyService {
    public void doSomething() {
    System.out.println("Doing something...");
    }
    }
  • @Component:通用组件
  • @Service:表示业务逻辑层
  • @Repository:表示数据访问层
  • @Controller:表示Web控制器
  1. 使用 @Bean 注解
    当你希望手动控制某个 Bean 的创建过程时,用这个方式。
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

@Bean // 在方法上使用 @Bean,Spring会调用这个方法并将返回值作为一个 Bean。
public MyService myService() {
return new MyService(); // 手动创建并返回对象
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Car {

private Engine engine;

@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}

public void start() {
engine.run();
}
}
  • 字段注入
1
2
3
4
5
6
7
8
9
10
@Component
public class Car {

@Autowired
private Engine engine;

public void start() {
engine.run();
}
}

AOP

AOP(Aspect-Oriented Programming)面向切面编程。在传统的面向对象编程中,比如我们在写一个网站的业务逻辑时,比如处理订单、用户登录等,我们经常还需要加上日志记录、权限校验、事务管理等功能。这些功能虽然必须有,但它们跟业务逻辑(比如下订单)并没有直接关系。

比如我有一个订单服务:

1
2
3
4
5
public void createOrder() {
System.out.println("日志:开始执行 createOrder");
// 下单逻辑
System.out.println("日志:createOrder 执行完毕");
}

Spring AOP 是 Spring 框架提供的一种实现 AOP 编程的方法,它通过代理对象把横切逻辑“织入”到业务逻辑中,不需要你改业务代码。Spring 根据配置创建一个代理对象,每当你调用业务方法(如 orderService.createOrder())时,代理对象会先执行切面逻辑(如日志),再执行真正的业务逻辑。

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
@Service
public class OrderService {

public void createOrder() {
System.out.println("执行创建订单逻辑...");
}
}

// 定义一个切面类(日志逻辑)
@Aspect
@Component
public class LogAspect {

// 切入点表达式:拦截所有 OrderService 的方法
@Pointcut("execution(* com.example.demo.OrderService.*(..))")
public void orderMethods() {}

// 前置通知:方法执行前记录日志
@Before("orderMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("日志:开始执行方法 -> " + joinPoint.getSignature().getName());
}

// 后置通知:方法执行后记录日志
@After("orderMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("日志:结束执行方法 -> " + joinPoint.getSignature().getName());
}
}

// 启动类开启 AOP 功能
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

这样,当我调用 orderService 的代码时,比如 orderService.createOrder(),控制台会打印:

1
2
3
日志:开始执行方法 -> createOrder
执行创建订单逻辑...
日志:结束执行方法 -> createOrder

Spring的原理

反射

反射是 Java 提供的一种机制,可以在程序运行时(不是写代码时),动态地获取类的信息(比如类的名字、方法、变量、构造函数等),甚至动态地调用对象的方法或访问它的属性。详细的反射介绍:反射

IoC原理

回顾IoC主要是将对象的创建和依赖管理的责任从程序员手中“反转”给一个容器来管理,从而降低耦合度。在Spring启动时,会进行以下处理:

  1. 当 Spring 启动时,会扫描指定的包或目录(通常通过 @ComponentScan 注解指定),查找类上标注的注解(如 @Component、@Service、@Repository、@Controller 等)
  2. 所有标注了这些注解的类会被注册为 Spring 容器中的 Bean。
  3. 在创建 Bean 时,如果该 Bean 有依赖的其他 Bean(如通过 @Autowired 注解标注),Spring 会自动注入这些依赖对象。

AOP原理

动态代理

实例化一个接口时:

静态代理:你写了一个类去实现接口,写死了逻辑,编译器在编译期生成字节码。
动态代理:你在运行时才“生成一个类”(不是写出来的源码,而是直接生成字节码并加载),这个类会去实现接口,并在方法调用时,把实际处理交给 InvocationHandler。

如何让Java自动生成一个类来实现一个Interface?

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
// newProxyInstance 是 Java 提供的 JDK 动态代理 的入口方法。
// 它能在运行时创建一个代理对象,这个代理对象会实现你指定的接口,
// 但它的方法调用实际会被转发给一个你自己定义的处理器对象(InvocationHandler)
public static Object newProxyInstance(
ClassLoader loader, // 类加载器
Class<?>[] interfaces, // 接口列表
InvocationHandler h);

// 调用示例:实现对UserCervice interface的动态代理
public interface UserService {
void createUser(String username);
void deleteUser(String username);
}

public class Main {
public static void main(String[] args) {
// 创建一个 InvocationHandler,用来处理所有方法的调用
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印日志
System.out.println("[LOG] 调用方法:" + method.getName());
System.out.println("[LOG] 参数:" + args[0]);

// 模拟方法执行
if (method.getName().equals("createUser")) {
System.out.println("正在创建用户:" + args[0]);
} else if (method.getName().equals("deleteUser")) {
System.out.println("正在删除用户:" + args[0]);
}

return null; // 接口方法返回 void,这里返回 null
}
};

// 创建代理对象(实现了 UserService 接口)
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[] { UserService.class },
handler
);

// 调用代理对象的方法,实际会被拦截并执行 handler.invoke(...)
proxy.createUser("alice");
proxy.deleteUser("bob");
}
}

以上例子,使用了动态代理的方法,创建了一个对象,实现了 UserService 接口。

实际输出:

1
2
3
4
5
6
7
[LOG] 调用方法:createUser
[LOG] 参数:alice
正在创建用户:alice

[LOG] 调用方法:deleteUser
[LOG] 参数:bob
正在删除用户:bob

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 的实例。

开发基础知识 - Spring基本介绍,特性与原理
https://thiefcat.github.io/2025/07/02/SWE-basic/spring/spring-basic/
Author
小贼猫
Posted on
July 2, 2025
Licensed under