java干货 spring aop的理解和使用

一、AOP 概念

1.1 aop 思想

APO (面向切面编程) 是一种编程思想,它通过将通用的横向关注点(日志、事务、权限控制等)与业务逻辑分离,实现解耦,使得代码更易于维护。核心就是将非核心代码抽取出来,在实际运行时将代码切回业务代码。如何切回?使用cglib动态代理jdk动态代理实现。

1.2 aop 应用场景

  • 日志记录:在系统中记录日志是非常重要的,可以使用aop来实现记录日志的功能,可以在业务方法执行前、执行后或者抛出异常时记录日志
  • 事务处理:在数据库操作中,使用事务可以保证数据的一致性,可以使用aop 来实现处理事务的功能,可以在方法开始前开启事务,在方法执行完毕后提交事务,或者在抛出异常时回滚事务
  • 安全控制:在系统中包含某些需要安全控制的操作,如登录、授权等,可以使用aop 实现安全控制的功能 ,在方法执行前进行权限判断,如果用户没有权限,则抛出异常或者转到错误页面,防止未经授权的访问
  • 异常处理: 系统中可能会出现各种异常,如空指针异常、数据库链接异常等,可以使用aop 来实现处理异常的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志发送邮件等)
  • 性能监控: 在系统中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈进行优化,可以使用aop 来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算执行时间并输出到日志中
  • 缓存控制:在系统中有些数据需要缓存起来以提高访问速度,可以使用aop 来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有直接返回,否则执行方法并将返回值存入缓存

二、aop 如何使用

2.1 八个核心名词

  • 横切关注点

    • 从每个方法中抽取出来的同一类非核心业务(如日志是一个业务,事务是一个业务)。在同一个项目,可以使用多个横向关注点,对同一个方法进行不同方面(日志、事务等)的的增强
    • AOP 把软件系统分为两个部分,核心关注点横切关注点,核心业务是核心关注点,与之关系不大的部分是横切关注点。
    • 横切关注点的特点是,它们经常发生在核心关注点的多处,而且各处基本相似,比如权限认证、日志、事务、异常等
  • 通知(也叫增强)

    • 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫做通知方法增强方法
    • 前置通知:在被代理的目标的方法前执行
    • 返回通知: 在被代理的目标方法成功后执行,可以修改返回值
    • 异常通知: 在被代理的目标方法异常结束后执行
    • 后置通知:在被代理的目标方法最终结束后执行,不可以修改返回值
    • 环绕通知: 使用 try…catch…finally 结构围绕整个被代理的目标方法,包括上面四种通知的所有位置
  • 连接点 (joinpoint)
    这是一个纯逻辑概念,指哪些可能被拦截到的点,也就被 通知增强 的各个点

  • 切入点 (pointcut)
    定位连接点的方式,可以理解为被选中的连接点。是一个表达式,比如execution(* com.spring.service.impl…(…))指定好了切入点,框架才知道在哪里进行增强

  • 切面(aspect)
    切入点和增强的结合,是一个类。在各个切入点进行增强,好比切西瓜,形成一个切面

  • 目标 target
    被代理的目标对象

  • 代理 proxy
    为了对目标对象应用通知而创建的对象,好比中介,目标是房东

  • 织入
    是指把通知应用到目标上,生成代理对象的过程,可以在编译时织入,也可以在运行时织入,spring 采用后者,也就是动态代理

2.2 代码实现

  • 加入依赖
    spring-context 已经集成了aop ,aop 只为 ioc 容器中的对象创建代理对象
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.6</version>
        </dependency>
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>6.0.6</version>
        </dependency>
  • 步骤

    • 编写业务类
    • 定义增强类,在类中编写增强方法,获取核心业务方法信息(方法名,参数,访问修饰符,所属的类信息等),编写非核心业务逻辑,如日志输出等
    • 在增强方法加注解,指定切入点(通过切点表达式指明)以及增强的时机(前置,后置等),这样就知道在哪个类的哪个方法上进行怎么样的增强
    • 增强类上加**@Component注解**,注入ioc容器,加**@Aspect注解**,指定为切面
    • 编写配置类加@Configuration注解,指定为配置类,指定扫描包(加@ComponentScan注解),增强类也必须注入ioc 容器,开启aspectj aop支持(加@EnableAspectJAutoProxy注解)
    • 在junit 测试类上加上@SpringJUnitConfig(value = JavaConfig.class),指定配置类,那么spring 将自动为我们创建ioc 容器,而不用我们手动 new ClassPathXmlApplication
  • 补充

    • 配置类相当于一个.xml文件,含有扫描包的路径。
    • @SpringJUnitConfig(value = JavaConfig.class) 相当于 new ClassPathXmlApplication(JavaConfig.class)
    • @SpringJUnitConfig(locations = {“xxx.xml”}) 相当于 new ClassPathXmlApplication(“xxx.xml”)
    • @EnableAspectJAutoProxy 相当于在xml 中配置aop 标签,@ComponentScan注解 相当于 component-sacn标签
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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">
    <context:component-scan base-package="com.binbin.service"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 编写业务类
    接口
public interface Calculator {
    public double div(int i, int j);
}

实现类

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public Double div(int i, int j){
        try {
            System.out.println("业务方法--正在计算除法");
            double r = i / j;
            return r;
        }catch (Exception e){
            System.out.println("业务方法--出现异常");
            throw e; // 抛异常
        }finally {
            // 回收资源
            System.out.println("业务方法----finally!");
        }
    }

}
  • 编写增强类,也就是切面
@Component
@Aspect
public class LogAdvice {
    @Before(value = "execution(* com.binbin.service.impl.*.*(..))")
    public void atBefore(JoinPoint joinPoint){
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Method Name: " + methodName);
        System.out.println("Before-前置增强!");
    }

