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

讲一讲Spring核心三大组件IOC、Bean、AOP

Spring 的三大核心组件是 IOC(控制反转)、Bean 和 AOP(面向切面编程),三者共同构成了 Spring 框架的核心基础

Bean

用一个 “学生管理系统” 举例子,明确告诉你哪些是 Bean,以及它们在代码中长什么样,看完你就彻底明白了。

先明确:什么是 Bean?

Bean 就是 “被 Spring 容器管理的 Java 对象”
简单说:你写的类,只要加了特定注解(比如@Service@Controller),或者在配置文件里声明过,Spring 就会自动创建这个类的对象,并且负责它的 “生死”(创建、初始化、销毁),这个对象就是 Bean。

学生管理系统中的 Bean 实例

假设系统有 3 个核心功能类,我们逐个看哪些是 Bean,为什么:

1. 控制器类(接收前端请求)
// 加了@Controller注解 → 这是一个Bean
@Controller
public class StudentController {// 这里会用到StudentService(也是个Bean)@Autowiredprivate StudentService studentService;// 处理“查询学生”的请求public Student queryStudent(Long id) {return studentService.getStudentById(id);}
}


为什么是 Bean?
因为加了@Controller注解,告诉 Spring:“这个类是处理用户请求的控制器,你帮我管理它”。Spring 会自动创建StudentController对象,放进容器里。

2. 服务类(处理业务逻辑)
// 加了@Service注解 → 这是一个Bean
@Service
public class StudentService {// 这里会用到StudentDao(也是个Bean)@Autowiredprivate StudentDao studentDao;// 业务逻辑:查询学生public Student getStudentById(Long id) {// 可能还有一些校验、计算等逻辑return studentDao.selectById(id);}
}


为什么是 Bean?
加了@Service注解,告诉 Spring:“这是处理业务逻辑的服务类,归你管”。Spring 会创建StudentService对象,放进容器,并且当StudentController需要它时,自动 “送过去”(依赖注入)。

3. 数据访问类(操作数据库)
// 加了@Repository注解 → 这是一个Bean
@Repository
public class StudentDao {// 操作数据库的方法(比如查数据库)public Student selectById(Long id) {// 实际中会用MyBatis/MySQL等查数据return new Student(id, "张三", 20);}
}


为什么是 Bean?
加了@Repository注解,告诉 Spring:“这是访问数据库的类,你负责管理”。Spring 会创建StudentDao对象,当StudentService需要时,自动注入进去。

4. 实体类(不是 Bean)
// 注意:这个类没有加任何Spring注解 → 不是Bean
public class Student {private Long id;private String name;private Integer age;// 构造方法、getter/setter...
}


为什么不是 Bean?
Student是用来存数据的实体类,我们需要根据业务动态创建(比如从数据库查一条记录就 new 一个),不需要 Spring 容器管理它的生命周期,所以不用加注解,也就不是 Bean。

5. 配置类(特殊的 Bean)
// 加了@Configuration注解 → 这是一个Bean(配置型Bean)
@Configuration
public class AppConfig {// 用@Bean注解手动定义一个Bean@Beanpublic Logger logger() {return new Logger(); // 创建一个日志工具对象,交给Spring管理}
}


为什么是 Bean?
@Configuration标记这是配置类,本身也是 Bean。里面的logger()方法加了@Bean,告诉 Spring:“我手动创建一个 Logger 对象,你帮我管起来,其他地方要用时直接拿”。

总结:哪些是 Bean?

看一个类 / 对象是不是 Bean,就看是否被 Spring 容器管理,判断方式:

  1. 类上有@Component@Service@Controller@Repository@Configuration这些注解
  2. 方法上有@Bean注解(通常在配置类里)
  3. 在 XML 配置文件里用<bean>标签声明过(现在很少用了)


像上面例子中的StudentControllerStudentServiceStudentDaoAppConfigLogger都是 Bean,而Student(实体类)不是 Bean。
Bean 的核心作用就是:让 Spring 帮你创建和管理对象,你不用手动 new,也不用关心它们怎么配合,直接用就行

