Spring
Spring介绍
Spring是一个IOC(DI)和AOP框架
Spring的优良特性
·非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
·依赖注入:DI是控制反转(IOC)最经典的实现
·面向切面编程:AOP
·组件化:Spring通过众多简单的组件配置组合成一个复杂应用
·一站化:Spring提供了一系列框架,解决了应用开发中的众多问题
Spring模块划分
Spring-IOC容器
组件和容器
·组件:具有一定功能的对象。
·容器:管理组件(创建,获取,保存,销毁)
可以将组件和容器的关系比喻成“房间”与“家具”的关系:
组件:可以看作是各种“家具”,比如桌子、椅子、灯、书架等。它们是构成界面或应用功能的基本元素,完成特定的功能任务,比如显示文本、输入数据等。
容器:则是“房间”或“空间”,用于装下各种家具。容器负责管理组件的布局、位置和相互之间的关系,同时也可能控制组件的生命周期和事件传递。
常见的容器:Servlet 容器(如 Tomcat、Jetty):用于管理和运行 Java Web 应用,处理 HTTP 请求和响应。
Docker 容器:用于封装应用及其依赖的环境,确保应用能够在不同平台上运行一致。
IOC和DI
IOC:Inversion of Control(控制反转)
控制:资源的控制权(资源的创建、获取、销毁等)
反转:传统上,对象的创建和依赖的管理是由对象本身控制的,而在控制反转的设计中,这种控制权被“反转”到了外部容器或框架中
DI:Dependency Injection(依赖注入)
依赖:组件的依赖关系,如 NewsController 依赖 NewsServices
注入:通过setter方法、构造器、等方式自动的注入(赋值)
简单示例
假设我们有一个 UserService 类,它依赖于 UserRepository:
1 | //无控制反转 |
注册组建的各种方式
1 | public static void main(String[] args) { |
1.通过@Bean
多应用在方法上
Spring 4后推荐我们使用Java Config的方式来注册组件。**@Configuration**是 Spring 框架中的一个注解,用于标记一个类为 配置类,相当于 Spring XML 配置文件的替代方式。被 @Configuration 标记的类可以用来定义 Bean,并将它们注册到 Spring 的 IoC 容器中。
告诉 Spring 该类包含了 一个或多个 @Bean 方法,这些方法会生成所需的 Bean,并注册到容器中以便在整个应用中共享
1 |
|
2.通过@Component
多应用在类上
MVC分层多用@Controller,@Service,@Component
1 | //@Service源代码 |
1 |
|
3.使用@ComponentScan扫描
1 |
|
excludeFilters来排除一些组件的扫描:
1 |
|
includeFilters的作用和excludeFilters相反,其指定的是哪些组件需要被扫描:
1 |
上面配置了只将Service纳入IOC容器,并且需要用useDefaultFilters = false来关闭Spring默认的扫描策略才能让我们的配置生效(Spring Boot入口类的@SpringBootApplication注解包含了一些默认的扫描策略)。
4.@Import导入
可以使用@Import来快速地往IOC容器中添加组件。
创建一个新的类Hello:
1 | public class Hello { |
然后在配置类中导入这个组件:
1 |
|
5.组件作用域@Scope
默认情况下,在Spring的IOC容器中每个组件都是单例的,即无论在任何地方注入多少次,这些对象都是同一个
在Spring中我们可以使用@Scope注解来改变组件的作用域:
1 |
|
有如下几个选项:
singleton:单实例(默认),在Spring IOC容器启动的时候会调用方法创建对象然后纳入到IOC容器中,以后每次获取都是直接从IOC容器中获取(map.get());容器启动的时候,会创建单实例组件的对象,容器启动完成之前就会创建
prototype:多实例,IOC容器启动的时候并不会去创建对象,而是在每次获取的时候才会去调用方法创建对象;容器启动的时候,不会创建非单实例组件的对象,什么时候获取,什么时候创建
request:一个请求对应一个实例;
session:同一个session对应一个实例。
我们可以使用多实例测试一下,在配置文件中给User添加Scope注解。
1 |
|
6.懒加载@Lazy
容器启动之前不会创建懒加载组件的对象
什么时候获取,什么时候创建
懒加载是针对单例模式而言的,正如前面所说,IOC容器中的组件默认是单例的,容器启动的时候会调用方法创建对象然后纳入到IOC容器中。
@Configuration
public class WebConfig {
1 |
|
注入组件的各种方式
1. 构造器注入
构造器注入是最常见和推荐的依赖注入方式之一。通过构造器注入,我们可以在创建一个Bean实例时,将其所需的依赖项作为构造函数的参数进行传递。Spring容器会负责解析依赖关系并创建Bean的实例。示例代码如下:
1 | public class ExampleService { |
2. Setter方法注入
Setter方法注入是另一种常用的依赖注入方式。通过Setter方法注入,我们在Bean的类中定义对应的Setter方法,Spring容器会通过调用这些Setter方法来设置依赖项。示例代码如下:
1 | public class ExampleService { |
3. 接口注入
除了构造器注入和Setter方法注入,Spring还支持通过接口注入来实现依赖注入。这种方式要求目标Bean实现特定的接口,并通过接口方法来设置依赖项。示例代码如下:
1 | public interface DependencyInjection { |
4.注解注入
Spring框架提供了多个注解用于依赖注入,简化了配置和代码的编写。常用的注解包括:
@Autowired:自动装配依赖项。
@Qualifier:在存在多个候选Bean时,指定要注入的具体Bean。
@Resource:指定要注入的Bean,并可以通过名称或类型进行查找。
@Value:注入简单的值,如基本类型、字符串等。
@Inject:与@Autowired类似,用于依赖注入。
示例代码如下:
1 | public class ExampleService { |
@Autowired 和 @Resource 有什么区别?
1.来源不同
@Autowired 和 @Resource 来自不同的“父类”,其中 @Autowired 是 Spring 定义的注解,而 @Resource 是 Java 定义的注解,它来自于 JSR-250(Java 250 规范提案)
2.依赖查找顺序不同
依赖注入的功能,是通过先在 Spring IoC 容器中查找对象,再将对象注入引入到当前类中。而查找有分为两种实现:按名称(byName)查找或按类型(byType)查找,其中 @Autowired 和 @Resource 都是既使用了名称查找又使用了类型查找,但二者进行查找的顺序却截然相反。
2.1 @Autowired 查找顺序
@Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找,它的具体查找流程如下:
2.2 @Resource 查找顺序
@Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找,它的具体流程如下图所示:
2.3 查找顺序小结
由上面的分析可以得出:
@Autowired 先根据类型(byType)查找,如果存在多个(Bean)再根据名称(byName)进行查找;
@Resource 先根据名称(byName)查找,如果(根据名称)查找不到,再根据类型(byType)进行查找。
3.支持的参数不同
@Autowired 和 @Resource 在使用时都可以设置参数,比如给 @Resource 注解设置 name 和 type 参数,实现代码如下:
1 |
|
但二者支持的参数以及参数的个数完全不同,其中 @Autowired 只支持设置一个 required 的参数,而 @Resource 支持 7 个参数
4.依赖注入的支持不同
其中, @Autowired 支持属性注入、构造方法注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入
5.编译器提示不同
当使用 IDEA 专业版在编写依赖注入的代码时,如果注入的是 Mapper 对象,那么使用 @Autowired 编译器会提示报错信息
@Bean的生命周期
Spring-AOP
什么是AOP
**横切关注点 (Cross-cutting Concerns)**:
- 指的是那些遍布于应用程序中多个模块的功能,但这些功能本身又与核心业务逻辑没有直接关系。
- 例如:日志、安全(权限)、事务、缓存、性能监控等。它们“横切”了许多不同的业务模块。
**切面 (Aspect)**:
- 横切关注点的模块化单元。它封装了通知(Advice)和切入点(Pointcut)。
- 在 Spring AOP 中,通常是一个带有
@Aspect
注解的类。
**通知 (Advice)**:
- 切面在特定连接点执行的动作。它定义了“什么时候”以及“做什么”。
- 类型:
@Before
(前置通知):在目标方法执行之前执行。@AfterReturning
(后置返回通知):在目标方法成功返回之后执行。@AfterThrowing
(异常通知):在目标方法抛出异常之后执行。@After
(最终通知):在目标方法执行结束后(无论是否抛出异常)执行。@Around
(环绕通知):包围目标方法的执行。可以在目标方法调用之前和之后执行自定义行为,是最强大和灵活的通知类型。
**连接点 (Join Point)**:
- 程序执行过程中可以插入切面的特定点。
- 例如:方法的调用、方法的执行、字段的设置/获取、异常的处理等。
- 在 Spring AOP 中,连接点通常指的就是方法的执行。
**切入点 (Pointcut)**:
- 一个表达式,定义了哪些连接点会触发通知的执行。它决定了“在哪里”切入。
- 通常使用
execution()
表达式来匹配方法签名。 - 例如:
execution(* com.example.service.*.*(..))
表示匹配com.example.service
包下所有类的所有方法。
**目标对象 (Target Object)**:
- 被一个或多个切面所通知的对象。也被称为被代理对象。
**织入 (Weaving)**:
- 将切面应用到目标对象,从而创建新的代理对象的过程。
- 织入时机:
- 编译时织入:需要特殊的编译器。
- 类加载时织入:需要特殊的类加载器。
- 运行时织入 (Spring AOP 主要方式):在应用程序运行时,通过动态代理(JDK 动态代理或 CGLIB 代理)来创建代理对象。
切入点表达式
单切面执行顺序
1 |
|

多切面执行顺序
•按照切面的优先级,优先级越高,越先执行,越是代理的最外层
按首字母排序
自定义:加上@Order() 数字越小,优先级最高
1 |
|

- 环绕通知固定写法如下
- object:返回值
- ProceedingJoinPoint:可以继续推进的切点
1 |
|

Spring中事务管理
在 Spring 中,事务管理是基于代理模式的,而代理的生效依赖于对代理对象的调用。当你在 同一个类 中通过 非事务方法 A 调用 事务方法 B
时,事务可能会失效。
原因是 Spring 的事务管理是通过 AOP 代理的,而 内部方法调用(即同一类中的方法调用)不会经过 AOP 代理,从而导致事务控制不生效。
为什么事务失效?
在 Spring 中,事务管理通常是通过@Transactional
注解和 Spring AOP 实现的。Spring 使用代理模式来控制事务,这意味着事务的管理实际上是由代理对象控制的,而不是直接在方法中执行的。当你调用类中的一个带有 @Transactional
注解的方法时,Spring 会创建一个代理对象,并通过代理对象来管理事务的开始、提交和回滚。
但是,如果你在 同一个类 中通过直接调用非事务方法来调用带有事务注解的方法,Spring 会认为这是一个普通的本地方法调用,而不会触发代理。因为事务管理是依赖于代理的,所以事务不会生效。
1 |
|
解决方法
1.使用AppContext.currentProxy()
为了解决 同类方法调用事务失效 的问题,Spring 提供了 AopContext.currentProxy() 来确保通过代理对象进行方法调用。通过 AopContext.currentProxy() 获取到当前的代理对象,并通过代理对象来调用事务方法,可以确保事务管理生效。
1 |
|
- 注意:启动类要加上
@EnableAspectJAutoProxy
1 |
|
2.直接注入实现类
1 | public interface UserService{ |
为什么proxy是代理对象?
Spring 容器会检测到 UserServiceImpl 中存在@Transactional
注解,生成一个 UserService 的代理对象(如果目标类实现了接口,Spring 会使用 JDK 动态代理)。
当 @Autowired
注入时,Spring 将代理对象注入到 proxy 中,而不是原始的实现类实例。
必须通过接口注入代理对象,而不是直接注入实现类。如果你直接写 @Autowired private UserServiceImpl proxy;
Spring 注入的会是原始类,而非代理类,事务不会生效。
