AOP基础

本文介绍了AOP(面向切面编程)的概念,旨在解决代码分散和混乱的问题。通过AOP,横切关注点可以被模块化到切面中,提高代码的可维护性和业务模块的简洁性。文章详细讲解了AOP术语,如切面、通知、切点等,并展示了如何使用AspectJ注解声明切面,包括不同类型的注解通知(前置、后置、返回、异常和环绕)。此外,还讨论了切面的优先级、切点表达式的重用以及基于配置文件的AOP配置。
摘要由CSDN通过智能技术生成

背景问题

  1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀.每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
  2. 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志打印代码.如果日志需求发生变化,必须修改所有模块

AOP简介

  1. AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充
  2. AOP的主要编程对象是切面(aspect),而切面模块化横切关注点
  3. 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里.
  4. AOP的好处:
    1. 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
    2. 业务模块更简洁,只包含核心业务代码

AOP术语

  1. 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  2. 通知(Advice):切面必须要完成的工作
  3. 目标(Target):被通知的对象
  4. 代理(Proxy):向目标对象应用通知之后创建的对象
  5. 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如ArithmethicCalculator#add()方法执行前的连接点,执行点为ArithmethicCalculator#add();方位为该方法执行前的位置
  6. 切点(pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件

用AspectJ注解声明切面

  1. 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例.当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那么与AspectJ切面相匹配的Bean创建代理
  2. 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类
  3. 通知是标注有某种注解的简单的Java方法
  4. AspectJ支持5中类型的通知注解:
    1. @Before:前置通知,在方法执行之前执行
    2. @After:后置通知,在方法执行之后执行
    3. @AfterRunning:返回通知,在方法返回结果之后执行
    4. @AfterThrowing:异常通知,在方法抛出异常之后执行
    5. @Around:环绕通知,围绕着方法执行

前置通知

  1. @Before(“execution(方法路径)”)
  2. AspectJ:Java社区里最完整最流行的AOP框架,在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
  3. 在Spring中启用AspectJ注解支持:
    1. 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aoplliance.jar、aspectj.weaver.jar和spring-aspects.jar
    2. 将aopSchema添加到<beans>根元素中.
    3. 要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>
    4. 当Spring IOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理

/声明该方法是一个前置通知:在目标方法开始前执行/

@Before("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("The method is begining");
    System.out.println("The method is " + methodName + " and args are " + args);
}

后置通知

// 声明该方法是一个后置通知:在目标方法结束后执行(无论是否发生异常)
// 在后置通知中不能访问目标方法执行的结果
@After("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());

    System.out.println("The method is end");
    System.out.println("The method is " + methodName + " and args are " + args);
}

返回通知

// 声明该方法是一个返回通知:在目标方法正常结束后执行
// 在返回通知中可以访问目标方法执行的结果
@AfterReturning(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))",  returning = "result")
public void afterReturnMethod(Object result){

    System.out.println("The method is end");
    System.out.println("The result is " + result);
}

异常通知

// 声明该方法是一个异常通知:在目标方法抛出异常后执行
@AfterThrowing(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))",  throwing = "exception")
public void afterThrowMethod(NullPointerException exception){
    System.out.println("The method throwing exception ");
    System.out.println("The exception is " + exception);
}*/

环绕通知

// 声明该方法是一个环绕通知,需要携带ProceedingJoinPoint类型的参数
// 环绕通知类似于动态代理的全过程;ProceedingJoinPoint类型的参数可以决定是否执行目标方法
// 且环绕通知必须有返回值,返回值即为目标方法的返回值
@Around("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){

    Object result = null;
    String methodName = proceedingJoinPoint.getSignature().getName();

    // 执行目标方法
    try{
        // 前置通知
        System.out.println("The method:" + methodName + " begings with:" + Arrays.asList(proceedingJoinPoint.getArgs()));

        result = proceedingJoinPoint.proceed();

        System.out.println("The result is:" + result);
    } catch (Throwable throwable) {
        System.out.println("异常通知");
        throwable.printStackTrace();
    }

    // 后置通知
    System.out.println("The method is ended");

    return result;
}

引入通知(较少使用)

引入通知是一种特殊的通知类型。它通过为接口提供实现类,允许对象动态的实现接口,就像对象已经在运行时扩展了实现类一样


切面的优先级

使用@Order类注解指定优先级,值越小优先级越高如@Order(2)


重用切点表达式

可以定义一个方法,用于声明切入点表达式.一般地,该方法中不再需要填入其他的代码,例如:
在切面类中定义方法:

//使用@Pointcut来声明切入点表达式,其他通知直接使用方法名来引用当前的切入点表达式
@Pointcut("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void declareJointPointExpression(){}

然后,上面前置通知、后置通知…使用引用“execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))”的地方可以替换为declareJointPointExpression(),如果是在其他类(包)中引用,需要带上类名(及包名)


基于配置文件配置AOP

  1. 先在配置文件中配置bean
  2. 配置AOP:
//<!-- 配置AOP -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))" id="pointcut">
        <!-- 配置切面及通知-->
        <aop:aspect ref="切面bean的id" order="2">
            <aop:before method="beforeMethod" pointcut-ref="pointcut">
        </aop:aspect>
    </aop:config>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vi_NSN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值