本文参考 JavaGuide

Spring

什么是Spring

Spring 是一款基于 Java 的轻量级开源开发框架,旨在提高开发人员的开发效率以及系统的可维护性。

Spring 框架(Spring Framework)是很多模块的集合,使用这些模块可以很方便地协助进行开发。Spring 支持控制反转(Inversion of Control, IOC) 和 面向切面编程(Aspect-Oriented Programming, AOP)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。

Spring的核心功能主要是 IoCAOP,核心思想是不重新造轮子,开箱即用,提高开发效率。

Spring模块

Spring 4.x

Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

  • Core Container:Spring 框架的核心模块、基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块。
    • spring-core:Spring 框架基本的核心工具类。
    • spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
    • spring-context:提供对国际化、事件传播、资源加载等功能的支持。
    • spring-expression:提供对表达式语言(Spring Expression Language, SpEL)的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。
  • AOP:面向切面编程,提供对切面编程的支持。
    • spring-aop:提供了面向切面的编程实现。
    • spring-aspects:该模块为与 AspectJ 的集成提供支持。
    • spring-instrument:提供了为 JVM 添加代理(agent)的功能。其为 Tomcat 提供了一个织入代理,能够为 Tomcat 传递类文件,就像这些文件是被类加载器加载的一样。
  • Data Access/Integration:数据访问/集成层,提供对数据库访问、事务管理、ORM、OXM、JMS 等功能的支持。
    • spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
    • spring-tx:提供事务管理的支持。
    • spring-orm:提供对 Hibernate、JPA、iBatis 等 ORM 框架的支持。
    • spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
    • spring-jms:消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。
  • Spring Web:Web 模块,提供对 Web 应用开发的支持。
    • spring-web:提供了基本的 Web 功能,如多文件上传、使用 Servlet 监听器初始化 Spring IoC 容器等。
    • spring-webmvc:提供了 Spring MVC 的实现。
    • spring-websocket:提供了对 WebSocket 的支持,可以让客户端和服务端进行双向通信。
    • spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。
  • Messagingspring-messaging 是Spring4.0新加入的模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。
  • Spring Test:Spring有控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 finalstaticprivate 方法)等等常用的测试框架支持的都比较好。

Spring各模块依赖关系

Spring/Spring Boot/Spring MVC之间的关系

  • Spring是基于 Java 的轻量级开源框架,包含众多模块,其中最重要的是Spring-Core(主要提供 IoC 依赖注入功能的支持)模块, Spring中的其他模块(如 Spring MVC)的功能实现基本都需要依赖于该模块。
  • Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
  • 使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。过于麻烦,所以 Spring Boot 诞生了,其只是简化了配置,如果需要构建 MVC 架构的 Web 程序,还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 简化了 Spring MVC 的很多配置,真正做到开箱即用!
    • Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。

Spring IoC✅

什么是Spring IoC

控制反转(Inversion of Control, IoC)是一种设计思想,而不是一个具体的技术实现,并非Spring特有。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。

现有类 A 依赖于类 B

  • 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
  • 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。

IoC使开发者丧失了创建、管理对象的权利,但是也使得开发者不用再考虑对象的创建、管理等一系列的事情,只需要专注于业务逻辑的实现。

IoC解决了什么问题

  1. 依赖管理和解耦:在传统的Java应用中,类与类之间的依赖是通过硬编码实现的,这导致了代码的高度耦合和难以维护。Spring IoC通过依赖注入(Dependency Injection,DI)模式,将对象的创建和依赖关系的管理交给Spring容器处理,从而实现了组件之间的解耦,提升了代码的可维护性和可测试性。
  2. 对象生命周期管理:Spring容器负责管理Bean的生命周期,从创建、初始化到销毁,开发者可以通过配置和注解来控制这些生命周期方法,从而避免了手动管理对象生命周期的复杂性。
  3. 配置集中管理:通过Spring IoC,可以将应用程序的配置信息集中在一个或多个配置文件中(如XML文件、Java配置类或注解),从而使得配置更加集中和统一,便于管理和维护。
  4. 代码的可测试性:通过依赖注入,可以轻松地替换实际的依赖对象为模拟对象(mock objects),从而提高了单元测试的可行性和便捷性。
  5. 模块化和可插拔性:Spring IoC支持通过不同的配置方式(XML、注解、Java配置类等)来实现模块化配置,便于扩展和维护。同时,Spring IoC容器支持不同类型的Bean作用域(如单例、原型等),增强了系统的灵活性和可扩展性。

通过Spring IoC的依赖注入和面向接口编程的方式,开发者可以更加专注于业务逻辑的实现,而不必过多关心对象的创建和管理,从而提升了开发效率和代码质量。

Spring IoC原理

控制反转指将对象的创建和依赖关系的管理从应用程序代码中抽离出来,交给Spring容器进行管理。

控制反转

  • 控制:指的是对象创建(实例化、管理)的权力
  • 反转:控制权交给外部环境(Spring 框架、IoC 容器)

实现控制反转有多种方式:

  • 依赖注入:通过注入方式实现依赖管理,易于测试和维护,广泛应用于现代开发框架(如Spring)。
  • 服务定位器模式:通过中央注册器获取服务,使用方便,但可能引入全局状态和隐藏依赖。
  • 事件驱动架构:通过事件进行解耦,适用于异步处理和复杂业务流程。
  • 依赖查找:通过标准API查找依赖,适用于某些特定场景,但查找过程可能较为复杂。

Spring IoC容器
Spring IoC容器是负责管理对象及其依赖关系的核心组件。IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。主要的IoC容器有:

  • BeanFactory:最基础的IoC容器,提供基本的依赖注入功能。
  • ApplicationContext:继承自BeanFactory,提供更多高级功能,如事件发布、国际化、AOP等。

什么是Spring Bean

在Spring中,bean是由IoC容器管理的对象。bean的定义和配置可以通过以下几种方式:

  • XML配置文件:在XML文件中定义bean及其依赖关系。
  • 注解配置:通过Java注解(如@Component@Autowired等)定义和注入bean
  • Java配置类:使用@Configuration@Bean注解,通过Java类定义bean

将一个类声明为Bean的注解有哪些

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

@Component/@Bean区别

主要区别

  1. 定义方式:
  • @Component 是在类上使用,Spring 自动扫描并注册 Bean。
  • @Bean 是在方法上使用,返回值被注册为 Bean。
  1. 适用场景:
  • @Component 适用于开发人员自己编写的类。
  • @Bean 适用于第三方库的类或需要复杂配置的 Bean。
  1. 扫描和配置:
  • @Component 需要启用组件扫描。
  • @Bean 通常与配置类一起使用,不需要组件扫描。

