Spring框架-aspect实现AOP

aspectj实现AOP

  • 在Spring中进行AOP的操作,使用的是AspectJ来具体实现。AspectJ是一个面向切面的编程框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

  • AspectJ不是Spring的内部集成,而是和Spring一起配合使用来实现AOP操作。

  • Spring2.0以后才增加了对AspectJ的支持。

  • Spring中使用AspectJ实现AOP操作,需要导入额外的aspectj的jar包。

<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.6</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.20</version>
        </dependency>
说明:

spring出现Caused by: java.lang.NoClassDefFoundError: 
org/aspectj/weaver/BCException的错误时,可能是因为Spring缺少aspectjweaver.jar包

aspectjrt.jar包是在使用jdk时所需要的jar文件,经测试,支持jdk1.7,
一般使用spring语句调入时会用到

aopalliance-1.0.jar的作用
这个包是AOP联盟的API包,里面包含了针对面向切面的接口。
通常Spring等其它具备动态织入功能的框架依赖此包。
  • 使用AspectJ实现AOP有二种方式:

    —基于AspectJ的XML配置

    —基于AspectJ的注解

基于AspectJ的XML配置

package com.sin.pojo;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class User1 {

    private String userName;

    public User1() {
    }

    public User1(String userName) {
        super();
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void login() {
        System.out.println("【" + this.userName + "】" + "用户正在登录。");
    }
}
package com.sin.service.impl;

import org.aspectj.lang.JoinPoint;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class MySINAspect {

    //此方法作为前置通知
    public void before(JoinPoint joinPoint){
        //获取被调用的类名
        String targetClassName=joinPoint.getTarget().getClass().getName();
        //获取被调用的方法名
        String targetMethodName=joinPoint.getSignature().getName();
        //日志格式字符串
        System.out.println("【前置通知】"+targetClassName+"类的"+targetMethodName+"方法开始执行。");
    }

}

配置文件中导入aop schema约束

applicationContext.xml

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

基于XML的声明式AspectJ

  • 配置切面

配置切面使用的是aop:aspect元素,该元素会将一个定义好事务Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。

属性名称描述
id用于定义该切面的唯一标识
ref用于引用普通的Spring Bean
  • 配置切入点

切入点是通过aop:pointcut元素来定义的。当aop:pointcut元素作为aop:config元素的子元素定义时,标识切入点是全局切入点。它可以被多个切面所共享;当aop:pointcut元素作为aop:aspect元素的子元素定义时,表示该切入点值对当前切面有效。aop:pointcut元素的属性及其描述如下:

属性名称描述
id用于指定切入点的唯一标识
ref用于指定切入点关联的切入点表达式
  • 配置通知

分别使用aop:aspect的子元素配置5中常用通知,

属性名称描述
pointcut该属性用于指定一个切入点,Spring将在匹配该表达式的连接点时织入该通知
pointcut-ref该属性用于指定一个已经存在的切入点名称,如配置该代码中myPointCut。通常pointcut和pointcut-ref两个属性只需要使用其中之一
method该属性指定一个方法名,指定将切面Bean中的该方法转换为增强
throwing该属性只对 元素有效,它用与指定一个形象名,异常通知方法可以通过该形参访问目标方法所抛出的异常
returning该属性只对元素有效,它用于指定一个形参名,后置通知方法可以该形参访问目标方法的返回值

配置切面和业务bean

  <!-- 配置切面bean -->
    <bean id="user1" class="com.sin.pojo.User1"></bean>
    <!-- 配置业务bean -->
    <bean id="mySINAspect" class="com.sin.service.impl.MySINAspect"></bean>

aop配置

 <!-- 使用xml配置方式完成aop的相关配置 -->
    <aop:config>
        <!-- 首先配置切入点(针对目标对象中的目标方法定义相关的切入规则) -->
        <aop:pointcut id="pointCut" expression="execution(* com.sin.pojo.*.*(..))"/>
        <!-- 再配置切面 -->
        <aop:aspect ref="mySINAspect">
            <!-- 针对切入点进行相关通知的配置 -->
            <aop:before method="before" pointcut-ref="pointCut"/>
        </aop:aspect>

    </aop:config>

提示! Spring AOP中expression表达式的语法格式:

//匹配指定包中所有类中的所有方法
execution(* com.sin.service.*.*(..))

//匹配指定包及子包中所有类中的所有方法
execution(* com.sin.service..*(..))")

//匹配UserServiceImpl类中的所有方法
execution(* com.sin.service.UserServiceImpl.*(..))")

//匹配UserServiceImpl类中的所有公共的方法
execution(public * com.sin.service.UserServiceImpl.*(..))")

//匹配UserServiceImpl类中的所有公共方法并且返回值为int类型
execution(public int com.sin.service.UserServiceImpl.*(..))")

//匹配UserServiceImpl类中第一个参数为int类型的所有公共的方法
execution(public * com.sin.service.UserServiceImpl.*(int , ..))") 

//多个表达式的关联使用
execution (* com.sin.servicei.UserServiceImple.save*(..)) || execution (* com.sin.servicei.UserServiceImple.update*(..))

测试

@Test
public void test1(){
    //加载SpringAop.xml配置
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取配置中的user实例
    User1 user=(User1) context.getBean("user1");
    user.setUserName("张三");
    user.login();

}

测试结果:

在这里插入图片描述

基于AspectJ的注解配置

  • 目标类接口
package com.sin.service;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public interface SomeService {
    public String doSomeSimple(String str, Integer num);


    public String doSomeAround(String str, Integer num);

    public Integer doSomeAfterThrowing(Integer num0, Integer num1);

}
  • 目标类实现类
package com.sin.service.impl;

import com.sin.service.SomeService;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSomeSimple(String str, Integer num) {
        System.out.println("===目标方法doSomeSimple(str=" + str + ", num=" + num + ")===");
        return str + num;
    }

    @Override
    public String doSomeAround(String str, Integer num) {
        System.out.println("===目标方法doSomeAround(str=" + str + ", num=" + num + ")===");
        return str + num;
    }

    @Override
    public Integer doSomeAfterThrowing(Integer num0, Integer num1) {
        System.out.println("===目标方法doSomeAfterThrowing(num0="
                + num0 + ", num1=" + num1 + ")===");
        return num0 / num1;
    }

}
package com.sin.service.impl;

