Spring AOP(注解方式和XML方式)


Spring框架一般都是基于AspectJ实现AOP操作。

AspectJ不是Spring组成部分,是一个独立的AOP框架,一般把AspectJ和Spring框架一起使用,来进行AOP操作。

在项目工程中引入AOP相关依赖
在这里插入图片描述

一、切入点表达式

execution ,匹配方法的执行(常用),让spring知道对哪个类中的哪个方法进行增强。

execution(表达式)

execution([权限修饰符] [返回值类型] 包名.类名.方法名(参数列表))

全匹配方式:

public void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)

访问修饰符可以省略:

void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)

返回值可以使用*号,表示任意返回值:

* com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)

包名可以使用*号,表示任意包,有几级包,需要写几个*

* *.*.*.*.AccountServiceImpl.saveAccount(com.arbor.domain.Account)

使用..来表示当前包,及其子包:

* com..AccountServiceImpl.saveAccount(com.arbor.domain.Account)

类名可以使用*号,表示任意类:

* com..*.saveAccount(com.arbor.domain.Account)

方法名可以使用*号,表示任意方法:

* com..*.*( com.arbor.domain.Account)

参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数:

* com..*.*(*)

参数列表可以使用..表示有无参数均可,有参数可以是任意类型:

* com..*.*(..)

全通配方式:

* *..*.*(..)

通常情况下,是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类:

execution(* com.arbor.service.impl.*.*(..))

二、基于注解方式

2.1 常用注解

@Aspect:把当前类声明为切面类

@Before:把当前方法看成是前置通知
@AfterReturning:把当前方法看成是后置通知
@AfterThrowing:把当前方法看成是异常通知
@After:把当前方法看成是最终通知

上面四个注解的参数:
value:用于指定切入点表达式,还可以指定切入点表达式的引用

使用案例

① 创建一个Spring的配置文件,开启注解扫描和开启aop

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

    <!-- 告知 spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="run.arbor"/>
    <!-- 开启 spring 对注解 AOP 的支持 -->
    <aop:aspectj-autoproxy/>
    
</beans>

② 创建一个类和方法,对该类中的方法做增强

@Component
public class User {
    public void add() {
        System.out.println("add....");
    }
}

③ 创建增强类,在增强类中创建方法,不同的方法代表不同的通知类型,并配置不同类型的通知

@Component
@Aspect // 生成代理对象,表明当前类是一个切面类
public class UserProxy {
    // @Before表示前置通知,会在方法执行前执行
    @Before("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    public void before() {
        System.out.println("before....");
    }
}

④ 测试

@Test
public void testAnno() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    user.add();
}

可以得到以下结果:
在这里插入图片描述
⑤ 其他几个通知大同小异:

@Component
@Aspect // 生成代理对象,表明当前类是一个切面类
public class UserProxy {

    // @Before表示前置通知,会在方法执行前执行
    @Before("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    public void before() {
        System.out.println("before....");
    }
	
	// 后置通知,方法执行后后执行,如果出现异常,则不执行
    @AfterReturning("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning....");
    }

	// 异常通知,方法出现异常会执行
    @AfterThrowing("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing....");
    }

	// 最终通知,不管方法有没有出现异常都会执行
    @After("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    public void after() {
        System.out.println("after....");
    }
}

可以得到结果:
在这里插入图片描述

最终通知是方法执行完毕后执行,而后置通知是在方法返回之后执行,所以最终通知要优先于后置通知和异常通知
通知的执行顺序:
前置通知 → 执行方法 → 最终通知 → 后置通知/异常通知

2.2 环绕通知

spring提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
在环绕通知执行时,spring提供该接口的实现类对象,直接使用即可

这里通知的执行顺序和上面的一样

常用注解
@Around:把当前方法看成是环绕通知

@Around("execution(* run.arbor.spring5.aop_anno.User.add(..))")
public Object around(ProceedingJoinPoint pjp) {
    // 定义返回值,被增强的方法的返回值
    Object rtVal = null;
    try {
        // 获取被增强方法的参数列表
        Object[] args = pjp.getArgs();
        System.out.println("环绕通知的前置通知....");
        // 执行被增强的方法,并传入参数,如果确定没有返回值或者没有参数可以不写
        rtVal = pjp.proceed(args);
        System.out.println("环绕通知的后置通知....");
    } catch (Throwable e) {
        System.out.println("环绕通知的异常通知....");
        e.printStackTrace();
    } finally {
        System.out.println("环绕通知的最终通知....");
    }
    // 将被增强方法的返回值返回
    return rtVal;
}

可以得到结果:
在这里插入图片描述

2.3 切入点表达式注解

@Pointcut:指定切入点表达式

@Component
@Aspect
public class UserProxy {

    // 定义切除点表达式
    @Pointcut("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    private void pt1() {}

    @Around("pt1()") //注意:千万别忘了写括号
    public Object around(ProceedingJoinPoint pjp) {
        // 定义返回值,被增强的方法的返回值
        Object rtVal = null;

        try {
            // 获取被增强方法的参数列表
            Object[] args = pjp.getArgs();

            System.out.println("环绕通知的前置通知....");

            // 执行被增强的方法,并传入参数,如果确定没有返回值或者没有参数可以不写
            rtVal = pjp.proceed(args);

            System.out.println("环绕通知的后置通知....");
        } catch (Throwable e) {
            System.out.println("环绕通知的异常通知....");
            e.printStackTrace();
        } finally {
            System.out.println("环绕通知的最终通知....");
        }
        // 将被增强方法的返回值返回
        return rtVal;
    }
}

2.4 多个增强类的优先级

@Order:设置增强类的优先级,值越小,优先级越高

定义另一个增强类,并设置优先级:

@Component
@Aspect
@Order(1)	// 设置优先级
public class UserProxy2 {

