Spring AOP从使用到原理

2023-01-01  本文已影响0人  JerrysCode

如果把Spring比作一座大厦的话,那么IoC和AOP无疑是大厦的两座基石。无论是日常工作还是应付面试,IoC和AOP都是绕不开的话题。上一篇文章我们介绍了IoC,今天我们通过一个例子详细介绍AOP的使用和原理

AOP

如果让你开发一个购物网站,你会考虑哪些功能呢?
我们不仅要完成商品浏览,订单管理,物流管理等业务功能,而且要支持鉴权,日志,审计等非业务功能。非业务功能横跨多个业务领域,我们称之为横切业务,或者Aspect业务。业务功能和非业务功能属于不同的关注点,我们需要把他们分离开来。
AOP全称是面向“切面”编程,是对面向对象编程的增强,可以在不修改业务代码的前提下添加非业务功能。

image.png

基本概念

Spring aop的概念理解比较拗口,建议大家不要翻译成中文,直接理解英文。
我们通过一个例子理清这些基本概念。下面这句话描述了一个切面业务。

image.png

Spring AOP使用例子

  1. 我们先定义一个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来实现

  1. 开启AOP
    定义一个Configuration类,然后打上@EnableAspectJAutoProxy标注
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
  1. 定义一个注解ToAudit,只有打了@ToAudit注解的方法才执行横切逻辑
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToAudit {
    String name();
}
  1. 定义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() + "-------------");
    }
}
  1. 修改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");
    }
}
  1. 启动程序,查看允许结果
    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

更多高级特性

  1. aspect优先级
    如果有多个aspect拦截同一个方法,Spring不保证Aspect的执行顺序。
    可以使用@Order为aspect指定执行顺序,@Order value越小表示优先级越高
@Aspect
@Order(1) 
@Component
public class LogAspect {
    //...
}
  1. 可以通过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增加切面逻辑。

image.png

我们在Idea中debug也可以发现,ctx.getBean(ProductService.class)得到的并不是原始的ProductService实例,而是一个经过CGLib增强后的Proxy实例。


product-service.JPG

没有银弹

AOP虽好,但是滥用也不好。过多使用AOP会让我们业务散落在各个地方,让维护工作变得非常困难。

总结

AOP是是Spring的重要基石,我在文章中通过一个例子介绍了AOP的基本概念和使用。我也介绍了AOP的实现技术动态代理。

上一篇 下一篇

猜你喜欢

热点阅读