IoC / DI 是什么?

1. 什么是 IoC(控制反转)?

控制权交给 Spring 容器,由容器来负责对象的创建和管理。

举个形象例子:

  • **传统方式:**我们手动写 UserService userService = new UserServiceImpl();
  • **IoC方式:**我们在配置文件或注解中声明,Spring 容器帮你 new 对象、注入依赖

👉 控制权从“开发者手中”转移给了“Spring 容器”,这就是控制反转(Inversion of Control)。


2. 什么是 DI(依赖注入)?

Spring 容器在创建对象时,把这个对象“依赖”的东西自动塞进去。

比如你在一个类中依赖了 DAO 层:

@Service
public class UserService {@Autowiredprivate UserDao userDao;
}
  • @Autowired 表示让 Spring 自动注入 UserDao
  • 你不需要自己去 new UserDaoImpl()

💡 IoC 是思想,DI 是实现方式。
可以说:“通过依赖注入,实现了控制反转。”


IoC / DI 过程图解:

传统方式:
UserService --> 手动 new UserDaoSpring方式(IoC):
Spring容器:1. new UserDaoImpl()2. new UserService()3. 把 UserDaoImpl 注入到 UserService 中

AOP

先明确:什么是 AOP?

AOP(Aspect-Oriented Programming)就是 “在不修改原有代码的情况下,给程序动态添加功能”
比如:你写了一个 “查询学生” 的核心功能,现在想在它执行前加个日志、执行后加个权限校验,又不想改原来的代码 —— 这时候就可以用 AOP。

学生管理系统中的 AOP 实例

假设我们已经有了核心业务类StudentService(里面有个getStudentById方法查学生),现在要给它加一些 “额外功能”,看看 AOP 怎么实现。

1. 先看没有 AOP 的情况(痛点)

如果不用 AOP,要加日志和权限校验,只能硬塞进核心代码里:

@Service
public class StudentService {public Student getStudentById(Long id) {// 1. 加日志(非核心功能)System.out.println("准备查询学生,id=" + id);// 2. 加权限校验(非核心功能)if (!checkPermission()) {throw new RuntimeException("没有权限!");}// 3. 核心业务逻辑(查学生)Student student = studentDao.selectById(id);// 4. 加日志(非核心功能)System.out.println("查询学生完成,结果:" + student);return student;}
}


问题来了

  • 核心业务代码(查学生)和非核心代码(日志、权限)混在一起,乱得像一锅粥。
  • 如果其他方法(比如addStudent)也需要加日志,就得复制粘贴,改起来要改 N 个地方。
2. 用 AOP 解决(清爽!)

AOP 的思路是:把非核心功能抽出来单独写,然后 “贴” 到核心功能上,不用改核心代码。

步骤 1:定义 “额外功能”(叫 “通知”)

把日志、权限校验这些非核心功能抽出来,写成一个独立的类(叫 “切面类”):

// 标记这是一个切面类(放额外功能的地方)
@Aspect
@Component // 注意:切面类本身也要是Bean,才能被Spring管理
public class LogAndPermissionAspect {// 定义“执行前的额外功能”(前置通知)// @Before("execution(* com.service.StudentService.getStudentById(..))") // 上面的表达式意思是:对StudentService的getStudentById方法生效@Before("execution(* com.service.StudentService.getStudentById(..))")public void beforeQuery(JoinPoint joinPoint) {//  joinPoint可以获取到目标方法的参数(比如这里的id)Long id = (Long) joinPoint.getArgs()[0];System.out.println("AOP前置通知:准备查询学生,id=" + id);}// 定义“执行后的额外功能”(后置通知)@AfterReturning(pointcut = "execution(* com.service.StudentService.getStudentById(..))",returning = "result" // 获取目标方法的返回值)public void afterQuery(Object result) {System.out.println("AOP后置通知:查询学生完成,结果:" + result);}// 定义“权限校验”(也可以放在前置通知里,这里单独举例)@Before("execution(* com.service.StudentService.getStudentById(..))")public void checkPermission() {if (!hasPermission()) { // 假设hasPermission是判断权限的方法throw new RuntimeException("AOP拦截:没有权限!");}}
}
步骤 2:告诉 Spring “要切到哪里”(叫 “切入点”)

上面代码里的execution(* com.service.StudentService.getStudentById(..))就是 “切入点表达式”,意思是:
“我要把这些额外功能,贴到StudentService类的getStudentById方法上”

步骤 3:核心业务代码保持纯净

原来的StudentService不用改,干干净净只有核心逻辑:

@Service
public class StudentService {public Student getStudentById(Long id) {// 只有核心业务逻辑,其他啥都不用加!return studentDao.selectById(id);}
}
3. 最终效果

当调用getStudentById方法时,Spring 会自动按顺序执行:

