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

Background

单例模式

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

示例单例模式的代码:

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 防止指令重排。

正确的单例模式代码:

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 实现,两者耦合度高:

// 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)

使用依赖注入:

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代码示例

代码结构

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:应用入口
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:实体类
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:仓库接口
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:服务类(业务逻辑)
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 接口)
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

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
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 的创建过程时,用这个方式。
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。

  • 构造器注入
@Component
public class Car {
    private final Engine engine;

    @Autowired
    public Car(Engine engine) {
        this.engine = engine;
    }
}
  • Setter 注入
@Component
public class Car {

    private Engine engine;

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

    public void start() {
        engine.run();
    }
}
  • 字段注入
@Component
public class Car {

    @Autowired
    private Engine engine;

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

AOP

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

比如我有一个订单服务:

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

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

@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(),控制台会打印:

日志:开始执行方法 -> 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?

// 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 接口。

实际输出:

[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基本介绍,特性与原理
http://example.com/2025/07/02/SWE_basis/Spring/spring-basic/
Author
Songlin Zhao
Posted on
July 2, 2025
Licensed under