public class CglibServiceImpl {
    public void doSome(){
        System.out.println("------CglibServiceImpl.doSome()------");
    }
}
  • MyAspect.java切面类
package com.sin.service.impl;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;
import java.util.Date;

/**
 * @Aspect:aspectJ框架中的注解
 *  表示:当前类是切面类(用来给业务代码增强功能的类,这里写有切面的功能代码)
 *  位置:类上
 */
@Aspect
public class MyAspect {

    /**
     * @Before:前置增强
     *  属性:
     *      value:切入点表达式
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
     */
    @Before(value = "execution(public String com.sin.service.impl.SomeServiceImpl.doSomeSimple(String,Integer))")
    public void myBefore(){
        // 书写切面要执行的功能增强代码
        System.out.println("切面前置增强:输出执行开始时间:" + new Date());
    }

    /**
     * 指定参数中的给定参数:JoinPoint(业务方法)
     *  作用:可在增强里获得业务方法的信息
     *  使用须知:JoinPoint是由框架赋予的,必须放在参数列表第一位
     */
    @Before(value = "execution(String do*Simple(..))")
    public void myBefore_02(JoinPoint point){
        // 书写切面要执行的功能增强代码
        // 获取业务方法完整定义
        System.out.println("业务方法定义:" + point.getSignature());
        System.out.println("业务方法名称:" + point.getSignature().getName());
        System.out.println("业务方法参数列表:");
        Arrays.stream(point.getArgs()).forEach(System.out::println);

        System.out.println("切面前置增强02:输出执行开始时间:" + new Date());
    }

    /**
     * @AfterReturning:后置增强
     *  属性:
     *      1、value:切入点表达式
     *      2、returning:自定义变量表示方法返回值(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、有参数(推荐Object)
     */
    @AfterReturning(value = "execution(* do*Simple(..))", returning = "res555")
    public void myAfterReturning(Object res555){
        // 书写切面要执行的功能增强代码
        System.out.println("切面后置增强:业务方法执行后返回值为:" + res555);
    }

    /**
     * @Around:环绕增强
     *  属性:
     *      1、value:切入点表达式
     *  要求:
     *      1、公共方法 public
     *      2、方法必须有返回值(推荐Object)
     *      3、方法名称自定义
     *      4、有固定参数(ProceedingJoinPoint extends JoinPoint)
     *
     * 环绕增强等同于JDK动态代理的InvocationHandler接口
     * 参数ProceedingJoinPoint等同于Method
     */
    @Around(value = "execution(* do*Around(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 定义返回值
        Object result = null;
        // 1、环绕增强之前置增强
        System.out.println("环绕增强之前置增强:业务方法执行前记录的时间:" + new Date());
        System.out.println("环绕增强之前置增强:业务方法入参:[");
        Arrays.stream(pjp.getArgs()).forEach(System.out::println);
        System.out.println("]");
        // 2、目标方法调用
        result = pjp.proceed(); // 相当于method。invoke()
        // 3、环绕增强之后置增强
        System.out.println("环绕增强之后置增强:业务方法执行结束提交事务");
        // 返回执行结果
        return result;
    }

