讲一讲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 容器管理,判断方式:
- 类上有
@Component
、@Service
、@Controller
、@Repository
、@Configuration
这些注解 - 方法上有
@Bean
注解(通常在配置类里) - 在 XML 配置文件里用
<bean>
标签声明过(现在很少用了)
像上面例子中的StudentController
、StudentService
、StudentDao
、AppConfig
、Logger
都是 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 会自动按顺序执行:
- AOP 的前置通知(日志 + 权限校验)→ 2. 核心业务方法(查学生)→ 3. AOP 的后置通知(日志)。
好处:
- 核心代码和额外功能彻底分离,清爽!
- 如果要改日志格式,只改
LogAndPermissionAspect
一个地方就行。 - 想给其他方法加功能,只需改切入点表达式(比如
execution(* com.service.StudentService.*(..))
表示对StudentService
的所有方法生效)。
AOP 的核心概念(用例子对应)
- 切面(Aspect):就是
LogAndPermissionAspect
这个类,里面放了所有要加的额外功能。 - 通知(Advice):就是类里的
beforeQuery
、afterQuery
这些方法,具体的额外功能(前置、后置、异常时执行等)。 - 切入点(Pointcut):就是
execution(...)
这个表达式,规定了 “额外功能要贴到哪些方法上”。 - 目标对象(Target):被加额外功能的对象,这里就是
StudentService
的实例(Bean)。 - 连接点(JoinPoint):可能被 “贴” 额外功能的地方(比如方法执行前、后,异常时等),
getStudentById
方法的执行前就是一个连接点。
ok那AOP 到底解决什么问题?
简单说,AOP 就是 “给 existing 代码打补丁”,而且这个补丁可以灵活地贴到任何地方,还不破坏原来的代码。
最常用的场景:日志记录、权限校验、事务管理、异常处理等 —— 这些功能几乎所有方法都需要,但又不想和核心业务混在一起,AOP 就是干这个的。