当前位置: 首页 > news >正文

Spring IOC 与 Spring AOP

一、 概述

IOC:Inversion of Control,控制反转,是一种对象创建与获取的设计思想。依赖注入(DI,Dependency Injection)是实现这种思想的常见方式。传统开发中对象通过 new 创建;采用 IoC 时,对象实例化与依赖关系由 IoC 容器负责,从而显著降低模块间耦合。

AOP:Aspect-Oriented Programming,面向切面编程,用于把那些与业务无关但被多个模块共同调用的逻辑抽离出来,以减少重复代码并降低耦合。Spring AOP 基于动态代理:当被代理对象实现了接口时优先使用 JDK Proxy;若无接口,则使用 CGLIB 为目标类生成子类代理。

在 Spring 中,IoC 和 AOP 常结合使用:IoC 管理对象与依赖,AOP 将横切关注点(例如事务、日志)切入到业务逻辑中,使代码更模块化、更易维护。例如通过 IoC 管理 Service/DAO 的依赖,并在 Service 层用 AOP 实现事务和日志。

二、Spring AOP

1. 核心概念

Aspect(切面):封装横切逻辑的模块,是 JoinPoint、Advice 和 Pointcut 的集合概念。

JoinPoint(连接点):程序执行中的一个点(在 Spring AOP 中通常是方法调用)。

Advice(通知):切面中要执行的逻辑,例如 before、after、around 等。通知常被实现为拦截器链,围绕 JoinPoint 执行。

Pointcut(切入点):用于选择哪些 JoinPoint 应该被 Advice 拦截。

Introduction(引入):允许在运行时把额外接口声明到目标对象上(例如动态为目标对象添加某个接口的实现)。

Weaving(织入):把切面逻辑插入到目标方法的过程。

AOP proxy(代理):在 Spring AOP 中由 JDK 动态代理或 CGLIB 生成的代理对象,负责在方法调用时触发 Advice。

Target object(目标对象):被代理的原始业务对象。

2.核心注解

@Aspect:用于定义切面类。

@Pointcut:声明切入点表达式。

@Before:在方法执行之前执行通知。

@After:在方法执行之后执行通知。

@Around:在方法执行前后都执行通知。

@AfterReturning:在方法执行后返回结果后执行通知。

@AfterThrowing:在方法抛出异常后执行通知。

3. 代码示例

目标接口与实现:

package com.example.service;
public interface UserService {void create(String name);
}package com.example.service;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {@Overridepublic void create(String name) {System.out.println("创建用户: " + name);}
}

切面示例:

package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class SimpleAspect {@Pointcut("execution(* com.example.service.UserService.*(..))")public void userServiceMethods() {}@Before("userServiceMethods()")public void before(JoinPoint jp) {System.out.println("[Before] " + jp.getSignature());}@Around("userServiceMethods()")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("[Around] start");Object ret = pjp.proceed(); // 执行目标方法System.out.println("[Around] end");return ret;}
}

运行与输出:

package com.example;
import org.springframework.context.annotation.*;@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig { }package com.example;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.UserService;public class Main {public static void main(String[] args) {var ctx = new AnnotationConfigApplicationContext(AppConfig.class);UserService svc = ctx.getBean(UserService.class);System.out.println("bean class = " + svc.getClass().getName());svc.create("alice");ctx.close();}
}/* 输出
bean class = com.sun.proxy.$Proxy...
[Before] void com.example.service.UserService.create(String)
[Around] start
创建用户: alice
[Around] end
*/
4. 原理

Spring AOP 使用运行时代理将 Advice 织入目标对象:

a. 容器启动时扫描 @Aspect 并解析 Pointcut。

b. 为匹配的目标 Bean 创建代理(JDK Proxy 或 CGLIB)。

c. 客户端调用代理 -> 触发 Advice 链 -> 最终调用目标方法。