  1. AOP 的前置通知(日志 + 权限校验)→ 2. 核心业务方法(查学生)→ 3. AOP 的后置通知(日志)。


好处

  • 核心代码和额外功能彻底分离,清爽!
  • 如果要改日志格式,只改LogAndPermissionAspect一个地方就行。
  • 想给其他方法加功能,只需改切入点表达式(比如execution(* com.service.StudentService.*(..))表示对StudentService的所有方法生效)。

AOP 的核心概念(用例子对应)

  • 切面(Aspect):就是LogAndPermissionAspect这个类,里面放了所有要加的额外功能。
  • 通知(Advice):就是类里的beforeQueryafterQuery这些方法,具体的额外功能(前置、后置、异常时执行等)。
  • 切入点(Pointcut):就是execution(...)这个表达式,规定了 “额外功能要贴到哪些方法上”。
  • 目标对象(Target):被加额外功能的对象,这里就是StudentService的实例(Bean)。
  • 连接点(JoinPoint):可能被 “贴” 额外功能的地方(比如方法执行前、后,异常时等),getStudentById方法的执行前就是一个连接点。

ok那AOP 到底解决什么问题?

简单说,AOP 就是 “给 existing 代码打补丁”,而且这个补丁可以灵活地贴到任何地方,还不破坏原来的代码。
最常用的场景:日志记录、权限校验、事务管理、异常处理等 —— 这些功能几乎所有方法都需要,但又不想和核心业务混在一起,AOP 就是干这个的。

http://www.lryc.cn/news/608044.html

相关文章:

  • 我的世界模组开发教程——物品item(1)
  • Vuex 4.0:Vue.js 应用的状态管理新篇章
  • 深度学习核心:神经网络-激活函数 - 原理、实现及在医学影像领域的应用
  • K8S部署ELK(一):部署Filebeat日志收集器
  • SCI 绘图实用 RGB 配色方案:多组比较
  • [Windows] 微软.Net运行库离线合集包 Microsoft .Net Packages AIO v13.05.25
  • Vue3+ts自定义指令
  • 从毫秒到真义:构建工业级RAG系统的向量检索优化指南
  • 入门MicroPython+ESP32:ESP32烧录MicroPython固件
  • Python进阶(5):类与继承
  • Unity_数据持久化_XML存储相关
  • 探索:Uniapp 安卓热更新
  • 智能合约漏洞导致的损失,法律责任应如何分配
  • 医疗后台管理系统开发实践
  • 分类任务当中常见指标 F1分数、recall、准确率分别是什么含义
  • 通过解决docker network connect实现同一个宿主机不同网络的容器间通信
  • 【stm32】点灯及蜂鸣器
  • 深度学习·mmsegmentation基础教程
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第三天(JavaScript)
  • ospf作业
  • 关于Web前端安全防御之点击劫持的原理及防御措施
  • winscp 连openwrt 返回127错误码
  • Java设计模式之行为型模式(解释器模式)实现方式举例说明
  • 大文件上传:自定义协议
  • 进程 Vs 线程
  • 电路原理图绘制专业实战教程1
  • 深入 Go 底层原理(十五):cgo 的工作机制与性能开销
  • Minimizing Coins(Dynamic Programming)
  • OAuth 2.0 的安全升级版授权协议 OAuth 2.1 详解
  • 【转】大模型安全治理的现状与展望