总结来说,@Component 更适合于直接在类上进行标注,简化了配置过程;而 @Bean 则提供了更多的灵活性,可以在配置类中以编程的方式定义 Bean。

@Component

  • 用途:@Component 用于标记一个类为 Spring 容器的组件(Bean)。
  • 使用方式:直接在类上使用。Spring 会自动扫描带有 @Component 注解的类,并将其注册为 Spring 容器中的 Bean。
  • 扫描:需要在配置类上使用 @ComponentScan 注解来启用自动扫描。
  • 适用范围:通常用于标记服务层、数据访问层、控制层等组件类。
  • 例子:
    1
    2
    3
    4
    @Component
    public class MyService {
    // 服务实现
    }
    @Bean
  • 用途:@Bean 用于在配置类中定义一个方法,该方法的返回值会被注册为 Spring 容器中的 Bean。
  • 使用方式:在配置类的方法上使用,方法的返回值会作为 Bean 注册到 Spring 容器中。
  • 配置类:通常与 @Configuration 注解一起使用,表明这是一个配置类。
  • 适用范围:通常用于定义第三方库的 Bean,或者需要复杂初始化的 Bean。
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class AppConfig {
    @Bean
    public MyService myService() {
    return new MyServiceImpl();
    }
    }

用于注入Bean的注解有哪些?

@Autowired

  • 用途@Autowired 用于自动注入 Bean,Spring 会自动满足标记了该注解的依赖。
  • 使用位置:可以用在字段、构造方法、Setter 方法以及普通方法上。
  • 特点:默认按类型注入,可以结合 @Qualifier 按名称注入。
  • 例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class MyService {

    @Autowired
    private MyRepository myRepository;

    // 或者使用构造方法注入
    @Autowired
    public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
    }
    }

@Resource

  • 用途@Resource 是 JSR-250 标准的注解,可以按名称或类型注入。
  • 使用位置:字段、Setter 方法。
  • 特点:默认按名称注入,如果找不到匹配的 Bean,则按类型注入。
  • 例子
    1
    2
    3
    4
    5
    6
    @Service
    public class MyService {

    @Resource(name = "myRepository")
    private MyRepository myRepository;
    }

@Qualifier

  • 用途:与 @Autowired 一起使用,按名称注入 Bean。
  • 使用位置:字段、构造方法参数、Setter 方法参数。
  • 特点:解决同类型 Bean 多个实例的冲突问题。
  • 例子
    1
    2
    3
    4
    5
    6
    7
    @Service
    public class MyService {

    @Autowired
    @Qualifier("specificRepository")
    private MyRepository myRepository;
    }

@Inject

  • 用途@Inject 是 JSR-330 标准的注解,功能与 @Autowired 类似。
  • 使用位置:字段、构造方法、Setter 方法。
  • 特点:仅支持按类型注入,可以结合 @Named 注解按名称注入。
  • 例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class MyService {

    @Inject
    private MyRepository myRepository;

    // 或者使用构造方法注入
    @Inject
    public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
    }
    }

@Value

  • 用途@Value 用于注入简单值,比如基本类型、字符串、Spring EL 表达式等。
  • 使用位置:字段、构造方法参数、Setter 方法参数。
  • 特点:可以注入配置文件中的属性值。
  • 例子
    1
    2
    3
    4
    5
    6
    @Service
    public class MyService {

    @Value("${app.name}")
    private String appName;
    }

@Autowired@Resource用的多一些。

@Autowired/@Resource区别

  • 默认注入方式:
    • @Autowired 默认根据接口类型(byType)去匹配并注入Bean(接口的实现类)。当一个接口存在多个实现类的话,Autowired 可以通过 @Qualifier 注解来显式指定类名称。
    • @Resource 当一个接口存在多个实现类的话,默认按类名称(byName)注入。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType
  • 注解来源:
    • @Autowired:Spring 框架提供的注解。
    • @Resource:JSR-250(JDK) 标准提供的注解。
  • 使用位置:
    • @Autowired 可以用在构造函数、方法、字段以及参数上。
    • @Resource 通常用在字段和 Setter 方法上的注入。
  • 配置属性:
    • @Autowired:可以结合 @Qualifier 按名称注入。
    • @Resource:可以通过 name 属性指定 Bean 名称,通过 type 属性指定 Bean 类型。
  • 处理机制:
    • @Autowired:使用 Spring 的依赖注入机制。
    • @Resource:使用 JNDI 进行资源查找,适合在 Java EE 容器环境中使用。

总的来说,@Autowired 更加灵活和强大,适合在 Spring 应用中广泛使用;而 @Resource 则提供了一种更标准化的方式进行依赖注入,特别是在需要与 Java EE 容器进行集成时。

@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。

1
2
3
4
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}

如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定nametype属性(不建议这么做)则注入方式为byType+byName

Bean的作用域有哪些?

在 Spring 框架中,Bean 的作用域(scope)定义了 Bean 的生命周期和可见性。Spring 提供了以下几种常见的 Bean 作用域:

  • Singleton
    • 描述:默认作用域。在整个 Spring 容器中只有一个 Bean 实例,每次请求该 Bean 时都会返回同一个实例。
    • 使用场景:大多数情况下使用,适用于无状态的 Bean。
  • Prototype
    • 描述:每次请求该 Bean 时,都会创建一个新的实例。
    • 使用场景:适用于有状态的 Bean 或需要每次使用时创建新实例的 Bean。
  • Request
    • 描述:在一个 HTTP 请求中,每次请求该 Bean 时,都会创建一个新的实例。该作用域仅在 Web 应用程序中有效。
    • 使用场景:适用于每个 HTTP 请求需要一个独立 Bean 实例的情况,例如处理用户请求的控制器。
  • Session
    • 描述:在一个 HTTP 会话中,每次请求该 Bean 时,都会返回同一个实例。该作用域仅在 Web 应用程序中有效。
    • 使用场景:适用于需要在整个用户会话期间保持状态的 Bean。
  • Global Session
    • 描述:在一个全局 HTTP 会话中,每次请求该 Bean 时,都会返回同一个实例。该作用域主要用于 Portlet 应用程序。
    • 使用场景:适用于需要在全局 Portlet 会话期间保持状态的 Bean。
  • Application
    • 描述:在整个应用程序生命周期内,每次请求该 Bean 时,都会返回同一个实例。通常在 Web 应用程序中使用。
    • 使用场景:适用于需要在整个应用程序生命周期内共享的 Bean。