Spring AOP 不修改目标类字节码(除非使用 AspectJ 的编译期/类加载期织入),而是通过代理间接实现增强。

5.动态代理和静态代理的区别

代理是一种常见设计模式,用来为目标对象提供一个代替对象以控制对目标的访问,从而解耦客户端与目标对象。代理按实现方式可以分为静态代理和动态代理,两者的区别与特点如下:

a. 动态代理

在运行时动态生成代理类并实例化,不需要为每个目标手写代理类,适用于统一处理大量类的增强逻辑(如日志、事务、缓存等)。

Java 动态代理主要分为两种类型:​

基于接口的代理(JDK 动态代理):这种类型的代理对象要求目标对象实现至少一个接口。通过 java.lang.reflect.Proxy 在运行时生成一个实现指定接口的代理类;所有方法调用会被转发到实现了 java.lang.reflect.InvocationHandler 的 handler 的 invoke 方法中处理。

基于类的代理(CGLIB 动态代理):CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB 代理通过重写方法来插入增强逻辑,最终返回生成的子类实例作为代理对象。

import java.lang.reflect.*;// InvocationHandler
class LogHandler implements InvocationHandler {private final Object target;public LogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[日志] 方法调用前");Object result = method.invoke(target, args);System.out.println("[日志] 方法调用后");return result;}
}// 使用动态代理
public class DynamicProxyDemo {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),new Class[]{UserService.class},new LogHandler(target));proxy.createUser("Bob");}
}

b. 静态代理

由程序员创建或者由特定的工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类。

// 静态代理类
class UserServiceProxy implements UserService {private final UserService target;public UserServiceProxy(UserService target) {this.target = target;}@Overridepublic void createUser(String name) {System.out.println("[日志] 方法调用前");target.createUser(name); // 调用目标对象方法System.out.println("[日志] 方法调用后");}
}// 使用静态代理
public class StaticProxyDemo {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = new UserServiceProxy(target);proxy.createUser("Alice");}
}
6. Spring AOP 和 AspectJ AOP的区别

Spring AOP:基于代理、运行时织入,轻量且易用,满足大部分企业场景。

AspectJ:功能更强,支持编译期/类加载期/运行期织入,可拦截方法之外的连接点(如字段访问、构造器等),在性能和能力上优于单纯的代理机制,但配置复杂度更高。

当切面非常多或需要更低开销时,可考虑使用 AspectJ。

三、Spring IOC

1. 核心概念

在传统的 Java SE 程序设计中,对象通常由程序通过 new 显式创建,类自身主动控制其依赖的构造与生命周期——也就是程序负责创建、组装和销毁依赖对象。

而在 Spring 的编程范式中,这一控制权被反转到 IoC 容器:容器负责对象的创建、依赖注入、初始化回调和销毁。具体来说:

创建对象:不再由业务类直接 new 实例,Spring 容器负责根据配置或注解实例化 Bean。

注入与初始化:对象之间的依赖由容器自动装配(构造器注入、Setter 注入或字段注入),容器还负责调用初始化回调(例如 @PostConstruct 或 InitializingBean)。

销毁与释放:容器在适当时机负责销毁 Bean 并触发销毁回调(例如 @PreDestroy 或 DisposableBean),释放资源,便于 JVM 回收内存。

控制权反转:由于容器掌握了创建与管理生命周期的职责,应用代码从“主动控制者”变为“被动使用者”,对象的管理不再由业务类直接操控,控制权交由 Spring 管理。

这一设计带来的好处包括降低模块耦合、便于替换实现、提高可测试性(可以注入 mock)、以及统一管理资源初始化与释放,从而提升系统的可维护性与健壮性。

2. 依赖注入

依赖注入是 IoC 的常见实现方式,主要注入方式包含:构造器注入、Setter 注入和字段注入(如 @Autowired)

DI 使得 Bean 之间通过容器进行耦合组装,便于替换实现、单元测试和代码解耦。

