首页>>后端>>Spring->Spring中的AOP面向切面编程

Spring中的AOP面向切面编程

时间:2023-11-30 本站 点击:0

AOP 概述

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态的角度考虑程序的运行过程。

这是 Spring 框架中的一个重要的内容,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑之间的耦合度降低,提高程序的复用性,同时提高了开发效率。

AOP 底层,就是采用了动态代理的模式来实现的。 其中有两种代理:JDK 的动态代理、CGLIB 的动态代理

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面植入到主业务逻辑中。所谓的“交叉业务”,就是指通用的、与主业务逻辑无关的代码。例如:日志信息、安全检查、事务、缓存、设置字符编码、发短信 等。

若不使用 AOP 。则会出现代码纠缠不清。不重要的业务功能和重要的业务功能代码混杂咋一起,使得整个程序的结构混乱不清。

例如,转账功能。在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑。而这些业务逻辑和主要的业务逻辑之间并没有直接的关系。但是他们的代码量能达到总代码量的一半甚至更多! 他们的存在,不仅产生了大量“冗余”的代码,还大大干扰了主业务逻辑的结构——转账

AOP 有什么好处?

减少重复

专注业务

注意:AOP 这是面向对象编程的一种补充

不使用 AOP 的开发方式 (实例)

(待补充)

AOP 术语 (掌握)

切面(Aspect)

切面泛指交叉业务逻辑。上述的事务处理、日志功能等,就可以理解为是切面,常用的切面是通知(Advice)(也可以理解为切入到目标代码的时间点)。

实际上就是对主业务逻辑的一种增强

连接点(JoinPoint)

连接点指可以被切面植入的具体方法,通常业务接口中的方法均为连接点

切入点(Pointcut)

切入点指的是 声明的一个或者多个连接点的集合。通过切入点指定的一组方法。

注意: 被标记为 final 的方法是不能作为连接点与切入点的,因为 final 是不能被修改、不能被增强的、

目标对象(Target)

目标对象指的是要被增强的对象,即包含主业务逻辑的类的对象。例如 StudentServiceImpl 的对象若被增强,则该类被称为目标类,该对象被称为目标对象。 若不能增强也就无所谓目标不目标的

通知(Advice)

通知,表示切面的执行时间,Advice 也叫增强。例如:MyInvocationHandler 就可以理解为是一种通知。

换个角度来说,通知定义了增强代码切入到目标代码的时间点: 是目标方法执行前执行、还是目标方法执行后执行……

通知类型不同,切入的时间也不同。切入点定义了切入的位置,通知定义了切入的时间

AspectJ 对 AOP 的实现 (掌握)

步骤

定义业务接口类和实现类

//接口类public interface SomService{    void doSome(String name,int age);}//实现类public class SomeServiceImpl implements SomService{    @Override    public void doSome(String name,int age){        System.out.priontln("执行了业务方法doSome");    }}

定义切面类

//类中定义了若干普通方法,用来增强功能//@Aspectj 是aspectj框架的注解,表示当前类是切面类@Aspectpublic class MyAspect{    /*    @Before:前置通知    属性:value,值 = 切入点表达式,表示切面的执行位置    位置:方法之上    */    public void myBefore(){        System.out.println("前置通知:在目标方法之前执行,例如打印日志信息");    }}

XML 文件的配置

在定义好切面 Aspect 后,要通知 Spring 容器,让容器生成 “目标类” + “切面” 的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,它就会自动扫描到 @Aspect 注解,并按照通知类型与切入点,将其植入,并生成代理

<!-- 声明目标类对象 --><bean id="someServiceTarget" class="com.gg.service.SomeServiceImpl" /><!-- 声明切面类对象 --><bean id="myAspect" class="com.gg.aspect.MyAspect" /><!-- 注册AspectJ的自动代理 --><!-- 声明自动代理生成器,创建代理 --><aop:aspectj-autoproxy />

aop:aspectj-autoproxy 的底层是由 AnnotationAwareAspectJAutoProxyCreator 这个类实现的。

从名字就可以看出,是基于AspectJ 的注解适配自动代理生成器

它的工作原理是,aop:aspectj-autoproxy 通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点表达式找到目标类的目标方法,再由通知类型找到切入的时间点。

@Before 前置通知,方法有 JoinPoint 参数

被 @Before 标记的增强方法,在目标方法执行之前执行。被注接为前置通知的方法,可以包含一个 JoinPoint 类型的参数。该类型的对象本身,就是切入点表达式。通过这个参数,可以获取切入点表达式、方法签名、目标对象等。

补充: 不光是前置通知的方法可以包含 JoinPoint 类型的参数,所有的通知方法都可以包含 JoinPoint 这个参数

