Spring AOP面向切面编程AspectJ

一、什么是AOP

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。简单来说就是可以在一个程序运行的各个阶段插入新功能,而不用修改原代码。
举个例子:
我们有A,B两个服务,现在需要给所有服务运行前后添加日志模块,实现方法有很多
一种方法是写一个工具类,然后在服务中引入工具类,源代码前后添加工具类调用,这样耦合度很高,不利于后续的扩展
最好的方法就是使用动态代理为原代码扩充新功能
https://blog.csdn.net/qq_33967820/article/details/119317255

而aop就是基于动态代理来实现操作的,spring中常用AspectJ来实现

二、AspectJ的设计理念

Aspect	切面,表示增强的功能,一个程序完成某个非业务功能,常见的切面功能有日志,事务,权限检查等;
JoinPoint	连接点,链接业务方法和切面的未知
Advice	表示切面执行的时间
Pointcut	这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction	引用允许你添加新方法或属性到现有的类中。
Target object	被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving	Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。

三、Advice和Pointcut

5种advice通知
Before前置通知	在一个方法执行之前,执行通知。
After后置通知	在一个方法执行之后,不考虑其结果,执行通知。
AfterReturning返回后通知	在一个方法执行之后,只有在方法成功完成时,才能执行通知。
AfterThrowing抛出异常后通知	在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
Around环绕通知	在建议方法调用之前和之后,执行通知。
Pointcut写法
访问修饰符 方法返回值 包名.类名.方法声明(参数) 异常类型

**其中访问修饰符,包名.类名,异常类型可以省略**
*表示任意多个字符(包括0个)
..用在方法参数中表示任意多个参数,用在包名后表示当前包以及子包路径
+用在类名后,表示当前类以及子类,用在接口后表示当前接口以及实现类

1)execution(* *(..))  
//表示匹配所有方法  
2)execution(public * com. fssx.service.UserService.*(..))  
//表示匹配com.fssx.server.UserService中所有的公有方法  
3)execution(* com.fssx.server..*.*(..))  
//表示匹配com.fssx.server包及其子包下的所有方法

四、AspectJ使用

在maven的pom文件中添加spring-context和spring-aspects依赖

1、使用xml文件配置

创建切面类

package com.fssx.aspectj;
import com.fssx.domain.Student;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspectJ {
    //before通知触发,程序运行之前,around通知之后
    public void Before(){
        System.out.println("before通知触发");
    }
    //after通知触发,程序运行之后、around通知运行后
    public void After(){
        System.out.println("after通知触发");
    }
    // 后置通知,在方法之后执行,能够捕捉目标方法的返回值,根据返回值做处理
    // 因为切面执行的时候,程序已经返回,切面拿到的只是返回值的引用,所以可以修改引用类型,不可以修改基本类型
    public void AfterReturning(Object res){
        Student student = (Student) res;
        System.out.println("afterReturn通知触发");
        if(student != null){
            System.out.println("返回值为"+student);
            student.setName("王二麻子");
        }
    }
    // 异常通知,会在异常发生时触发
    public void AfterThrowing(Exception ex){
        System.out.println("异常错误");
    }
    // 环绕通知,可以在程序运行前,程序运行后,通过ProceedingJoinPoint.proceed()调用服务
    public Object Around(ProceedingJoinPoint pjp){
        Object res = null;
        System.out.println("环绕通知开始");
        try {
            res = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return res;
    }
}

xml文件配置

<!--将切面类加入Spring容器-->
<bean id="aspect1" class="com.fssx.aspectj.MyAspectJ"/>
<aop:config>
        <aop:aspect id="myAspect" ref="aspect1">
            <!--设置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* *(..))"/>
            <!--before通知触发,程序运行之前,around通知之后-->
            <aop:before method="Before" pointcut-ref="pointcut"/>
            <!--after通知触发,程序运行之后、around通知运行后-->
            <aop:after method="After" pointcut-ref="pointcut"/>
            <!--后置通知,在方法之后执行,能够捕捉目标方法的返回值,根据返回值做处理
			因为切面执行的时候,程序已经返回,切面拿到的只是返回值的引用,
			所以可以修改引用类型,不可以修改基本类型-->
            <aop:after-returning method="AfterReturning" 
            pointcut-ref="pointcut" returning="res" />
            <!--异常通知,会在异常发生时触发-->
            <aop:after-throwing method="AfterThrowing" 
            pointcut-ref="pointcut" throwing="ex"/>
            <!--环绕通知,可以在程序运行前后,
            通过ProceedingJoinPoint.proceed()调用服务-->
            <aop:around method="Around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

