Spring AOP 面向切面编程


Spring AOP 面向切面编程

一、概述

1、简介

  • AOP是一个面向切面编程的思想,它补充了面向对象的不足
  • 简化代码把方法中非核心功能且重复的代码抽取出来
  • 增强代码把特定的功能封装到切面类中,哪里需要就往上套,被套用的方法被切面增强了
  • 对方法的增强,本质上就是在执行方法的前后添加功能
  • 把一些与业务逻辑层无关的代码分离出来

2、核心概念

  1. 切面:类是对物体特征的抽象,切面就是对横切关注点的抽象
  2. 通知:指拦截到连接点之后要执行的代码,分为前置、后置、异常、返回后、环绕通知五类
  3. 横切关注点:对哪些方法进行拦截,拦截后怎么处理,为横切关注点
  4. 连接点:被拦截到的点,被抽取的方法
  5. 目标:被代理的目标对象
  6. 代理:向目标对象应用通知之后创建的代理对象
  7. 切入点:对连接点进行拦截的定义

3、应用场景

  • 记录日志
  • 监控性能
  • 权限控制
  • 事务管理
  • 缓存

二、AOP注解

  1. @Aspect:切面类
  2. @PointCut:切点
  3. @Before:前置通知
  4. @After:后置通知
  5. @AfterReturning:返回后通知
  6. @AfterThrowing:异常通知
  7. @Around:环绕通知
  8. @Order:优先级,value指越小优先级越高

三、基于XML的AOP

1、导入jar包pom.xml

  <!--配置jar包-->
    <dependencies>
        <!--spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.21</version>
        </dependency>
        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
        <!--AOP依赖包 Spring Boot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
                <version>2.7.0</version>
            </dependency>
    </dependencies>

2、创建Calculator接口

package com.sgz.aop;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 12:20
 * 需求:
 */
public interface Calculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

3、创建CalculatorImpl实现类

package com.sgz.aop;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 12:20
 * 需求:目标对象,专门用来实现核心代码
 */
public class CalculatorImpl implements Calculator{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部,result:" + result);
        return result;
    }
}

4、创建LoggerAspect类

package com.sgz.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 14:11
 * 需求:
 */
@Component
public class LoggerAspect {

    // 前置通知
    public void beforeAdviceMethod(JoinPoint joinPoint) {
        // 获取连接点所对应方法的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->前置通知" + signature.getName());
    }

    // 后置通知
    public void afterAdviceMethod(JoinPoint joinPoint) {
        // 获取连接点所对应方法的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->后置通知" + signature.getName());
    }

    // 返回通知
    public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
        // 获取连接点所对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->返回通知" + signature.getName() + ",结果:" + result);
    }

    // 异常通知
    public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex) {
        // 获取连接点所对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->异常通知" + signature.getName() + ",异常通知" + ex);
    }

    // 环绕通知
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            System.out.println("环绕通知-->前置通知");
            // 表示目标对象方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->返回通知");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("环绕通知-->异常通知");
        } finally {
            System.out.println("环绕通知-->后置通知");
        }
        return result;
    }

}

5、创建ValidateAspect类

package com.sgz.aop;

import org.springframework.stereotype.Component;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 15:40
 * 需求:
 */
@Component
public class ValidateAspect {
    private void beforeMethod(){
        System.out.println("ValidateAspect-->前置通知");
    }
}

6、配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       https://www.springframework.org/schema/context/spring-context.xsd 
       http://www.springframework.org/schema/aop 
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.sgz.aop"></context:component-scan>

    <aop:config>
        <!--设置一个公共的切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(* com.sgz.aop.CalculatorImpl.*(..))"/>
        <!--将IOC容器中的某个bean设置为切面-->
        <aop:aspect ref="loggerAspect">
            <aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowingAdviceMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
        <!--越小优先级越高-->
        <aop:aspect ref="validateAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>

7、创建TestAopXML测试类

package com.sgz.aop.test;

import com.sgz.aop.Calculator;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 15:42
 * 需求:
 */
public class TestAopXML {
    @Test
    public void test(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator calculator = ioc.getBean(Calculator.class);
        calculator.add(1,1);
    }

}

四、基于注解的AOP

1、导入jar包pom.xml

  <!--配置jar包-->
    <dependencies>
        <!--spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.21</version>
        </dependency>
        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
        <!--AOP依赖包 Spring Boot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
                <version>2.7.0</version>
            </dependency>
    </dependencies>

2、创建Calculator接口

package com.sgz.aop;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 12:20
 * 需求:
 */
public interface Calculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

3、创建CalculatorImpl实现类

package com.sgz.aop;
import org.springframework.stereotype.Component;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 12:20
 * 需求:目标对象,专门用来实现核心代码
 */