1
2
3
4
5
6
// Scope中可以指定Bean的作用域,取值:singleton、prototype、request、session、globalSession、application
@Component
@Scope("singleton")
public class MySingletonBean {
// 实现代码
}

Bean是线程安全的吗

在 Spring 框架中,Bean 的线程安全性取决于其作用域和状态。以下是一些常见情况:

Singleton Bean

  • 默认作用域@Scope("singleton")
  • 描述:单例 Bean 在 Spring 容器中只有一个实例,并且该实例会被多个线程共享。
  • 线程安全性:单例 Bean 本身并不是线程安全的。如果单例 Bean 中包含可变的共享状态,则需要确保其线程安全。这可以通过以下方式实现:
    • 不在单例 Bean 中使用可变状态。
    • 使用线程安全的数据结构(如 ConcurrentHashMap)。
    • 在访问可变状态时使用同步(如 synchronized)。
    • 采用无状态的设计模式。

Prototype Bean

  • 作用域@Scope("prototype")
  • 描述:原型 Bean 每次请求时都会创建一个新的实例,因此每个线程都会获得一个新的实例。
  • 线程安全性:由于每个线程都有自己的实例,原型 Bean 通常是线程安全的。但是,原型 Bean 的生命周期由使用它的对象管理,可能需要注意其创建和销毁的时机。

Request & Session Scoped Beans

  • 作用域@Scope("request")@Scope("session")
  • 描述:请求作用域的 Bean 在一个 HTTP 请求中创建和销毁。会话作用域的 Bean 在一个 HTTP 会话中创建和销毁。
  • 线程安全性:请求作用域的 Bean 通常是线程安全的,因为每个请求都是独立的。会话作用域的 Bean 可能会被多个线程共享,需要考虑线程安全性。

Application Scoped Beans

  • 作用域@Scope("application")
  • 描述:应用程序作用域的 Bean 在整个应用程序生命周期内共享。
  • 线程安全性:与单例 Bean 类似,应用程序作用域的 Bean 需要注意线程安全性问题。

Global Session Scoped Beans

  • 作用域@Scope("globalSession")
  • 描述:全局会话作用域的 Bean 在 Portlet 应用程序的全局会话中共享。
  • 线程安全性:类似于会话作用域的 Bean,需要考虑线程安全性。

总之,除非 Bean 的设计明确考虑了线程安全性,否则默认情况下 Spring 容器不会保证 Bean 的线程安全。确保线程安全的最佳实践包括无状态设计、使用线程安全的数据结构以及适当的同步机制。

Bean的生命周期

  1. 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
  2. Bean 属性填充(依赖注入):为 Bean 设置相关属性和依赖,如@Autowired 等注解注入的对象、@Value 注入的值、setter方法或构造函数注入依赖和值、@Resource注入的各种资源。
  3. Bean的初始化
    1. 调用Aware接口的方法:
      • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
      • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
      • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
      • 如果实现了其他 *.Aware接口,就调用相应的方法。
    2. 前置处理:如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
    3. 初始化方法:如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    4. 后置处理:如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。
  4. 使用 Bean:Bean 可以被容器使用了,可以调用 Bean 的方法处理业务逻辑。
  5. 销毁 Bean:销毁并不是立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。
    • 也可以直接通过@PreDestroy 注解标记 Bean 销毁之前执行的方法。

Bean的生命周期

Spring 中提供的 Aware 接口主要有:
BeanNameAware:注入当前 bean 对应 beanName
BeanClassLoaderAware:注入加载当前 beanClassLoader
BeanFactoryAware:注入当前 BeanFactory 容器的引用。

Spring AOP✅

对Spring AOP的理解

面向切面编程(Aspect-Oriented Programming, AOP)是面向对象编程(OOP)的延续,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

Spring AOP代理方式

Spring AOP也集成了 AspectJ,可以使用 AspectJ 的注解来实现 AOP。

AOP解决了什么问题

  1. 横切关注点分离:在传统的面向对象编程中,某些功能(如日志记录、事务管理、安全性验证等)往往分散在多个类和方法中,导致代码的重复和耦合。AOP通过将这些横切关注点独立出来,以切面的形式进行集中管理,从而简化了核心业务逻辑。
  2. 提高代码可读性和可维护性:通过将横切关注点分离出来,核心业务代码变得更加简洁、清晰。这样,开发者可以更加专注于业务逻辑的实现,提高代码的可读性和可维护性。
  3. 动态代理和拦截功能:AOP提供了动态代理机制,可以在不修改原有代码的情况下,对方法进行拦截和增强。这种机制在处理权限验证、性能监控、缓存管理等场景时非常有用。
  4. 代码重用:由于横切关注点可以以独立的切面形式存在,可以在不同的应用和模块之间共享和重用,提高了代码的复用性。
  5. 减少代码耦合:通过AOP,将横切关注点从业务逻辑中分离出来,使得各个模块之间的耦合度降低,从而提高了系统的灵活性和可扩展性。

AOP应用场景

  1. 日志记录:可以在方法执行前后自动记录日志,而不需要在每个方法中手动添加日志代码。
  2. 事务管理:可以在方法开始时开启事务,方法结束时提交或回滚事务,确保数据一致性。
  3. 安全性验证:可以在方法执行前进行权限验证,确保用户有权限执行该操作。
  4. 性能监控:可以统计方法的执行时间,进行性能分析和优化。
  5. 异常处理:可以统一处理方法中的异常,避免重复的异常处理代码。

Spring AOP原理

Spring AOP(面向切面编程)是Spring框架中的一个重要模块,用于在不修改现有代码的情况下向程序中添加新的行为。基本原理和关键概念如下:

  1. 核心概念
    • Target(目标):被通知的对象
    • Proxy(代理):向目标对象应用通知之后创建的代理对象
    • Join Point(连接点):目标对象的所属类中,定义的所有方法均为连接点
    • Pointcut(切点):切点是用于匹配连接点的表达式。被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
    • Advice(通知):增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情。Spring AOP支持五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
    • Aspect(切面):切面是AOP的核心概念。切入点(Pointcut)+通知(Advice)。
    • Weaving(织入):将通知应用到目标对象,进而生成代理对象的过程动作。Spring AOP在运行时通过动态代理实现织入。
  2. 实现机制
    Spring AOP主要通过两种方式来实现AOP功能:
    • JDK动态代理:适用于基于接口的代理。Spring AOP使用JDK动态代理来创建实现了一个或多个接口的代理对象。
    • CGLIB代理:适用于没有实现接口的类。Spring AOP使用CGLIB库生成目标类的子类来实现代理。
  3. 工作流程
    • 定义切面和通知:使用@Aspect注解定义切面,并在切面类中使用@Before@After等注解定义通知。
    • 配置AOP:在Spring配置文件中启用AOP支持,或使用@EnableAspectJAutoProxy注解来启用AOP自动代理。
    • 应用切面:Spring容器根据配置和切点表达式在运行时生成代理对象,并将通知织入到目标方法的执行中。