3. 设计一个Spring IOC,需要考虑哪些方面

Bean 的创建与销毁生命周期管理(初始化、销毁回调)。

依赖注入实现:属性注入、构造器注入、方法注入的解析与注入机制。

Bean 的作用域支持:singleton、prototype、request、session 等。

AOP 支持:如何在创建或获取 Bean 时生成代理并织入切面。

异常处理:Bean 创建或依赖注入失败时的回滚与错误信息。

配置加载:支持 XML、注解或 Java 配置方式。

工厂抽象支持:例如实现类似 FactoryBean 的能力以支持复杂对象创建逻辑。


文章转载自:

http://gyIOIDw8.hnsdr.cn
http://WXoA7DE2.hnsdr.cn
http://dbQIj3ZD.hnsdr.cn
http://ZBldPlEd.hnsdr.cn
http://sDtxr90c.hnsdr.cn
http://5jaMJKK1.hnsdr.cn
http://4pvMgAea.hnsdr.cn
http://ncdtehyh.hnsdr.cn
http://1VZuzZO4.hnsdr.cn
http://tPpNhB1W.hnsdr.cn
http://XhwhVSTO.hnsdr.cn
http://4yLU0iZ0.hnsdr.cn
http://KVlItaQa.hnsdr.cn
http://W6uvYfzz.hnsdr.cn
http://OIUKNtmd.hnsdr.cn
http://M2PJziGp.hnsdr.cn
http://STvODpxn.hnsdr.cn
http://d7Hhc90p.hnsdr.cn
http://tKGQoODg.hnsdr.cn
http://gRTZzevT.hnsdr.cn
http://Jm36U8DO.hnsdr.cn
http://Gi5bgrvP.hnsdr.cn
http://sVYsjmwh.hnsdr.cn
http://voRcac7c.hnsdr.cn
http://EuIy3l8d.hnsdr.cn
http://mlwXYDRQ.hnsdr.cn
http://1QKQxVSK.hnsdr.cn
http://ZE5jsz0B.hnsdr.cn
http://Bfl5W7pQ.hnsdr.cn
http://qjg9TwAB.hnsdr.cn
http://www.dtcms.com/a/387143.html

相关文章:

  • 【FreeRTOS】队列API全家桶
  • 【Docker项目实战】使用Docker部署Cup容器镜像更新工具
  • (笔记)内存文件映射mmap
  • springboot传输文件,下载文件
  • 基于51单片机的出租车计价器霍尔测速设计
  • 【笔记】Agent应用开发与落地全景
  • C++ STL底层原理系列学习路线规划
  • LAN口和WAN口
  • Dify + Bright Data MCP:从实时影音数据到可落地的智能体生产线
  • 数据库--使用DQL命令查询数据(二)
  • 【FreeRTOS】创建一个任务的详细流程
  • CKA06--storageclass
  • 宝塔安装以及无法打开时的CA证书配置全攻略
  • wend看源码-Open_Deep_Research(LangChain)
  • 摄像头文档识别与透视变化技术和背景建模技术(追踪)
  • 123、【OS】【Nuttx】【周边】效果呈现方案解析:find 格式化打印
  • DC-4靶机渗透
  • 大模型在线对话平台集锦(持续更新ing...)
  • JavaScript中 i++ 与 ++i
  • 【cookie】JavaScript操作增删改查
  • OC-AFNetworking
  • Java全栈学习笔记35
  • kylin v10 系统 上 qt 5.15.17版本构建及使用
  • Linux:基于环形队列的生产者消费模型
  • Nginx 配置 Vue 项目 Hash/History 模式路由跳转错误的解决方案
  • Linux Makefile与进度条
  • 硬件驱动——I.MX6ULL裸机启动(3)(按键设置及中断设置
  • 深度学习基本模块:RNN 循环神经网络
  • 【深度学习】PixelShuffle处理操作
  • 10.1 - 遗传算法(旅行商问题C#求解)