测试类

public void Test1()
    {
        String config = "spring.xml";
        ApplicationContext context= new ClassPathXmlApplicationContext(config);
        MyService service = (MyService) context.getBean("service1");
        Student res = (Student) service.doSome();
        System.out.println("最终返回值为"+res);
    }

运行结果

before通知触发
环绕通知开始
doSome
环绕通知结束
afterReturn通知触发
返回值为Student{name='李四', sex='男', age=22}
after通知触发
最终返回值为Student{name='王二麻子', sex='男', age=22}
2、使用注解配置

创建切面类

package com.fssx.aspectj;
import com.fssx.domain.Student;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
//注册切面类
@Aspect
public class MyAspectJ2 {
    //  创建切入点,和上文中的配置规则相同
    @Pointcut("execution(* *(..))")
    public void setPointcut(){}

    // 两种方式配置切入点,1是使用value值赋值,value可以省略,2是使用创建好的切入点方法
    //before通知触发,程序运行之前,around通知之后
    @Before("setPointcut()")
    public void Before(){
        System.out.println("before通知触发");
    }
    //after通知触发,程序运行之后、around通知运行后
    @After("setPointcut()")
    public void After(){
        System.out.println("after通知触发");
    }
    // 后置通知,在方法之后执行,能够捕捉目标方法的返回值,根据返回值做处理
    // 因为切面执行的时候,程序已经返回,切面拿到的只是返回值的引用,所以可以修改引用类型,不可以修改基本类型
    @AfterReturning(value = "setPointcut()", returning = "res")
    public void AfterReturning(Object res){
        Student student = (Student) res;
        System.out.println("afterReturn通知触发");
        if(student != null){
            System.out.println("返回值为"+student);
            student.setName("王二麻子");
        }
    }
    // 异常通知,会在异常发生时触发
    @AfterThrowing(value = "setPointcut()",throwing = "ex")
    public void AfterThrowing(Exception ex){
        System.out.println("异常错误");
    }
    // 环绕通知,可以在程序运行前,程序运行后,通过ProceedingJoinPoint.proceed()调用服务
    @Around("setPointcut()")
    public Object Around(ProceedingJoinPoint pjp){
        Object res = null;
        System.out.println("环绕通知开始");
        try {
            res = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return res;
    }
}

xml文件配置

<!--将切面类加入Spring容器-->
<bean id="aspect2" class="com.fssx.aspectj.MyAspectJ2"/>
<!--声明自动代理生成器,会把spring中的所有的目标对象,一次性生成代理对象-->
<aop:aspectj-autoproxy/>

测试类

public void Test1()
    {
        String config = "spring.xml";
        ApplicationContext context= new ClassPathXmlApplicationContext(config);
        MyService service = (MyService) context.getBean("service1");
        Student res = (Student) service.doSome();
        System.out.println("最终返回值为"+res);
    }

运行结果

环绕通知开始
before通知触发
doSome
环绕通知结束
after通知触发
afterReturn通知触发
返回值为Student{name='李四', sex='男', age=22}
最终返回值为Student{name='王二麻子', sex='男', age=22}

xml和注解两种运行结果不一致
原因未知,代解决

三、其他

如果目标类有接口的话,aspectj会自动使用jdk动态代理实现,如果没有接口就会使用cglib
也可以添加
xml可以<aop:config proxy-target-class=“true”>
注解可以<aop:aspectj-autoproxy proxy-target-class=“true”/>
让框架在有接口的情况下也使用cglib

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值