    @After(value = "execution(* com.binbin.service.impl.*.*(..))")
    public void atAfter(JoinPoint joinPoint){
        System.out.println("After-后置增强!");
    }

    @AfterReturning(value = "execution(* com.binbin.service.impl.*.*(..))",returning="res")
    public void atAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("AfterReturning -后置返回增强! 返回结果 res = " + res.toString());
    }

    @AfterThrowing(value = "execution(* com.binbin.service.impl.*.*(..))",throwing = "e")
    public void error(Exception e){
        System.out.println("AfterThrowing-异常处理增强!" + e);
    }
}
  • 编写配置类
@Configuration
@ComponentScan(basePackages = {"com.binbin.service","com.binbin.advice"})
@EnableAspectJAutoProxy // 开启aop 功能
public class JavaConfig {
}
  • 编写测试类
@SpringJUnitConfig(value = JavaConfig.class) // 由spring 创建ioc 容器,相当于 new ClassPathXmlApplication("xxx.xml") 或者 new ClassPathXmlApplication(xxx.class)
public class SpringTest {
    @Autowired
    Calculator calculator;
    @Test
    public void  test() {
        try {
            Double r = calculator.div(1, 1);
        }catch (Exception e){
            System.out.println();
        }

    }
}

调用calculator.div(1, 1):
依次触发 Before、AfterReturning、After,无异常

Method Name: div
Before-前置增强!
业务方法--正在计算除法
业务方法----finally!
AfterReturning -后置返回增强! 返回结果 res = 1.0
After-后置增强

调用calculator.div(1, 0):
依次触发 Before、AfterThrowing、After有异常

Method Name: div
Before-前置增强!
业务方法--正在计算除法
业务方法--出现异常
业务方法----finally!
AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
After-后置增强!

  • 小结
    执行顺序:前置增强业务核心返回值后置异常增强后置增强
    异常还是会传播方法调用处

2.3 统一切点管理

  • 将切点表达式全部放到一个类中,并加@Component注解多个execution 可以用 || 分开,方法上加@Pointcut 注解
@Component
public class MyPointCut{
    @Pointcut(value = "execution(* com.binbin.service.impl.*.*(..)) || execution(* com.binbin.pointcut.*.*())")
    public void pc1(){}

 @Pointcut(value = "execution(* com.binbin.service.impl.*.*(..)) || execution(* com.binbin.pointcut.*.*())")
    public void pc2(){}
}
  • 在切面中使用,通过 类的全限名.方法名() 进行使用,多个可以使用 || 分开
@Component
@Aspect
public class LogAdvice {
    @Before("com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc1()")
    public void atBefore(JoinPoint joinPoint){
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        int modifier = joinPoint.getSignature().getModifiers();
        System.out.println("Modifier: " + Modifier.toString(modifier));
        System.out.println("Method Name: " + methodName);
        System.out.println("Before-前置增强!");
    }
}    

2.4 环绕通知

  • 相当于 前置通知、后置通知、异常通知的组合实现,可以分开写,也可以使用环绕通知,环绕通知,需要自己调用目标对象的方法
@Component
@Aspect
public class MyAroundAdvice {

    @Around(value = "com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc2()")
    public Object testAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();// 获取目标方法参数
        Object result = null;
        try {
            System.out.println("开启事务!");
            result = proceedingJoinPoint.proceed(args);
            System.out.println("提交事务");
        }catch(Throwable t){
            System.out.println("回滚事务");
            throw new RuntimeException(); // 抛异常
        }finally {
            System.out.println("回收资源");
        }
        return  result;
    }
}

2.5 设置切面优先级

  • 一个切点可能有多个切面进行增强,因此可以设置优先级,从而改变各个切面的执行顺序
  • 通过加@Order 注解进行实现,数字越小优先级越高
@Component
@Aspect
@Order(1)
public class TransitionAdvice {
    @Before("com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc2()")
    public void atBefore(JoinPoint joinPoint){
        System.out.println("TransitionAdvice-Before-前置增强!");
    }

    @After(value = "execution(* com.binbin.service.impl.*.*(..))")
    public void atAfter(JoinPoint joinPoint){
        System.out.println("TransitionAdvice-After-后置增强!");
    }

    @AfterReturning(value = "execution(* com.binbin.service.impl.*.*(..))",returning="res")
    public void atAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("TransitionAdvice-AfterReturning -后置返回增强! 返回结果 res = " + res.toString());
    }

    @AfterThrowing(value = "execution(* com.binbin.service.impl.*.*(..))",throwing = "e")
    public void error(Exception e){
        System.out.println("TransitionAdvice-AfterThrowing-异常处理增强!" + e);
    }
}

不抛异常时的运行结果:
TransitionAdvice 优先级高before 最先执行,但是AfterReturning 和 After 最后执行,这好比嵌套的括号

TransitionAdvice-Before-前置增强!
Before-前置增强!
业务方法--正在计算除法
业务方法----finally!
AfterReturning -后置返回增强! 返回结果 res = 1.0
After-后置增强!
TransitionAdvice-AfterReturning -后置返回增强! 返回结果 res = 1.0
TransitionAdvice-After-后置增强!

抛异常时的运行结果:
TransitionAdvice 优先级高before 最先执行,但是AfterThrowing 和 After 最后执行,这好比嵌套的括号

TransitionAdvice-Before-前置增强!
Before-前置增强!
业务方法--正在计算除法
业务方法--出现异常
业务方法----finally!
AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
After-后置增强!
TransitionAdvice-AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
TransitionAdvice-After-后置增强!
  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值