Spring AOP的核心在于通过动态代理和切点表达式,实现了对横切关注点的模块化管理,使得代码更易于维护和扩展。

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
// 定义切面
@Aspect
public class LoggingAspect {
// 定义切点表达式
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 定义前置通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Executing method: " + joinPoint.getSignature().getName());
}
// 定义后置通知
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Method executed: " + joinPoint.getSignature().getName());
}
}
// 启用AOP支持
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}

在示例中,定义了一个LoggingAspect切面,其中包含前置通知和后置通知。然后,在Spring配置中启用了AOP支持,并将切面类注册为Bean。Spring容器将在运行时生成代理对象,并在目标方法执行前后执行通知。

Spring AOP/AspectJ AOP区别

  1. 实现方式
    • Spring AOP:基于代理机制(Proxy-based),主要使用JDK动态代理和CGLIB动态代理。属于运行时增强
    • AspectJ AOP:基于字节码操作(Bytecode Manipulation),通过编译时、加载时和运行时织入(Weaving)实现。属于编译时增强
  2. 功能
    • Spring AOP:只支持方法级别的AOP。
    • AspectJ AOP:提供更强大的功能和更高的性能,适用于类级别和方法级别的AOP。
  3. 性能
    • Spring AOP:由于基于代理机制,性能相对较低,适用于大多数应用场景,但在高性能要求的场景中可能不够高效。
    • AspectJ AOP:由于直接操作字节码,性能较高,适用于对性能要求较高的应用场景。
  4. 配置和使用
    • Spring AOP:配置相对简单,使用Spring的注解和配置文件即可实现。适用于已经使用Spring框架的应用,容易集成和使用。
    • AspectJ AOP:配置较为复杂,需要AspectJ编译器或AspectJ加载时编织器。需要对AspectJ的语法和配置有一定了解,适用于需要更强大AOP功能的应用。
  5. 使用场景
    • Spring AOP:适用于大多数Spring应用,特别是那些只需要方法级别AOP的场景。
    • AspectJ AOP:适用于需要更强大AOP功能、更高性能和更细粒度控制的场景。

如果项目已经在使用Spring框架并且AOP需求较为简单,Spring AOP是一个不错的选择;如果需要更强大和高性能的AOP功能,AspectJ AOP是更好的选择。

AspectJ的五种通知类型

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

多个切面的执行顺序如何控制

  1. 通常使用@Order 注解直接定义切面顺序

    1
    2
    3
    4
    5
    // 值越小优先级越高
    @Order(3)
    @Component
    @Aspect
    public class LoggingAspect implements Ordered {
  2. 实现Ordered 接口重写 getOrder 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    @Aspect
    public class LoggingAspect implements Ordered {
    // ....
    @Override
    public int getOrder() {
    // 返回值越小优先级越高
    return 1;
    }
    }

SpringMVC✅

MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

MVC核心组件

  • DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping处理器映射器,根据 URL 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端

SpringMVC工作原理

SpringMVC工作原理

流程

  1. 客户端(浏览器)发送请求, DispatcherServlet拦截请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. DispatcherServlet 调用 HandlerAdapter适配器执行 Handler
  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServletModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View
  5. ViewResolver 会根据逻辑 View 查找实际的 View
  6. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  7. View 返回给请求者(浏览器)

Spring框架中用到了哪些设计模式

  1. **工厂模式 (Factory Pattern)**:
    • Spring 使用工厂模式来创建对象实例。BeanFactoryApplicationContext 是 Spring 中的两种主要的工厂类,用于管理和创建 bean 对象。
      • BeanFactory:延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
      • ApplicationContext:容器启动的时候,不管用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。
  2. **单例模式 (Singleton Pattern)**:
    • 默认情况下,Spring 容器中的 bean 是单例的,这意味着在整个 Spring 容器中只有一个 bean 实例存在。这通过 Spring 的 IoC 容器来管理。
  3. **代理模式 (Proxy Pattern)**:
    • Spring AOP (Aspect-Oriented Programming) 使用代理模式来实现横切关注点(如事务管理、日志记录等)的分离。Spring 提供了 JDK 动态代理和 CGLIB 代理两种方式。
  4. **模板模式 (Template Method Pattern)**:
    • Spring 中的模板类(如 JdbcTemplate, RestTemplate)通过封装一系列的操作步骤,简化了数据库操作和 REST 调用等重复性的编程任务。
  5. **依赖注入模式 (Dependency Injection Pattern)**:
    • 依赖注入是 Spring 的核心设计模式,通过构造器注入、setter 注入和接口注入等方式,实现了对象之间的松耦合。
  6. **观察者模式 (Observer Pattern)**:
    • Spring 事件机制使用了观察者模式。可以通过 ApplicationEventApplicationListener 在应用程序中发布和监听事件。
  7. **适配器模式 (Adapter Pattern)**:
    • Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
    • Spring MVC 中的 HandlerAdapter 用于适配不同类型的处理器方法,使得这些方法可以作为统一的处理器进行调用。
  8. **策略模式 (Strategy Pattern)**:
    • Spring 中的某些功能(如事务管理)使用了策略模式,允许在运行时选择具体的实现策略。
  9. **装饰者模式 (Decorator Pattern)**:
    • Spring 的 BeanPostProcessorBeanFactoryPostProcessor 用于在 bean 初始化前后进行一些自定义的处理,这实际上是对原有 bean 的功能进行扩展和增强。

这些设计模式的使用,使得 Spring 框架具备高度的灵活性、可扩展性和可维护性。

BeanFactory/ApplicationContext区别

  • 功能:

    • BeanFactory是Spring的核心接口,提供了基本的IOC(Inversion of Control)容器功能,负责实例化、配置和管理bean。
    • ApplicationContextBeanFactory的子接口,扩展了更多的企业级功能,如事件机制、国际化支持、AOP(面向切面编程)集成、Web应用上下文等。
  • 初始化:

    • BeanFactory采用懒加载机制,只有在第一次访问某个bean时,才会实例化该bean。这对启动性能有好处,但可能会导致首次访问时的延迟。
    • ApplicationContext预初始化:其在启动时会预先实例化所有单例bean,确保在应用启动时所有必要的bean已经准备好。这有助于捕获配置问题并提升应用的响应速度。
  • 使用场景:

    • BeanFactory适用于资源受限的环境,如移动设备或嵌入式系统,适用于对性能要求高且对Spring的高级功能需求较少的应用。
    • ApplicationContext适用于标准的企业级应用中,特别是Web应用,适用于需要Spring框架提供的所有功能和高级特性的应用。
  • 依赖查找:

    • BeanFactory依赖于显式的查找,开发者需要通过getBean方法手动获取bean。
    • ApplicationContext除了显式查找外,还支持自动注入和其他更加灵活的依赖管理方式。
  • 如果应用需要完整的Spring功能,包括事件发布、国际化、AOP支持以及其他企业级特性,使用ApplicationContext

  • 如果应用在一个资源受限的环境中运行并且只需要基本的IOC容器功能,BeanFactory可能更合适。

通常在实际的Spring应用开发中,大多数开发者会选择使用ApplicationContext,因为它提供了更丰富的功能和更好的开发体验。

Spring循环依赖及解决方案

在Spring框架中,循环依赖是指两个或多个bean之间相互依赖,导致无法正常实例化的问题。例如,Bean A依赖Bean B,而Bean B又依赖Bean A。这种情况下,Spring容器无法确定先实例化哪个bean,从而陷入死循环。

Spring主要通过三级缓存解决循环依赖:
如果发生循环依赖的话,就去 三级缓存 singletonFactories 中拿到三级缓存中存储的 ObjectFactory 并调用它的 getObject() 方法来获取这个循环依赖对象的前期暴露对象(虽然还没初始化完成,但是可以拿到该对象在堆中的存储地址了),并且将这个前期暴露对象放到二级缓存中,这样在循环依赖时,就不会重复初始化了!

循环依赖的问题主要有两种情况:

  1. 构造函数循环依赖:构造函数循环依赖指的是两个bean在构造函数中相互依赖。这种情况由于在实例化bean时必须提供构造函数的参数,所以Spring无法解决这种类型的循环依赖。
    1. 将构造函数注入改为setter注入:Setter注入是解决循环依赖的常用方法。Spring能够在实例化一个bean并将其放入singleton池后,通过setter方法注入其他bean的依赖。
    2. 使用@Lazy注解:@Lazy注解可以延迟bean的初始化,直到真正需要该bean时才进行实例化,从而打破循环依赖。
    3. 使用接口和代理:使用Spring AOP可以创建代理对象,从而打破循环依赖。通过AOP,Spring可以创建一个代理对象来代替实际的bean,这样即使两个bean相互依赖,Spring也能处理。
  2. Setter循环依赖:Setter循环依赖指的是两个bean通过setter方法相互依赖。Spring可以通过三级缓存机制来解决这种类型的循环依赖。
    1. Spring默认已经能够解决setter循环依赖,无需额外的配置。Spring会在实例化bean时,通过三级缓存提前暴露bean的引用,从而解决循环依赖。
    2. 使用@PostConstruct注解:使用@PostConstruct注解可以在依赖注入完成后执行初始化方法,这样可以在方法内部设置依赖,避免循环依赖。

Spring的三级缓存

Spring通过三级缓存解决了循环依赖的问题。三级缓存分别是singletonObjectsearlySingletonObjectssingletonFactories,分别是三个 Map。

  • 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般获取 Bean 都是从这里获取的,但非所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。
  • 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充),也就是三级缓存中ObjectFactory产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。
  • 三级缓存(singletonFactories):存放ObjectFactoryObjectFactorygetObject()方法可以生成原始 Bean 对象或者代理对象。三级缓存只会对单例 Bean 生效。