    @Pointcut("execution(* run.arbor.spring5.aop_anno.User.add(..))")
    private void pt1() {}

    @Before("pt1()")
    public void before() {
        System.out.println("UserProxy2.before....");
    }

    @AfterReturning("pt1()")
    public void afterReturning() {
        System.out.println("UserProxy2.afterReturning....");
    }

    @AfterThrowing("pt1()")
    public void afterThrowing() {
        System.out.println("UserProxy2.afterThrowing....");
    }

    @After("pt1()")
    public void after() {
        System.out.println("UserProxy2.after....");
    }

}

给之前的UserProxy类设置为3:

@Component
@Aspect
@Order(3)
public class UserProxy {
	...
}

执行测试方法:
在这里插入图片描述
可以看到:

@Order的值越小的增强类,会优先执行。
而同一个类中,同时有环绕通知和普通的通知时,环绕通知会优先执行

2.5 不使用XML文件开启AOP注解

定义一个配置类,使用@EnableAspectJAutoProxy注解

@Configuration
@ComponentScan(basePackages="run.arbor")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

三、基于XML配置文件方式

3.1 常用配置

<aop:config>:声明开始aop的配置

<aop:aspect>:用于配置切面

参数:
id:给切面提供一个唯一标识
ref:引用配置好的通知类bean的id

<aop:pointcut>:配置切入点表达式(指定对哪些类的哪些方法进行增强)

参数:
expression:定义切入点表达式
id:给切入点表达式提供一个唯一标识

<aop:before>:配置前置通知(指定增强的方法在切入点方法之前执行)
<aop:after-returning>:用于配置后置通知
<aop:after-throwing>:用于配置异常通知
<aop:after>:用于配置最终通知

上面四个配置的参数:
method:指定通知类中的增强方法名称
poinitcut:指定切入点表达式
ponitcut-ref:指定切入点的表达式的引用

配置文件的约束:

<?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"
	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">
    
</beans>

使用案例

① 创建一个类和方法,对该类中的方法做增强

public class Book {
    public void buy() {
        System.out.println("buy....");
    }
}

② 创建增强类,用于增强对应的方法

public class BookProxy {

    public void before() {
        System.out.println("before....");
    }

    public void afterReturning() {
        System.out.println("afterReturning....");
    }

    public void afterThrowing() {
        System.out.println("afterThrowing....");
    }

    public void after() {
        System.out.println("after....");
    }
}

③ 创建Spring的配置文件,bean2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 创建Book对象 -->
    <bean id="book" class="run.arbor.spring5.aop_xml.Book"/>
    <bean id="bookProxy" class="run.arbor.spring5.aop_xml.BookProxy"/>

    <!-- 配置AOP的增强 -->
    <aop:config>

        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* run.arbor.spring5.aop_xml.Book.buy(..))" id="pt1"/>

        <!-- 配置切面 -->
        <aop:aspect ref="bookProxy">
            <!--配置通知的类型要写在此处-->
            <aop:before method="before" pointcut-ref="pt1"/>
            <aop:after method="after" pointcut-ref="pt1"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pt1"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>
        </aop:aspect>

    </aop:config>
</beans>

④ 测试

@Test
public void testXml() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
    Book book = context.getBean("book", Book.class);
    book.buy();
}

运行可以看到:
在这里插入图片描述

3.2 环绕通知

<aop:around>:用于配置环绕通知

参数:
method:通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用

spring提供的一种可以在代码中手动控制增强代码什么时候执行的方式,通常情况下,环绕通知都是独立使用的。

① 创建环绕通知类

public class BookProxy {
    public Object around(ProceedingJoinPoint pjp) {
        // 定义返回值,被增强的方法的返回值
        Object rtVal = null;

        try {
            // 获取被增强方法的参数列表
            Object[] args = pjp.getArgs();

            System.out.println("环绕通知的前置通知....");

            // 执行被增强的方法,并传入参数,如果确定没有返回值或者没有参数可以不写
            rtVal = pjp.proceed(args);

            System.out.println("环绕通知的后置通知....");
        } catch (Throwable e) {
            System.out.println("环绕通知的异常通知....");
            e.printStackTrace();
        } finally {
            System.out.println("环绕通知的最终通知....");
        }
        // 将被增强方法的返回值返回
        return rtVal;
    }
}

② 编写XML文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 创建Book对象 -->
    <bean id="book" class="run.arbor.spring5.aop_xml.Book"/>
    <bean id="bookProxy" class="run.arbor.spring5.aop_xml.BookProxy"/>

    <!-- 配置AOP的增强 -->
    <aop:config>

        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* run.arbor.spring5.aop_xml.Book.buy(..))" id="pt1"/>

        <!-- 配置切面 -->
        <aop:aspect ref="bookProxy">
            <!--配置通知的类型要写在此处-->
            <aop:around method="around" pointcut-ref="pt1"/>
        </aop:aspect>

    </aop:config>
</beans>

③ 测试
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

VincentHu_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值