    /**
     * @AfterThrowing:异常增强
     *  属性:
     *      1、value:切入点表达式
     *      2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、有一个参数是Exception,如果还有就是JoinPoint
     *  特点:
     *      1、在目标方法抛出异常时执行
     *      2、可作异常监控
     */
    @AfterThrowing(value = "execution(* do*AfterThrowing(..))", throwing = "exception666")
    public void myAfterThrowing(Exception exception666){
        // 书写切面要执行的功能增强代码
        System.out.println("切面异常增强:业务方法运行时抛出异常:" + exception666.getMessage());
    }

    /**
     * @After:最终增强
     *  属性:
     *      1、value:切入点表达式
     *      2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
     *  特点:
     *      1、总是执行
     *      2、可作异常监控
     */
    @After(value = "execution(* do*After*(..))")
    public void myAfter(){
        // 书写切面要执行的功能增强代码
        System.out.println("切面最终增强:总是会被执行");
        // 一般做资源清除工作的
    }

    /**
     * @Pointcut:定义和管理切入点(复用:多处使用同一个切入点时定义一个公用的切入点)
     *  属性:
     *      1、value:切入点表达式
     *  特点:
     *      当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
     *      其它的通知中, value属性就可以使用这个方法名称,代替切入点表达式
     */
    @Pointcut(value = "execution(* do*(..))")
    private void myPointcutComm(){
        //无需代码
    }

    // 使用@Pointcut定义的切入点
    @After(value = "myPointcutComm()")
    public void myAfterCommon(){
        // 书写切面要执行的功能增强代码
        System.out.println("***通用的切面最终增强:总是会被执行***");
    }
}

applicationContext.xml

    <!--对象交给Spring容器,由Spring容器统一创建、管理-->
    <!--目标对象-->
    <bean id="someService" class="com.sin.service.impl.SomeServiceImpl" />
    <bean id="cglibService" class="com.sin.service.impl.CglibServiceImpl" />
    <!--切面对象-->
    <bean id="myAspect" class="com.sin.service.impl.MyAspect" />
    <!--声明自动代理生成器:使用aspectJ框架内部功能创建目标对象的代理对象
        创建代理对象是在内存中实现的,修改目标对象在内存中的结构来创建代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy:会把Spring容器中所有目标对象,一次性都生成代理对象

        默认没有接口使用cglib,有接口使用jdk
    -->
    <aop:aspectj-autoproxy />

    <!--
        如果期望目标类有接口还依然使用cglib动态代理
        proxy-target-class="true":告诉框架死活给我用cglib代理
    -->
    <!--<aop:aspectj-autoproxy proxy-target-class="true" />-->

测试类

@Test
    public void test(){
        ApplicationContext applicationContext = new
                ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("==============没有接口时是CGLIB动态代理=====================");
        CglibServiceImpl cglib =
                (CglibServiceImpl) applicationContext.getBean("cglibService");
        System.out.println("没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + cglib.getClass().getName());

        SomeService proxy = (SomeService) applicationContext.getBean("someService");
        System.out.println("存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + proxy.getClass().getName());
        System.out.println("\n====================前置+后置===========================");
        System.out.println("proxy.doSomeSimple()最终执行结果==" + proxy.doSomeSimple("哦吼吼", 20));
        System.out.println("\n=====================环---绕===========================");
        System.out.println("proxy.doSomeAround()最终执行结果==" + proxy.doSomeAround("咚恰恰", 666));
        System.out.println("\n=====================异常+最终===========================");
        System.out.println("proxy.doSomeAfterThrowing()最终执行结果==" + proxy.doSomeAfterThrowing(12, 1));
    }

测试结果

在这里插入图片描述

第一步 @Around 环绕前执行
第二步 @Before 目标方法之前执行
第三步 执行目标方法
第四步:
		1、假如执行目标出现异常 执行@AfterThrowing
		2、假如执行目标正常,而且有返回值 执行@AfterReturning

第五步 @After 目标方法之后执行(始终执行),相当于finally部分
第六步 @Around 环绕后执行

重要提示!

Spring5.2.2 GA版中针对通知执行的顺序问题,在XML配置中和Spring4系列是一致的,但是在使用注解的时候,就是上面的这个顺序过程。

  • 41
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陆卿之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值