AOP & IOC
先理解 AOP 是啥
AOP 全称 Aspect-oriented Programming,中文叫面向切面编程。
你可以这样理解:
你写了很多 Controller 接口,比如登录、查询用户、下单等等。现在老板说:"每个接口都要记录一下谁访问了、花了多长时间、传了什么参数。"
笨办法是每个接口里都写一遍日志代码,又累又乱。
AOP 的思路是:我不改你的业务代码,而是在旁边"切"一刀,统一帮你加上日志功能。
就像你上班打卡,不是你每做一件事都自己记时间,而是门口有个打卡机统一帮你记。

几个术语,用大白话解释
| 术语 | 大白话 |
|---|---|
| 横切关注点 | 那些跟核心业务没直接关系、但每个方法都要做的事,比如日志、权限、事务 |
| 切面(Aspect) | 把上面这些事封装成一个类,用 @Aspect 标记 |
| 通知(Advice) | 具体什么时候干什么,比如"方法执行前记录开始时间" |
| 连接点(JoinPoint) | 可以被切的地方,比如某个方法被调用时 |
| 切点(Pointcut) | 指定哪些方法要被切,比如"controller 包下所有 public 方法" |
通知有 5 种:
- @Before:方法执行前
- @After:方法执行后(不管成功失败)
- @AfterReturning:方法成功返回后
- @AfterThrowing:方法抛异常后
- @Around:把整个方法包起来,前后都能插手(最常用)
切入点表达式的语法格式规范如下所示:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?)
modifiers-pattern?为访问权限修饰符ret-type-pattern为返回类型,通常用*来表示任意返回类型declaring-type-pattern?为包名name-pattern为方法名,可以使用*来表示所有,或者set*来表示所有以 set 开头的类名param-pattern)为参数类型,多个参数可以用,隔开,各个参与也可以使用*来表示所有类型的参数,还可以使用(..)表示零个或者任意参数throws-pattern?为异常类型?表示前面的为可选项
举个例子:
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")
表示 com.codingmore.controller 包下的所有 public 方法都要应用切面的通知。
实操 AOP 记录接口访问日志
它做了一个"接口访问日志切面",步骤是:
第一步:在 pom.xml 里加 AOP 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:写一个 WebLog 类,用来装日志信息,比如:
- 操作描述
- 谁操作的
- 开始时间
- 花了多少时间
- 请求路径
- 方法名
- IP
- 请求参数
- 返回结果
/**
* Controller层的日志封装类
* Created by macro on 2018/4/26.
*/
public class WebLog {
private String description;
private String username;
private Long startTime;
private Integer spendTime;
private String basePath;
private String uri;
private String url;
private String method;
private String ip;
private Object parameter;
private Object result;
//省略了getter,setter方法
}
第三步:写切面类 WebLogAspect:
@Aspect
@Component
public class WebLogAspect {
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")
public void webLog() {}
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 记录开始时间
Object result = joinPoint.proceed(); // 执行原来的方法
long endTime = System.currentTimeMillis(); // 记录结束时间
// ... 把各种信息塞进 WebLog,然后打印日志
return result; // 返回原方法的结果
}
}
/**
* 统一日志处理切面--完整代码示例
*/
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
}
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
}
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求信息(通过Logstash传入Elasticsearch)
WebLog webLog = new WebLog();
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setIp(request.getRemoteUser());
Map<String,Object> logMap = new HashMap<>();
logMap.put("spendTime",webLog.getSpendTime());
logMap.put("description",webLog.getDescription());
LOGGER.info("{}", JSONUtil.parse(webLog));
return result;
}
}
核心就一句:joinPoint.proceed() 是"执行你原来那个接口方法",在它前后加代码,就能记录时间和日志。
第四步:启动项目,访问接口,控制台就会自动打印日志。
一句话总结
AOP 就是让你不改业务代码,就能给一批方法统一加上日志、权限、事务等功能。文章就是用一个"记录接口访问日志"的例子,把这个过程完整走了一遍。
IoC 到底是啥
IoC = Inversion of Control,中文叫控制反转。
控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。
容器负责创建、配置和管理 bean,也就是它管理着 bean 的生命,控制着 bean 的依赖注入。
通俗点讲,因为项目中每次创建对象是很麻烦的,所以我们使用 Spring IoC 容器来管理这些对象,需要的时候你就直接用,不用管它是怎么来的、什么时候要销毁,只管用就好了。
举个例子,就好像父母没时间管孩子,就把小朋友交给托管所,就安心的去上班而不用管孩子了。托儿所,就是第三方容器,负责管理小朋友的吃喝玩乐;父母,相当于程序员,只管接送孩子,不用管他们吃喝。
等下,bean 又是什么?
Bean 其实就是包装了的 Object,无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object(想一下别人送礼物给你的时候都是要包装一下的,自己造的就免了)。
Bean 是 Spring 的主角,有种说法叫 Spring 就是面向 bean 的编程(Bean Oriented Programming, BOP)。
一句话解释:
以前是你自己
new对象,现在把"创建和管理对象(Bean)"这件事交给 Spring 容器去做,你需要的时候直接拿就行。
打个比方: - 以前:你想喝咖啡,自己买豆子、磨粉、煮水、冲泡,全程自己搞。 - 现在:你直接去咖啡店,说"来杯美式",店员(Spring 容器)给你做好,你只管喝。
咖啡店就是那个"第三方容器",你不再控制咖啡怎么做,控制权交出去了,这就是"反转"。
为什么要这么做(解藕)
想象一个项目里有 A、B、C、D 四个对象,互相 new 来 new 去:
A → new B
B → new C
C → new D
一旦 C 改了构造方法,B 就得跟着改;B 改了,A 也得改。牵一发动全身,这就是耦合太紧。
用了 Spring 容器之后:
A ─┐
B ─┼──→ Spring 容器(统一管理)
C ─┤
D ─┘
每个对象都只跟容器打交道,彼此不直接依赖。谁需要什么,容器负责"注入"进去。这就是解藕。
几个关键名词
| 名词 | 大白话 |
|---|---|
| Bean | 被 Spring 容器管理的对象。其实就是包装过的 Object |
| IoC 容器 | 管理所有 Bean 的地方,负责创建、配置、销毁 |
| BeanFactory | 最基础的容器,可以理解成一个 HashMap:key 是 bean 名字,value 是 bean 对象 |
| ApplicationContext | 高级容器,是 BeanFactory 的子类,功能更多,实际开发常用 |
| 依赖注入(DI) | 容器把对象需要的资源"送进去",而不是对象自己去拿 |
| IoC 和 DI 的关系: | |
| - IoC 是思想(控制权交给容器) | |
| - DI 是实现方式(具体怎么交?靠注入) | |
| - 它俩其实是同一件事的不同角度描述 |
不用 IoC vs 用 IoC
不用 IoC(传统写法):
Rectangle rect = new Rectangle(); // 自己 new
rect.setLength(2);
rect.setWidth(3);
对象是你自己创建的,控制权在你手里。
用 IoC:
ApplicationContext context = new ClassPathXmlApplicationContext("service.xml");
Rectangle rect = context.getBean("rectangle", Rectangle.class);