缺点

  • 增加了内存开销(需要维护三级缓存,也就是三个 Map),降低了性能(需要进行多次检查和转换)。
  • 少部分情况是不支持循环依赖的,比如非单例的 bean 和@Async注解的 bean 无法支持循环依赖。

Spring创建Bean流程

  1. 先去 一级缓存 singletonObjects 中获取,存在就返回;
  2. 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;
  3. 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,获取成功之后,从三级缓存移除,并将该对象加入到二级缓存中。

Spring 在创建 Bean 的时候,如果允许循环依赖的话,Spring 就会将刚刚实例化完成,但是属性还没有初始化完的 Bean 对象给提前暴露出去。

三级缓存解决循环依赖流程

  • 当 Spring 创建 A 之后,发现 A 依赖了 B ,又去创建 B,B 依赖了 A ,又去创建 A;
  • 在 B 创建 A 的时候,那么此时 A 就发生了循环依赖,由于 A 此时还没有初始化完成,因此在 一二级缓存 中肯定没有 A;
  • 那么此时就去三级缓存中调用 getObject() 方法去获取 A 的 前期暴露的对象 ,也就是调用上边加入的 getEarlyBeanReference() 方法,生成一个 A 的 前期暴露对象;
  • 然后就将这个 ObjectFactory 从三级缓存中移除,并且将前期暴露对象放入到二级缓存中,那么 B 就将这个前期暴露对象注入到依赖,来支持循环依赖。

只有两级缓存可以吗

在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。但是,当涉及到 AOP 时,二级缓存就显得非常重要了,因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。

@Lazy解决循环依赖

@Lazy 用来标识类是否需要懒加载/延迟加载,可以作用在类上、方法上、构造器上、方法参数上、成员变量中。

  • 如果一个 Bean 没有被标记为懒加载,那么它会在 Spring IoC 容器启动的过程中被创建和初始化。
  • 如果一个 Bean 被标记为懒加载,那么它不会在 Spring IoC 容器启动时立即实例化,而是在第一次被请求时才创建。这可以帮助减少应用启动时的初始化时间,也可以用来解决循环依赖问题。

@Lazy解决循环依赖的原理:
举一个例子,比如说有两个 Bean,A 和 B发生了循环依赖。那么 A 的构造器上添加 @Lazy 注解之后(延迟 Bean B 的实例化),加载的流程如下:

  1. 创建BeanA实例:Spring开始创建BeanA实例。在创建BeanA实例时,Spring发现其构造函数参数BeanB上有@Lazy注解。
  2. 创建BeanB的代理对象:由于@Lazy注解的存在,Spring不会立即创建BeanB实例,而是创建一个BeanB的 _代理对象_。这个代理对象会延迟实际的BeanB实例化,直到代理对象的某个方法被调用时才创建目标bean。
  3. 注入代理对象:Spring将BeanB的代理对象注入到BeanA中。
  4. 完成BeanA的创建:Spring完成BeanA的创建,并继续执行BeanA的初始化方法。
  5. 创建BeanB实例:当BeanB需要创建时,代理对象会触发实际的BeanB实例化过程。
  6. 注入BeanA实例:在创建BeanB实例时,Spring会发现BeanB依赖于BeanA,此时BeanA已经创建完成并且在Spring容器中可用,所以可以直接注入BeanA实例。