/*    1、通知方法:使用了通知注解修饰的方法    2、通知方法可以有参数,但参数不是任意    3、JoinPoint:表示连接点方法       3.1、该参数只能出现在形参的第一位,其他形参可以跟在后面       3.2、任何通知的方法中都可以包含该参数*/@Before(value="execution(* *..SomeServiceImpl.do*(..))")public void myBefore(JoinPoint jp){        //通过JoinPoint获取方法签名,方法定义,方法参数等    System.out.println("连接点的方法定义" + jp.getSignature);    System.out.println("连接点方法参数个数" + jp.getArgs().length);        //获取方法参数信息    Object[] args = jp.getArgs();    for(Object arg:args){        System.out.println(arg);    }        //切面代码功能,例如日志输出,事务处理    System.out.println("前置通知:输出日志");}

@AfterReturning 后置通知 - 注解带有 returning 属性

被 @AfterReturning 注解标记的方法是后置通知,在目标方法执行之后执行。

由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。(此参数用于指定变量名,这个变量名就代表方法的返回值)

所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值参数的变量,并做出修改。该变量最好使用 Object 类型,因为目标方法中的返回值可能是任何类型。

定义接口与实现类

//新增接口中的方法public interface SomService{    void doSome(String name,int age);    String doOther(String name,int age); //新增的方法}//实现类public class SomeServiceImpl implements SomService{    @Override    public String doOther(String name,int age){        System.out.priontln("执行了业务方法doOther");        return "abcd";    }}

定义切面类中方法

@AfterReturning(value="execution(* *..SomeServiceImpl.doOther(..))",returning="result")public void myAfterReturning(){    //修改目标方法中的执行结果    if(result != null){ //此处的result就是returning属性的参数名(自定义参数名),result代表的是:目标方法的返回值,可能是任意类型        String s = (String)result;        result = s.toUpperCase();    }    System.out.println("后置通知:在目标方法执行之的功能增强,例如事务处理。" + result);}

@Around 环绕通知-增强法有 ProceedingJoinPoint 参数

被 @AroundProceedingJoinPoint 注解标记的增强方法,在目标方法执行的前后均执行。被注解为环绕通知的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 里面有一个 proceed() 方法,用于执行目标方法。

若目标方法有返回值,则该方法的返回值就是目标方法的返回值。

最后,环绕增强方法将其返回值返回,实际上就是拦截了目标方法的执行

定义接口与实现类

//新增接口中的方法public interface SomService{    void doSome(String name,int age);    String doOther(String name,int age);     String doFirst(String name,ing age); //新增的方法}//实现类public class SomeServiceImpl implements SomService{    @Override    public String doFirst(String name,int age){        System.out.priontln("执行了业务方法doFirst");        return "doFirst";    }}

定义切面

@Around(value="execution(* *..SomeServiceImpl.doFirst(..))")public Object myAround(ProceedingJoinPoint pjp) throws Throwable{    Object obj = null;        //增强功能    System.out.println("环绕通知:在目标方法执行之前执行,例如输出日志");        //执行目标方法的调用,等同于method.invoke(target,args)    obj = pjp.proceed();        //增强功能    System.out.println("环绕通知:在目标方法之后执行的内容。如事务");        return obj;}

@AfterThrowing 异常通知 - 注解中有 throwing 属性(了解内容)

被 @AfterThrowing 标记的方法,在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

定义接口与实现类

//新增接口中的方法public interface SomService{    void doSome(String name,int age);    void doAfterThrowing(); //新增的方法}//实现类public class SomeServiceImpl implements SomService{    @Override    public void doAfterThrowing(){        System.out.println("执行了业务方法doAfterThrowing" + 10/0);    }}

定义切面类

@AfterThrowing(value="execution(* *..SomeServiceImpl.doAfterThrowing(..))",throwing="ex")public void myAfterThrowing(Throwable ex){    //把异常发生的时间、位置、原因 记录到数据库,日志文件等地方    //可以在异常发生时,把异常信息通过短信、邮件发送给开发人员    System.out.println("异常通知:在目标方法抛出时异常执行,异常原因:" + ex.getMessage());}

@After 最终通知(了解内容)

无论目标方法是否抛出异常,该增强均会被执行

定义接口与实现类

//类中定义了若干普通方法,用来增强功能//@Aspectj 是aspectj框架的注解,表示当前类是切面类@Aspectpublic class MyAspect{    /*    @Before:前置通知    属性:value,值 = 切入点表达式,表示切面的执行位置    位置:方法之上    */    public void myBefore(){        System.out.println("前置通知:在目标方法之前执行,例如打印日志信息");    }}0

定义切面类

//类中定义了若干普通方法,用来增强功能//@Aspectj 是aspectj框架的注解,表示当前类是切面类@Aspectpublic class MyAspect{    /*    @Before:前置通知    属性:value,值 = 切入点表达式,表示切面的执行位置    位置:方法之上    */    public void myBefore(){        System.out.println("前置通知:在目标方法之前执行,例如打印日志信息");    }}1

@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护都比较麻烦。AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。

用法:将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性均可使用该方法名作为切入点。代表的就是 @Pointcut 定义的切入点。

这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即,没有实际作用的方法。

//类中定义了若干普通方法,用来增强功能//@Aspectj 是aspectj框架的注解,表示当前类是切面类@Aspectpublic class MyAspect{    /*    @Before:前置通知    属性:value,值 = 切入点表达式,表示切面的执行位置    位置:方法之上    */    public void myBefore(){        System.out.println("前置通知:在目标方法之前执行,例如打印日志信息");    }}2
原文:https://juejin.cn/post/7096780005295783973


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Spring/4465.html