Spring AOP 的配置及使用

Spring AOP 的配置及使用

文章目录

  • Spring AOP 的配置及使用
    • 1 切入点表达式
    • 2 开始前的准备工作:创建 Maven 工程,导入相关依赖
    • 3 基于 xml 配置实现 AOP
      • 3.1 创建被代理类的接口及实现类
      • 3.2 创建通知类
      • 3.3 配置 xml 文件
      • 3.4 测试方法
      • 3.5 环绕通知
    • 4 基于注解实现 AOP
      • 4.1 创建通知类
      • 4.2 配置 xml 文件
      • 4.3 测试方法
      • 4.4 环绕通知
    • 5 完全注解实现 AOP
      • 5.1 创建配置类
      • 5.2 测试方法


上一篇博客介绍了 Spring AOP 的概念、动态代理模式和入门案例,本片博客将详细介绍 AOP 的配置及使用相关的细节,包括 切入点表达式、基于 xml 配置 AOP,基于注解配置 AOP,基于注解配置类配置AOP。

上一篇博客链接:Spring AOP 概念及动态代理模式

博客展示 demo 的 github 地址:https://github.com/Jacks5320/SpringAopStudy


1 切入点表达式

正式进行 AOP 编程之前,先了解一个核心概念————切点表达式。

作用:让 Spring 框架知道是对哪个类的哪个方法进行增强。

语法结构:[权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表)
使用 execution 来解析切入点表达式:execution([权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表))

com.jk.xml.service.impl.AccountServiceImpl.saveAccount() 方法为例,切点表达式如下所示:

标准写法:public void com.jk.xml.service.impl.AccountServiceImpl.saveAccount()

省略访问修饰符:void com.jk.xml.service.impl.AccountServiceImpl.saveAccount()

使用通配符表示任意类型的返回值:* com.jk.xml.service.impl.AccountServiceImpl.saveAccount()

/*	包结构通配

使用通配符表示包名:* *.*.*.*.*.AccountServiceImpl.saveAccount(),有几级包就得写几个 *. 来通配

使用 .. 表示子包:* *..AccountServiceImpl.saveAccount()

*/

/* 类和方法通配

使用通配符来表示类名:* *..*.saveAccount()

使用通配符来表示方法名:* *..AccountServiceImpl.*(),这样只会通配没有参数列表的方法

*/

/* 参数列表通配

基本类型通配:* *..AccountServiceImpl.*(int),表示通配只有一个参数,且参数为 int 类型,如果有多个参数,可以用逗号隔开进行通配。

引用类型:* *..AccountServiceImpl.*(java.util.List)

*/

全通配写法:* *..*.*(..)

以上只是介绍切入点表达式的一些规则和写法,可以按需搭配。

实际开发中的通常写法如下:* com.jk.xml.service.impl.*.*(..),表示业务逻辑层的所有类和方法
  • 通过上面展示的语句,应该对切入点表达式有深刻理解了。
  • 可以通通配符* 代表任意。(任意权限修饰,任意方法,任意包,任意类)。
  • .. 可以表示当前包的子包,也可以表示任意参数列表。
  • 有了切入点表达式的概念以后就可以开始配置 AOP 功能了。

2 开始前的准备工作:创建 Maven 工程,导入相关依赖

<dependencies>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.2.8.RELEASEversion>
    dependency>
    
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.9.5version>
    dependency>
    
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-testartifactId>
        <version>5.2.8.RELEASEversion>
    dependency>
dependencies>

3 基于 xml 配置实现 AOP

3.1 创建被代理类的接口及实现类

public interface AccountService {
    //模拟保存
    void saveAccount();
    //模拟更新
    void updateAccount(int id);
    //模拟删除
    int deleteAccount();
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void saveAccount() {
        System.out.println("保存方法执行了。。。");
        // int a = 1/0;		//制造异常通知
    }

    @Override
    public void updateAccount(int id) {
        System.out.println("更新方法执行了。。。");
    }

    @Override
    public int deleteAccount() {
        System.out.println("删除方法执行了。。。");
        return 0;
    }
}

3.2 创建通知类