这里的代理对象使用 AOP 实现,Spring可以创建两种类型的代理对象:

  • JDK动态代理:使用Java的反射机制创建代理对象,适用于实现了接口的bean。
  • CGLIB代理:使用CGLIB库生成代理对象,适用于没有实现接口的bean。

Spring事务✅

事务是逻辑上的一组操作,要么都执行,要么都不执行。
四大特性:

  • 原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
  • 隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  • 持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

Spring对事务的支持

程序是否支持事务首先取决于数据库 ,如使用 MySQL,若 存储引擎是 innodb 引擎,则可以支持事务的,若是 myisam 引擎,那就不支持事务的。

Spring管理事务的两种方式

  • 编程式事务:在代码中硬编码(在分布式系统中推荐使用) :通过 TransactionTemplate或者 TransactionManager 手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。
  • 声明式事务:在 XML 配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用):实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多),开发中推荐使用,代码入侵性小。

Spring中的事务管理接口

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。Spring通过该接口,为JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等平台提供了对应的事务管理器,但具体由各个平台自己实现。
    • 主要定义了三个方法:getTransaction()commit()rollback()
      1
      2
      3
      4
      5
      6
      // 获得事务
      TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
      // 提交事务
      void commit(TransactionStatus var1) throws TransactionException;
      // 回滚事务
      void rollback(TransactionStatus var1) throws TransactionException;
    • 该接口将事务管理行为抽象出来,然后不同的平台去实现它,这样可以保证提供给外部的行为不变,方便扩展。
  • TransactionDefinition:事务属性(包含五个方面:事务隔离级别、传播行为、事务超时、是否只读、回滚规则)。
  • TransactionStatus:事务运行状态。
    • 该接口用来记录事务的状态 定义了一组方法,用来获取或判断事务的相应状态信息。

可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

Spring事务传播行为

在 Spring 框架中,事务传播行为(Transaction Propagation Behavior)定义了事务方法如何与现有事务协作。这些传播行为决定了一个事务方法是否在一个现有事务中运行,是否创建一个新的事务,或者是否在没有事务的情况下运行。

Spring 提供了七种事务传播行为:

  1. PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为。
    • 行为:加入当前事务或创建一个新的事务。
    • 适用场景:大多数业务场景,确保方法在一个事务中运行。
  2. PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则将当前事务挂起。
    • 行为:始终创建一个新事务,并挂起当前事务(如果存在)。
    • 适用场景:需要在一个完全独立的事务中运行的方法,例如日志记录。
  3. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
    • 行为:如果有事务则使用事务,没有则非事务执行。
    • 适用场景:既可以在事务中执行也可以在非事务中执行的方法。
  4. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
    • 行为:非事务方式执行,如果有事务则挂起当前事务。
    • 适用场景:方法必须在非事务中执行,例如读操作不需要事务管理。
  5. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    • 行为:非事务方式执行,如果有事务则抛出异常。
    • 适用场景:确保方法绝对不会在事务中运行的情况。
  6. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • 行为:必须在事务中执行,如果没有事务则抛出异常。
    • 适用场景:必须有现有事务的方法,例如依赖上游事务的操作。
  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则与 PROPAGATION_REQUIRED 的效果相同。
    • 行为:如果有事务则创建一个嵌套事务,如果没有则创建一个新事务。
    • 适用场景:需要保存部分回滚点的方法,适合复杂的业务场景。

Spring事务隔离级别

  • TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

@Transactional(rollbackFor = Exception.class)注解

Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

@Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时也可以在方法级别使用该标注来覆盖类级别的定义。

@Transactional 注解默认回滚策略是只有在遇到RuntimeException(运行时异常) 或者 Error 时才会回滚事务,而不会回滚 Checked Exception(受检查异常)。这是因为 Spring 认为RuntimeExceptionError 是不可预期的错误,而受检异常是可预期的错误,可以通过业务逻辑来处理。

如果想要修改默认的回滚策略,可以使用 @Transactional 注解的 rollbackFornoRollbackFor 属性来指定哪些异常需要回滚,哪些异常不需要回滚。例如,如果想要让所有的异常都回滚事务,可以使用如下的注解:

1
2
3
4
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
// some business logic
}

如果想要让某些特定的异常不回滚事务,可以使用如下的注解:

1
2
3
4
@Transactional(noRollbackFor = CustomException.class)
public void someMethod() {
// some business logic
}

Spring Data JPA

Spring Data JPA 是 Spring Data 项目的一部分,它为 Java 持久化 API (JPA) 提供了一个高层次的抽象。其主要目的是简化与数据库交互的复杂性,通过最小化配置和编码,使开发者能够更轻松地创建、读取、更新和删除数据库中的数据。

主要特点和功能

  1. 简化的存储库接口
    • Spring Data JPA 提供了 JpaRepository 接口,继承该接口的存储库(Repository)可以自动拥有常见的数据操作方法,如 savefindAllfindByIddelete 等。
  2. 自定义查询方法
    • 通过定义遵循命名规则的方法名,Spring Data JPA 可以自动生成 SQL 查询。例如,findByLastName 方法会生成一个根据 lastName 字段进行查询的 SQL。
  3. 查询注解
    • 可以使用 @Query 注解编写自定义的 JPQL 或原生 SQL 查询。
  4. 分页和排序
    • 支持分页和排序,通过方法参数 PageableSort,可以轻松实现分页和排序功能。
  5. 规格查询
    • 使用 Specification 接口,可以创建复杂的动态查询。
  6. 自动实现
    • Spring Data JPA 可以根据接口自动生成实现类,无需手动编写实现代码。

核心组件

  1. Repository 接口
    • CrudRepository:提供基本的 CRUD 操作。
    • PagingAndSortingRepository:在 CrudRepository 的基础上增加分页和排序功能。
    • JpaRepository:在 PagingAndSortingRepository 的基础上增加 JPA 相关操作。
  2. 实体类
    • 使用 JPA 注解(如 @Entity@Table@Id 等)来映射数据库表和字段。
  3. Spring Data JPA 配置
    • 通过配置文件(如 application.propertiesapplication.yml)来配置数据源、JPA 供应商、数据库方言等。

通过这些组件,开发者可以快速创建与数据库交互的应用程序,而不必编写大量的样板代码。Spring Data JPA 的抽象和自动化能力,使得开发过程更加高效和简洁。

JPA审计功能