@Component
public class CalculatorImpl implements Calculator{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部,result:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部,result:" + result);
        return result;
    }
}

4、创建LoggerAspect类

package com.sgz.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


/**
 * 作者:沈公子
 * 日期:2022/8/3 - 19:38
 * 需求:切面,日志切面
 * 在切面中,需要通过指定的注解将方法标识为通知方法
 *
 * @Before:前置通知,在目标对象方法执行之前执行
 * @After:后置通知,在目标对象方法的finally字句中执行
 * @AfterReturning:返回通知,在目标对象方法返回值之后执行 切入点表示式:设置在标识通知的注解的value属性中
 * @AfterThrowing:异常通知,在目标对象方法的catch字句中执行
 * @Before("execution(public int com.sgz.aop.annotation.CalculatorImpl.add(int,int))")
 * @Before("execution(* com.sgz.aop.annotation.CalculatorImpl.*(..))")
 * 第一个 * 表示任意的访问修饰符和返回值类型
 * 第二个 * 表示类中任意的方法
 * .. 表示任意的参数列表
 * 类的地方也可以使用 *,表示包下所有的类
 * <p>
 * <p>
 * 重用切入点表达式
 * 声明一个公共的切入点表达式
 * @Pointcut("execution(* com.sgz.aop.annotation.CalculatorImpl.*(..))")
 * public void pointCut(){
 * }
 *
 * <p>
 * 3、获取连接点的信息
 * 在通知方法的参数位置,设置 JoinPoint 类型的参数,就可以获取连接点所对应方法的信息
 * // 获取连接点所对应方法的方法名
 * Signature signature = joinPoint.getSignature();
 * // 获取连接点所对应方法的参数
 * Object[] args = joinPoint.getArgs();
 *
 * 切面的优先级
 * 可以通过 @Order 注解的 value 属性设置优先级,默认值 Integer 的最大值
 * @Order 注解的 value值越小优先级越高
 */
@Component
@Aspect // 标识为切面
public class LoggerAspect {

    // 切入点
    @Pointcut("execution(* com.sgz.aop.CalculatorImpl.*(..))")
    public void pointCut() {
    }

    // 前置通知
    // @Before("execution(public int com.sgz.aop.CalculatorImpl.add(int,int))")  // 通过方法
    // @Before("execution(* com.sgz.aop.CalculatorImpl.*(..))")  // 通过类
    @Before("pointCut()")   // 通过切入点
    public void beforeAdviceMethod(JoinPoint joinPoint) {
        // 获取连接点所对应方法的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->前置通知" + signature.getName());
    }

    // 后置通知
    @After("pointCut()")
    public void afterAdviceMethod(JoinPoint joinPoint) {
        // 获取连接点所对应方法的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->后置通知" + signature.getName());
    }

    // 返回后通知
    // 在返回通知中若要获取目标对象方法的返回值,
    // 只需要通过 @AfterReturning 注解的 returning 属性,
    // 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
        // 获取连接点所对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->返回通知" + signature.getName() + ",结果:" + result);
    }

    // 异常通知
    // 在异常通知中若要获取目标对象方法的异常,
    // 只需要通过 @AfterThrowing 注解的 throwing 属性,
    // 就可以将通知方法的某个参数指定为接收目标对象方法的异常的参数
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex) {
        // 获取连接点所对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect-->异常通知" + signature.getName() + ",异常通知" + ex);
    }

    // 环绕通知
    @Around("pointCut()")
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            System.out.println("环绕通知-->前置通知");
            // 表示目标对象方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->返回通知");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("环绕通知-->异常通知");
        } finally {
            System.out.println("环绕通知-->后置通知");
        }
        return result;
    }

}

5、创建ValidateAspect类

package com.sgz.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 15:40
 * 需求:
 */
@Component
@Aspect
@Order(1)    // 优先级设置,越小优先级越高
public class ValidateAspect {

    @Before("com.sgz.aop.LoggerAspect.pointCut()")
    private void beforeMethod() {
        System.out.println("ValidateAspect-->前置通知");
    }
    
}

6、配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        AOP的注意事项:
        切面类和目标类都需要交给IOC容器管理
        切面类必须通过 @Aspect 注解标识为一个切面
        在 Spring 的配置文件中设置  <aop:aspectj-autoproxy/> 开启基于注解的AOP功能
    -->
    <!--扫描组件-->
    <context:component-scan base-package="com.sgz.aop"></context:component-scan>
    
    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy/>

</beans>

7、创建TestAop测试类

package com.sgz.aop.test;

import com.sgz.aop.Calculator;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 作者:沈公子
 * 日期:2022/8/5 - 15:42
 * 需求:
 */
public class TestAop{
    @Test
    public void test(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator calculator = ioc.getBean(Calculator.class);
        calculator.add(1,1);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值