你没有 new,对象是 Spring 容器根据配置文件帮你创建好、注入好属性,你直接拿。

配置文件长啥样
Spring 需要一个 XML 配置文件告诉它:要创建哪些对象、属性怎么赋值。
<bean id="rectangle" class="com.example.Rectangle">
<property name="length" value="2"/>
<property name="width" value="3"/>
</bean>
id:这个 bean 的唯一名字(身份证)class:类的全限定名(从包名到类名)property:给属性赋值,name对应 setter 方法名
注意:必须要有 setter 方法,因为 Spring IoC 默认是通过 setter 注入的。
两个常见疑问
1. 容器里的对象是单例吗?
是的,默认是单例。你 getBean 两次拿到的是同一个对象(== 返回 true)。想改成每次新建,配置里加 scope="prototype"。
2. 对象什么时候创建? 默认在容器启动时就全部创建好了(singleton 情况下)。所以即使你不 getBean,构造函数也会执行。
手动导 jar 包 vs Maven
通过Spring Framework 八大模块:
- 用 IoC 功能,至少需要 4 个 jar 包:spring-beans、spring-core、spring-context、spring-expression,另外还要 commons-logging。
- 手动导太麻烦,实际开发都用 Maven,在 pom.xml 里写依赖,自动下载。
一句话总结
IoC 就是把"创建和管理对象"的权力从程序员手里交给 Spring 容器,从而实现解藕。你不再
new对象,而是从容器里"拿"现成的。DI(依赖注入)是实现 IoC 的具体手段。
对比:
| IoC | AOP | |
|---|---|---|
| 解决什么 | 对象怎么创建、怎么管理 | 给一批方法统一加功能 |
| 核心 | 容器管 Bean | 切面管通知 |
| 关系 | Spring 的根基 | 建立在 IoC 之上 |