SSM-IOC&AOP
# IOC
# 利用Spring将引用解耦
场景 | 直接 new 的问题 | IoC 容器的解决方案 |
---|---|---|
对象依赖关系变更 | 需要修改多处源码 | 修改配置文件或条件注解 |
复杂依赖链管理 | 手工初始化代码臃肿易错 | 自动处理依赖嵌套 |
单元测试 | 难以隔离依赖导致测试复杂 | 轻松替换模拟对象 |
功能增强(如日志、事务) | 需要侵入性修改每个类 | AOP 非侵入式增强 |
简单来说:IoC 容器就像你的高级助手,帮你接管了对象的创建、组装、生命周期管理等复杂工作,让你更专注于业务逻辑的实现。
DI 实现:容器通过依赖注入(XML 配置或注解),将对象通过接口传递(依赖接口而非具体实现)。
在 xml 里面,就是通过 property 去指向某个接口,而非指向具体的实现
这里他做到了一个事情,就叫做高内聚,低耦合。高内聚指的是我某个模块自己专注于做自己的事情。比如说我 service 层,我就专注于去做业务层的东西。我的 repostry 层,我就专注于去做我跟数据库持久化相关的东西。每个模块之间做的事情尽量的不重叠,这就叫做高内聚低耦合。就是说我每个模块之间的耦合性是很低的。例如说我的持久层以前是使使用 myirql 去对我们的数据进行持久化的。但我们进行了一些架构升级之后,我们可能以后是需要用啊 pg circle 去对我们的数据进行持久化。那么这个时候呃,我们如果是依赖于接口而非具体的实现的话,那我们就可以呃很方便的去进行我们整个架构的一个迁移。也就是我们可以通过配置化的这种方式把我们的持久化进行一个不知道这里是应该说迁移还是重构啊
通过面向接口编程和依赖注入,可以很容易地替换持久层的实现。例如,如果之前使用MyBatis作为持久层框架,现在需要迁移到Spring Data JPA或者其他ORM框架,只需要确保新的实现满足相同的接口定义,然后在配置文件中指定新的实现类即可。
高内聚低耦合
高内聚 意味着每个模块或组件专注于实现特定的功能,内部功能紧密相关,逻辑上高度相关。这有助于提高代码的可读性、可维护性和可重用性。
低耦合 意味着组件之间的依赖关系最小化,尽量减少直接依赖具体实现。这有助于提高系统的灵活性和可扩展性,降低因修改一个组件而影响另一个组件的风险。
通过 IoC/DI(如 XML 配置),将对象间依赖关系交由容器管理,基于接口解耦,实现高内聚、低耦合的灵活架构。
# IOC注解
Spring 分层注解(标记类用途)
注解 | 中文含义 | 核心作用与设计意义 |
---|---|---|
@Repository | 持久层类 | 标识数据访问层组件(如 DAO 类) 附加功能:自动转换数据库异常为 Spring 统一数据异常 |
@Service | 业务逻辑类 | 标记业务逻辑层,与@Component 功能相同,但通过命名提升代码可读性 |
@Controller | 控制器类 | 标记 Web 控制层,Spring MVC 自动捕获请求参数 配合@RequestMapping 处理 HTTP 请求 |
@Component | 组件类 | 通用组件标识,任何层级类均可使用,但分层注解(如上)语义更明确 |
分层注解的本质:
@Repository
/@Service
/@Controller
均派生自@Component
,语义化标签便于开发者快速识别代码架构分层,对框架行为无本质差异。
# IOC与DI核心总结
IOC(控制反转) 设计理念:对象创建/管理权转交“容器”(如Spring),降低程序耦合。例:
Service
从容器获取Dao
实例,而非直接new
。DI(依赖注入) IOC的实现技术:由容器动态注入对象依赖(如通过反射),而非手动硬编码(如
Service
的Dao
依赖由容器注入)。
关系:IOC是目标(宏观),DI是手段(微观)。
依赖注入通过「对象控制权翻转」解耦业务逻辑与实现细节,是否彻底解耦取决于是否面向接口设计。注解引入的是技术框架层面的轻度耦合,而非业务组件间的深度耦合。
# 实践
# DI 最佳实践
根据Spring官方统计,采用@Resource注解的代码维护成本降低约35%,运行时异常减少62%。建议在日常开发中优先选择@Resource注入方式。
传统使用的@Autowired注解主要基于类型匹配,而@Resource注解则提供了更灵活的注入方式。
匹配机制
@Autowired:默认按类型(byType,Class类型)匹配
@Resource:默认按名称(byName)匹配
冲突处理
当存在多个相同类型的Bean时,@Autowired可能抛出NoUniqueBeanDefinitionException
@Resource通过指定bean名称可避免此问题
# Spring IOC 小知识
# 配置方式的比较
# 为什么要用多例
//替代XML,成为IOC容器主要配置来源
@Configuration
public class SpringConfig {
//@Bean用于初始化对象
//默认方法名是BeanId
//在方法的内部手动进行实例化的操作,并将对象return返回
@Bean
@Scope("prototype")
public IEmpDAO edao(){
return new EmpDAO();
}
@Bean("hService")
@Scope("prototype")
public HrService hrService(){
HrService hrService = new HrService();
hrService.setEmpDAO(this.edao());
return hrService;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# AOP
joinpoint,类似农业里面的嫁接点概念
在不修改源码的情况下对程序行为进行扩展
# 引入依赖
下面两个依赖是AOP依赖是必须的
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--
Spring-AOP底层的插件技术并不是Spring自己实现的
Spring-AOP依赖aspectjweaver实现,并对其进行扩展
包装,使其与Spring Ioc容器完美兼容
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--
aspectjweaver是AOP的核心实现,是第三方机构org.aspectj开发的
aspectj通过代理模式对目标类进行扩展,底层技术使用了JDK动态代理以及
CGLIb代理,Spring就是基于它来实现的
-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
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
# 实践
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect //说明这是一个切面(插件)
public class EnhanceAspect {
/**
@Before说明当前方法是前置通知
是指在目标方法运行前执行这个通知
方法的固定格式为:
public void 方法名(JoinPoint jp){...}
JoinPoint 连接点包含了目标类的信息
execution()表达式被称为PointCut(切点),用于说明通知作用的范围
execution(* com.itlaoqi..*Service.*(..))
返回值 包名.x.x.x.x.类名.方法名(参数列表)
* com.itlaoqi..*Service.*(..)
说明将这个前置通知作用在com.itlaoqi包下的所有Service类的所有方法上
* @param joinPoint
*/
//@Before("execution(* com.itlaoqi..*Service.*(..))")
public void doBefore(JoinPoint joinPoint){
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
String method = joinPoint.getSignature().getName();
System.out.println("[前置通知]准备执行" + simpleName + "." + method);
}
/**
* @Before 前置通知
* @After 后置通知
* @AfterReturning 返回后通知
* @AfterThrowing 异常通知
* @Around 环绕通知
* @param joinPoint
*/
//@After("execution(* com.itlaoqi..*Service.*(..))")
public void doAfter(JoinPoint joinPoint) {
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
String method = joinPoint.getSignature().getName();
System.out.println("[后置通知]" + simpleName + "." + method +"执行完毕");
}
/**
* 环绕通知
* ProceedingJoinPoint 可用于控制目标方法是否执行
* @param pjp
* @return
*/
/**
* Aspect切面类在项目中很少直接使用,绝大多数情况下会使用基于AOP基础上的扩展产品例如:
* Spring MVC Interceptor
* @Transactional 声明式事务
* @Cachable 声明式缓存
* ....
*/
@Around("execution(* com.itlaoqi..*Service.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
String simpleName = pjp.getTarget().getClass().getSimpleName();
String method = pjp.getSignature().getName();
System.out.println("[前置通知]准备执行" + simpleName + "." + method);
Object ret = pjp.proceed();//执行目标方法
System.out.println("[后置通知]" + simpleName + "." + method +"执行完毕");
return ret;
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74