审计功能主要是帮助记录数据库操作的具体行为比如某条记录是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候。

实体之间的关联关系注解

  • @OneToOne:一对一。
  • @ManyToMany:多对多。
  • @OneToMany:一对多。
  • @ManyToOne:多对一。

利用 @ManyToOne@OneToMany 也可以表达多对多的关联关系。

Spring Security

Spring Security 是一个强大的且高度可定制的认证和访问控制框架,属于 Spring 框架的一部分。它为基于 Spring 的企业应用程序提供了全面的安全性解决方案。以下是 Spring Security 的一些关键特性和功能:

  1. 认证(Authentication):Spring Security 提供了多种认证方式,包括基于表单的登录、HTTP Basic 认证、OAuth2 认证等。它还支持自定义认证逻辑,可以与数据库、LDAP、OAuth2 提供者等集成。
  2. 授权(Authorization):授权是指控制用户对资源的访问权限。Spring Security 使用表达式和基于角色的访问控制来实现细粒度的授权控制。你可以通过注解(如 @PreAuthorize@Secured)或基于 URL 的配置来定义访问规则。
  3. 安全上下文(Security Context):Spring Security 维护一个安全上下文,其中包含当前用户的认证信息。这个安全上下文可以在应用程序的各个部分访问,用于确定用户的身份和权限。
  4. 防护机制:Spring Security 提供了多种安全防护机制来防止常见的攻击,如:
    • CSRF(跨站请求伪造)防护:通过生成和验证 CSRF 令牌来防止跨站请求伪造攻击。
    • 会话固定攻击防护:通过在用户登录后生成新的会话 ID 来防止会话固定攻击。
    • **内容安全策略(CSP)**:防止 XSS(跨站脚本攻击)和数据注入攻击。
  5. 密码存储:Spring Security 提供了多种密码编码器(如 BCrypt、SCrypt、PBKDF2)来安全地存储用户密码。你可以选择合适的密码编码器来加强密码存储的安全性。
  6. 集成:Spring Security 与其他 Spring 项目(如 Spring Boot、Spring MVC)无缝集成,简化了安全配置和管理。使用 Spring Boot,可以通过自动配置快速启动安全功能。
  7. 可扩展性:Spring Security 是高度可定制的。你可以扩展或替换其默认实现来满足特定的安全需求。比如,自定义认证提供者、授权管理器、过滤器等。

MyBatis

MyBatis 是一个流行的持久层框架,用于简化 Java 应用程序与数据库之间的交互。主要提供下面功能:

  • SQL 映射:MyBatis 允许你使用 XML 或注解的方式将 SQL 语句映射到 Java 方法上,这样可以直接在代码中调用这些方法来执行 SQL 查询和更新操作。
  • 简化 JDBC 操作:MyBatis 封装了 JDBC 操作,使开发者不需要处理复杂的 JDBC API,如手动管理连接、结果集处理等。
  • 动态 SQL:MyBatis 支持动态生成 SQL 语句,可以根据条件灵活构建查询或更新语句,避免了在代码中拼接 SQL 字符串的麻烦。
  • 缓存支持:MyBatis 提供了一级缓存(默认开启)和二级缓存(可选配置),通过缓存机制提高了数据库查询的性能。
  • 灵活的配置:MyBatis 提供了多种配置方式,可以通过 XML 文件、注解和 Java 配置类进行配置,满足不同的使用需求。
  • 扩展性:MyBatis 具有良好的扩展性,开发者可以通过插件机制自定义和扩展 MyBatis 的功能,如自定义类型处理器、插件等。

SpringBoot

SpringBoot启动流程

  1. 调用启动类的main 方法
    • Spring Boot 应用通常是通过一个包含 @SpringBootApplication 注解的主类启动的,该类的 main 方法中调用SpringApplication.run()
  2. 创建 SpringApplication 实例
    • SpringApplication.run() 内部首先会创建一个 SpringApplication 实例,主要用于配置和管理整个应用的启动过程。
    • 这个实例初始化了一些基本的属性,比如:启动参数、运行环境(如开发、生产)、应用类型(如 Web 应用或非 Web 应用)。
  3. 设置启动配置
    • Spring Boot 会根据应用的上下文和依赖来设置配置属性,例如:
      • 通过 ApplicationContextInitializer 实例对上下文进行初始化。
      • 加载所有在 META-INF/spring.factories 文件中声明的 ApplicationListener 监听器。
    • 此时,SpringApplication 根据类路径、系统属性等配置自动决定应用类型(ReactiveServletNone)。
  4. 启动引导器(SpringApplicationRunListeners):Spring Boot 创建并启动 SpringApplicationRunListeners,它们会监听应用启动的各个阶段,并触发相应事件。主要包括以下几个事件:
    • Starting:表示应用程序刚刚启动。
    • Environment Prepared:此阶段设置环境参数(例如配置文件、属性源等)。
    • Context Prepared:在创建并配置好 ApplicationContext 后触发。
    • Context Loaded:当所有 Bean 定义被加载到上下文但尚未被实例化时触发。
    • Started:表示 ApplicationContext 已经启动并准备接受请求。
  5. 创建并准备 ApplicationContext
    • Spring Boot 根据应用的类型(比如 Web 应用)创建适当的 ApplicationContext,例如:
      • Web 应用使用 AnnotationConfigServletWebServerApplicationContext
      • 非 Web 应用使用 AnnotationConfigApplicationContext
    • 然后,Spring Boot 开始加载 @Configuration 类、扫描组件并注册所有的 Bean 定义。
  6. 配置环境 (Environment)
    • ApplicationContext 准备好后,Spring Boot 开始配置运行时环境。
    • 环境配置包含了系统属性、环境变量以及外部配置文件(如 application.propertiesapplication.yml),并通过 ConfigurableEnvironment 注入到上下文中。
  7. 刷新 ApplicationContext
    • Spring Boot 调用 refresh() 方法,完成 ApplicationContext 的初始化,包括 Bean 的创建、依赖注入、生命周期管理等。
    • 在此阶段,所有的 Bean 被完全加载和实例化,CommandLineRunnerApplicationRunner 也会在此之后执行。
  8. 启动内嵌的 Web 服务器(如果有)
    • 如果是 Web 应用,Spring Boot 会在应用上下文加载完成后启动内嵌的 Web 服务器(例如 Tomcat、Jetty 或 Undertow)。
    • 这个步骤通常是通过 WebServerStartStopLifecycle 组件来完成的。
  9. ApplicationRunnerCommandLineRunner 执行
    • 一旦所有的 Bean 都初始化完毕,Spring Boot 会执行所有实现了 ApplicationRunnerCommandLineRunner 接口的组件。这些组件通常用于执行一些应用启动后的初始化逻辑。
  10. 完成启动,进入事件循环
    • 启动流程最后,Spring Boot 进入应用的主事件循环(对于 Web 应用来说)。此时应用已经可以接收 HTTP 请求或执行其他业务逻辑。

