如果把Spring比作一座大厦的话,那么IoC和AOP无疑是大厦的两座基石。无论是日常工作还是应付面试,IoC和AOP都是绕不开的话题。上一篇文章我们介绍了IoC,今天我们通过一个例子详细介绍AOP的使用和原理
AOP
如果让你开发一个购物网站,你会考虑哪些功能呢?
我们不仅要完成商品浏览,订单管理,物流管理等业务功能,而且要支持鉴权,日志,审计等非业务功能。非业务功能横跨多个业务领域,我们称之为横切业务,或者Aspect业务。业务功能和非业务功能属于不同的关注点,我们需要把他们分离开来。
AOP全称是面向“切面”编程,是对面向对象编程的增强,可以在不修改业务代码的前提下添加非业务功能。
基本概念
Spring aop的概念理解比较拗口,建议大家不要翻译成中文,直接理解英文。
我们通过一个例子理清这些基本概念。下面这句话描述了一个切面业务。
- aspect: What,期望spring执行什么样的横切逻辑
- advice:When, 什么时候执行aspect。可以通过注解指定执行时机
- @Before:方法执行前
- @After:方法执行后
- @AfterReturning:方法返回后
- @AfterThrowing:抛出异常后
- pointcut:Where,在哪里执行横切逻辑
- join point: Who, 谁触发横切,spring里一般是方法调用触发
Spring AOP使用例子
- 我们先定义一个Class ProductService,这个Class有两个方法,addProduct负责添加商品,removeProduct负责移除商品。
@Service
public class ProductService {
public void addProduct()
{
System.out.println("ProductService add product");
}
public void removeProduct()
{
System.out.println("ProductService remove product");
}
}
现在来了新需求,我们要在添加商品时记录AuditLog,便于事后审计。这是一个典型的横切功能,我们就要借助AOP来实现
- 开启AOP
定义一个Configuration类,然后打上@EnableAspectJAutoProxy标注
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
- 定义一个注解ToAudit,只有打了@ToAudit注解的方法才执行横切逻辑
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToAudit {
String name();
}
- 定义Aspect类
@Aspect //@Aspect表示这是一个切面类
@Component
public class LogAspect {
//定义PointCut: 所有标注了@ToAudit的方法
//定义advice:在pointcut执行横切逻辑
@Before("@annotation(ToAudit)")
public void after(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("---------Write audit log for " + method.getName() + "-------------");
}
}
- 修改ProductService,给方法打上@ToAudit注解
@Service
public class ProductService {
@ToAudit(name = "Add Product")
public void addProduct()
{
System.out.println("ProductService add product");
}
public void removeProduct()
{
System.out.println("ProductService remove product");
}
}
- 启动程序,查看允许结果
main方法中通过Spring context获取Bean,执行addProduct方法。
public class AopTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
ProductService productService = ctx.getBean(ProductService.class);
productService.addProduct();
ctx.close();
}
}
执行结果如下,我们看到addProduct执行时记录了auditlog, 符合预期。
---------Write audit log for addProduct-------------
ProductService add product
更多高级特性
- aspect优先级
如果有多个aspect拦截同一个方法,Spring不保证Aspect的执行顺序。
可以使用@Order为aspect指定执行顺序,@Order value越小表示优先级越高
@Aspect
@Order(1)
@Component
public class LogAspect {
//...
}
- 可以通过AOP修改方法参数
可以在aspect代码中,修改原始的请求参数
@Around("annotationPoinCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取被拦截的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//改变原始的请求参数,然后交给Aspect Chain上的下一个Aspect处理
Object[] args = joinPoint.getArgs();
Object[] newArgs = {"New args in aspect"};
joinPoint.proceed(newArgs);
}
AOP实现原理
Spring AOP功能非常强大,Spring很多特性都是基于AOP实现的(如@Transaction事务管理)。
我们不仅仅要知其然,而且要知其所以然,AOP的实现基于动态代理,当我们开启@EnableAspectJAutoProxy,如果某个Bean有对应的@Aspect逻辑要执行,Spring就会为这个Bean生成一个proxy Bean,在Proxy Bean中对原始Bean增加切面逻辑。
我们在Idea中debug也可以发现,ctx.getBean(ProductService.class)得到的并不是原始的ProductService实例,而是一个经过CGLib增强后的Proxy实例。
product-service.JPG
没有银弹
AOP虽好,但是滥用也不好。过多使用AOP会让我们业务散落在各个地方,让维护工作变得非常困难。
总结
AOP是是Spring的重要基石,我在文章中通过一个例子介绍了AOP的基本概念和使用。我也介绍了AOP的实现技术动态代理。
网友评论