目录
一、AOP概述
二、AOP使用示例
三、AOP相关术语
四、总结
一、AOP概述
我们知道,使用面向对象编程(OOP)有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、权限校验等,我们只有在每个对象里面引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了,所以就有了对面向对象编程的不从,即面向切面编程(AOP),AOP关注的方向是横向的,而OOP关注的是纵向。
AOP是Aspect Oriented Programming的缩写,意为:面向切面编程。AOP是指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式。可以说AOP是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
我们举一个比较容易理解的例子。假如我们需要监控系统中某些重要方法的执行时间,应该怎么实现?
直接在每个方法执行前、后输出系统当前时间,直接相减就得到方法的执行时间。如下图:
缺点:每个方法执行前、后都要写一遍同样的统计逻辑,而且统计执行时间跟具体的业务逻辑没什么关系,但是这样的话,直接将代码编写在一起,耦合度太高,且代码重复。
那么有没有一种简便的方法,不需要每个方法都编写一次,其实AOP就提供了这样的功能,也就是下面的方案二。
直接切入到每个方法的执行前、后,拦截到这些方法的执行,然后执行增强功能。如下图:
这样接口就只需要关心具体的业务,而不需要关注其他非该接口的逻辑或处理,把额外的不涉及业务的代码放到切面类中进行处理,降低了它们之间的耦合度。
二、AOP使用示例
下面我们就通过AOP来实现前面举的例子----统计方法的执行时间。
首先定义两个业务层接口,代码也比较简单:
package com.wsh.service;
public interface UserService {
void addUser();
}
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("新增用户...");
}
}
public interface ProductService {
void addProduct();
}
@Service("productServiceImpl")
public class ProductServiceImpl implements ProductService {
@Override
public void addProduct() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("添加商品...");
}
}
下面定义AOP配置类,主要是开启AOP注解支持、扫描组件类的功能:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.wsh")
public class AopConfig {
}
然后我们定义切面类,使用@Aspect注解标识:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
// @Aspect:告诉Spring这是一个切面类
@Aspect
@Component
public class SimpleAspect {
// 定义切入点表达式
@Pointcut("execution(* com.wsh.service..*.*(..))")
private void pointcut() {
}
// 环绕通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
String methodName = proceedingJoinPoint.getSignature().getName();
System.out.println("执行" + methodName + "的环绕通知(@Around)...");
try {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(methodName + "()方法耗时: " + (endTime - startTime) + "毫秒");
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
// 前置通知
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行" + methodName + "的前置通知(@Before)...");
}
// 后置通知
@After("pointcut()")
public void after(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行" + methodName + "的后置通知(@After)...");
}
// 返回通知
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行" + methodName + "的后置返回通知(@AfterReturning)...");
}
}
使用@Pointcut注解定义所有需要织入的连接点,也就是方法执行的地方。这里我们使用@Around环绕通知功能,@Around是在方法执行前、执行后执行。
接着定义一个测试控制层接口:
import com.wsh.service.ProductService;
import com.wsh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("simpleController")
public class SimpleController {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
public void test() {
userService.addUser();
System.out.println("==================");
productService.addProduct();
}
}
测试类:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
SimpleController simpleController = (SimpleController) annotationConfigApplicationContext.getBean("simpleController");
simpleController.test();
}
}
输出结果如下:
执行addUser的环绕通知(@Around)... 执行addUser的前置通知(@Before)... 新增用户... addUser()方法耗时: 2013毫秒 执行addUser的后置通知(@After)... 执行addUser的后置返回通知(@AfterReturning)... ================== 执行addProduct的环绕通知(@Around)... 执行addProduct的前置通知(@Before)... 添加商品... addProduct()方法耗时: 1014毫秒 执行addProduct的后置通知(@After)... 执行addProduct的后置返回通知(@AfterReturning)...
从输出结果可以看到,我们成功监控到方法的执行时间,AOP切面就像拦截器一样,拦截了切入点指定的方法的执行,并执行切面的增强功能:统计方法执行时间,以上就是关于Spring AOP一个简单的使用示例。
三、AOP相关术语
前面大概了解了Spring AOP的使用,下面描述一些AOP相关概念和术语:
Spring官网对AOP相关术语的描述如下地址:Core Technologieshttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn
Spring AOP 包括以下类型的Advice(通知/增强):
对照我们前面统计方法执行时间例子来说:
四、总结
通过前面的总结,我们大致了解了Spring AOP的概念、相关术语以及使用方法。在实际项目中,我们可以使用AOP进行一些不涉及业务逻辑的功能,比如日志记录、性能统计、权限控制、事务处理、异常处理等等,将这些代码从业务逻辑中抽取出来,将它们独立到AOP切面中,这样可以降低各模块之间的耦合,并且改变改变这些行为的时候也不会影响业务逻辑的代码。
Spring AOP主要是使用动态代理的技术实现的,主要是JDK动态代理、Cglib动态代理,本篇文章先熟悉一下AOP相关术语,下篇文章我们开始介绍AOP原理部分的内容。