Tomcat如何接收http请求

  1. 启动和初始化
    1. 启动 Tomcat
      • 当 Tomcat 启动时,它会加载并初始化所有配置文件(如 server.xmlweb.xml)。
      • 它会创建并配置一个或多个 Connector 对象,这些对象负责接收来自客户端的 HTTP 请求。
    2. 配置 Connector
      • server.xml 文件中配置 Connector,例如:
        1
        2
        3
        <Connector port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443" />
      • Connector 会监听指定的端口(例如 8080),并处理进入的请求。
  2. 接收 HTTP 请求
    1. 监听端口:Connector 监听配置的端口,接收来自客户端的 TCP 连接。
    2. 建立连接:当有客户端发起连接时,Connector 接受这个连接,并将其交给一个处理线程。
  3. 请求处理
    1. 请求解析:处理线程会解析 HTTP 请求数据,包括请求行(请求方法、请求 URI 和协议)、请求头和请求体。请求数据会被封装成一个 HttpServletRequest 对象。
    2. 请求路由:Tomcat 会根据请求的 URI 进行路由,将请求分发到相应的 Servlet
    3. 调用 Servlet:请求会被传递到适当的 Servlet 实例的 service() 方法中。具体的处理由 doGet()doPost() 等方法实现,取决于请求的方法类型。
    4. 生成响应:Servlet 处理完请求后,会生成响应内容并将其封装到 HttpServletResponse 对象中。响应内容包括响应状态码、响应头和响应体。
    5. 发送响应:处理线程将 HttpServletResponse 对象中的响应数据写回到客户端。Tomcat 会将响应数据通过网络发送回客户端,并关闭连接(如果是非持久连接)。
  4. 关闭连接:根据请求的 Connection 头部的值(如 Connection: keep-alive),Tomcat 决定是否保持连接以处理后续请求。在处理完请求后,Tomcat 可能会关闭连接或重用以提高性能。

SpringBoot自动装配原理

自动装配(Auto-Configuration)是Spring Boot框架的一个核心特性之一,它通过扫描应用程序的classpath和依赖关系,自动配置和装配Spring应用程序所需的各种组件。

在传统的Spring应用程序中,开发者需要手动配置大量的bean,例如数据源、事务管理器、视图解析器等。这些配置过程繁琐而容易出错,加大了开发难度和成本。而Spring Boot的自动装配机制则能够自动完成这些配置过程,使得开发者可以更加专注于业务逻辑的实现。

  1. 启动类注解扫描
    @SpringBootApplication:这是Spring Boot应用程序的入口注解,它集成了多个关键功能,包括自动配置(@EnableAutoConfiguration)、组件扫描(@ComponentScan)和配置(@Configuration)。
    @EnableAutoConfiguration:启动自动配置机制。
    @ComponentScan:扫描@Component、@Service、@Controller等注解的Bean,将它们注册到Spring容器中。
  2. 自动配置类的加载
    Spring Boot在启动时,会扫描项目依赖中的jar包,查找META-INF/spring.factories文件。
    这个文件中列出了所有自动配置类的全限定类名。
    Spring Boot通过SpringFactoriesLoader读取这些类名,并加载相应的自动配置类。
  3. 条件化装配
    自动配置类中的配置并不是无条件加载的,而是根据条件注解(如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnProperty等)来决定是否加载某个配置。
    @ConditionalOnClass:当类路径中存在指定的类时,才加载配置。
    @ConditionalOnBean:当容器中存在指定类型的Bean时,才加载配置。
    @ConditionalOnProperty:当配置文件中存在指定的属性且值满足条件时,才加载配置。
  4. 配置类的注册与绑定
    生效的配置类会将带有@Bean注解的组件注册到Spring的IOC容器中。
    同时,配置类也可以进行配置绑定,通过@ConfigurationProperties注解将配置文件中的值绑定到对应的Properties类中。
  5. 配置文件的解析与覆盖
    Spring Boot默认使用application.properties或application.yml作为全局配置文件。
    开发者可以在这些文件中定义或覆盖自动配置的默认值,以满足特定的需求。
  6. 自定义配置与排除
    开发者可以创建自定义的自动配置类,通过@Configuration和条件注解来定义自己的自动装配规则。
    如果需要排除某个自动装配的组件或配置,可以使用@SpringBootApplication注解的exclude属性,或在配置文件中通过spring.autoconfigure.exclude属性来实现。
  7. SpringApplication.run方法执行
    当执行SpringApplication.run(XXX.class, args)方法时,Spring Boot会启动并执行上述流程。
    这个方法会判断应用的类型(普通项目还是Web项目),加载初始化器和应用程序监听器,推动并设置main方法的定义类,最终启动Spring容器。
    综上所述,Spring Boot的自动装配流程是一个基于条件化装配的自动化过程,它通过注解、配置文件和条件注解来实现应用程序的自动配置和组件的自动注册。这个过程极大地简化了Spring应用程序的搭建和配置工作,让开发者能够更专注于业务逻辑的开发。

SpringBoot如何配置跨域

在Spring Boot中实现跨域操作,主要是通过配置CORS(跨源资源共享)来实现的。CORS是一个W3C标准,它允许你指定哪些动态资源允许跨域访问。Spring Boot通过提供@CrossOrigin注解和全局CORS配置两种方式来实现跨域请求的支持。

  1. 使用@CrossOrigin注解
    @CrossOrigin注解可以添加到控制器(Controller)类上或特定的方法上,以允许跨域请求。这个注解可以指定允许的源(origins)、HTTP方法、头部(headers)等。

  2. 全局CORS配置
    如果应用中有多个控制器或方法需要跨域访问,那么使用全局CORS配置会更加方便。Spring Boot允许你通过添加一个配置类并实现WebMvcConfigurer接口来自定义CORS策略。

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

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "http://example.com") // 只允许来自http://example.com的跨域请求
public class MyController {

@GetMapping("/greeting")
public String greeting() {
return "Hello, World!";
}
}

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有路径
.allowedOrigins("http://example.com") // 允许的源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的HTTP方法
.allowedHeaders("*") // 允许的头信息
.allowCredentials(true) // 是否允许发送Cookie
.maxAge(3600); // 缓存预检请求的结果的时间,单位为秒
}
}

SpringClod

其他