public class AopUtil {
    //前置通知
    public void beforeAdvice() {
        System.out.println("<==before,前置通知加上了==>");
    }
    //后置通知
    public void afterReturningAdvice() {
        System.out.println("<==afterReturning,后置通知加上了==>");
    }
    //异常通知
    public void afterThrowingAdvice() {
        System.out.println("<==afterThrowing,异常通知加上了==>");
    }
    //最终通知
    public void afterAdvice() {
        System.out.println("<==after,最终通知加上了==>");
    }
    //环绕通知
    public Object aroundAdvice(ProceedingJoinPoint jp) {
        Object rtValue = null;
        try {
            Object[] args = jp.getArgs();       //获取执行方法的参数

            System.out.println("<==around,环绕通知在之前加上了,before==>");       //写在 proceed 之前表示前置通知

            rtValue = jp.proceed(args);     //明确调用切入点方法

            System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知

            return rtValue;

        } catch (Throwable t) {
            System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>");//写在 catch 里面表示异常通知
            throw new RuntimeException(t);
        } finally {
            System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 finally 里面表示最终通知
        }
    }
}

3.3 配置 xml 文件


<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="accountService" class="com.jk.xml.service.impl.AccountServiceImpl"/>
    
    <bean id="aopUtil" class="com.jk.xml.utils.AopUtil"/>
    
    <aop:config>
        
        <aop:aspect id="logAdvice" ref="aopUtil">
            
            <aop:before method="beforeAdvice" pointcut="execution(* com.jk.xml.service.impl.*.saveAccount(..))"/>
            
            
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="aopPoint"/>
            
            
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="aopPoint"/>
            
            
            <aop:after method="afterAdvice" pointcut-ref="aopPoint"/>
            
            <aop:pointcut id="aopPoint" expression="execution(* com.jk.xml.service.impl.*.saveAccount(..))"/>
        aop:aspect>
    aop:config>
beans>
  • 要使用 AOP 配置功能需要引入 aop 名称空间。
  • 在 xml 配置文件中要将被代理对象和增强类同时实例化。
  • 使用 标签表示是 AOP 的配置。
  • 使用 标签中的 标签表示配置的切面,id 属性是这个切面的唯一标识,可以被其他切面引用,ref 属性指定增强类的 id 属性。
  • 使用 标签中的通知类标签 xxx 表示通知类型,method 属性表示通知类中的增强方法,pointcut 属性使用切入点表达式指向增强的方法。
  • 可以使用 标签提取通用的切入点表达式,然后使用通知类型标签 中的 pointcut-ref 属性来引入公共切入点表达式的 id 属性。
  • 关于 标签,如果写在 标签外部,必须在引用切面的前面定义,如果是在内部,则可以放任意位置,为了规范,一般都放在引用位置的前面。

3.4 测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:xml/bean2.xml"})
public class TestBean2 {
    @Autowired
    AccountService as;

    //测试 前置通知、后置通知、异常通知和最终通知
    @Test
    public void testBeforeAdvice(){
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}

打印结果:

<==before,前置通知加上了==>
保存方法执行了。。。
<==afterReturning,后置通知加上了==>
<==after,最终通知加上了==>
更新方法执行了。。。
删除方法执行了。。。
  • 因为切入点表达式 execution(* com.jk.xml.service.impl.*.saveAccount(..)) 表示值拦截 saveAccount() 方法,所以只有这个方法被增强了。
  • 需要注意的是:后置通知afterReturning 和 异常通知 afterThrowing 永远不会同时出现。

3.5 环绕通知

之所以环绕通知单独提取出来,是因为,在环绕通知中可以涵盖其他通知,而且长得更像上篇博客介绍的动态代理模式。

public class AopUtil {
    public Object aroundAdvice(ProceedingJoinPoint jp) {
        Object rtValue = null;
        try {
            Object[] args = jp.getArgs();//获取执行方法的参数

            System.out.println("<==around,环绕通知在之前加上了,before==>");//写在 proceed 之前表示前置通知

            rtValue = jp.proceed(args);//明确调用切入点方法

            System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知

            return rtValue;

        } catch (Throwable t) {
            System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>");//写在 catch 里面表示异常通知
            throw new RuntimeException(t);
        } finally {
            System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 finally 里面表示最终通知
        }
    }
}
  • JoinPoint 的类视图:

Spring AOP 的配置及使用_第1张图片

  • ProceedingJoinPointJoinPoint 的子接口,都是由 AspectJ 提供,用于管理连接点的。
  • 只支持作为环绕通知的参数,如果写在其他通知中会出现异常:java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
  • 有两个实现类:JoinPointImplMethodInvocationProceedingJoinPoint。这里就不展开了,介绍几个与代理相关的方法:
    • getTarget().getClass():获取被代理对象
    • getThis().getClass():获取代理的对象
    • getSignature().getName():获取当前拦截的方法名
    • getArgs():获取当前拦截方法的参数数组,可以使用索引获取参数。

4 基于注解实现 AOP

这里依然使用上面的实现类和接口。

4.1 创建通知类

@Component
@Aspect//表示当前类是一个切面类
public class AopUtil {

    //指定切入点表达式
    @Pointcut("execution(* com.jk.annotation.service.impl.*.*(..))")
    private void aopAdvice() {
    }

    //前置通知
    @Before("aopAdvice()")
    public void beforeAdvice() {
        System.out.println("<==before,前置通知加上了==>");
    }

    //后置通知
    @AfterReturning("aopAdvice()")
    public void afterReturningAdvice() {
        System.out.println("<==afterReturning,后置通知加上了==>");
    }

    //异常通知
    @AfterThrowing("aopAdvice()")
    public void afterThrowingAdvice() {
        System.out.println("<==afterThrowing,异常通知加上了==>");
    }

    //最终通知
    @After("aopAdvice()")
    public void afterAdvice() {
        System.out.println("<==after,最终通知加上了==>");
    }
}
  • @Aspect 等价于 xml 中的
  • @Pointcut 等价于 xml 中的
  • @Before("aopAdvice()") 等价于 xml 中的

4.2 配置 xml 文件


<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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan  base-package="com.jk.annotation"/>
    
    <aop:aspectj-autoproxy/>
beans>

4.3 测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:annotation/bean.xml")
public class TestDemo {
    @Autowired
    private AccountService as;
    //测试注解
    @Test
    public void testAnnotation(){
        as.saveAccount();
    }
}

执行结果:

<==before,前置通知加上了==>
保存方法执行了。。。
<==afterReturning,后置通知加上了==>
<==after,最终通知加上了==>
  • 效果与基于 xml 配置实现的 AOP 一样。

4.4 环绕通知

@Component
@Aspect//表示当前类是一个切面类
public class AopUtil {

    @Around("aopAdvice()")
    public Object aroundAdvice(ProceedingJoinPoint jp) {
        Object rtValue = null;
        try {
            Object[] args = jp.getArgs();       //获取执行方法的参数

            System.out.println("<==around,环绕通知在之前加上了,before==>");       //写在 proceed 之前表示前置通知

            rtValue = jp.proceed(args);         //明确调用切入点方法

            System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知

            return rtValue;

        } catch (Throwable t) {
            System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>"); //写在 catch 里面表示异常通知
            throw new RuntimeException(t);
        } finally {
            System.out.println("<==around,环绕通知在最后加上了,after==>");        //写在 finally 里面表示最终通知
        }
    }
}

执行结果:

<==around,环绕通知在之前加上了,before==>
保存方法执行了。。。
<==around,环绕通知在之后加上了,afterReturning==>
<==around,环绕通知在最后加上了,after==>

5 完全注解实现 AOP

完全注解配置的意思指的是不需要任何的 xml 配置都可以运行,这里只需要添加一个配置注解类,删除配置文件即可。

5.1 创建配置类

@Configuration
@ComponentScan(basePackages = "com.jk.annotation")
@EnableAspectJAutoProxy     //等同于    
public class SpringConfig {
}

5.2 测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class TestDemo2 {

    @Autowired
    AccountService as;

    @Test
    public void fullAnnotation(){
        as.saveAccount();
    }
}
  • 测试方法中只把 @ContextConfiguration 的值换成了配置类。

以上就是 AOP 相关的配置和使用方法了,希望能帮助你理解 AOP 的概念。

你可能感兴趣的:(SSM框架,spring,aop,AspectJ)