Spring AOP配置过程详解

4 篇文章 1 订阅

AOP中的专业术语

Joinpoint(连接点):连接点是指哪些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

Pointcut(切入点):指我们要对哪些连接点进行拦截的定义

Advice(通知/增强):拦截到连接点之后要做的事情
通知的类型:前置通知(在调用invoke方法前),后置通知(在调用invoke方法后),异常通知(在try语句中),最终通知(在finally语句中),环绕通知(整个增强方法)。

Target(目标对象):代理的目标对象。

Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面):是切入点和通知(引介)的结合。

AOP整体配置过程

  1. 在bean.xml中将切面类注入到Spring容器中

    <!-- 配置Logger类 -->
    <bean id="logger" class="yonmin.utils.Logger"></bean>
    
  2. 使用<aop:config>标签配置AOP

    <!-- 配置Logger类 -->
    <bean id="logger" class="yonmin.utils.Logger"></bean>
    
    <!-- 配置AOP -->
    <aop:config>
        ...
    </aop:config>
    
  3. <aop:config>标签内部配置切入点(可省略),即被增强的方法。
    id属性是通知引用切入点时的标识,expression是切面表达式,需导入aspectjweaver,具体写法见下文。
    注意:切入点可以写在配置切面的内部,也可以写在外部。但是写在外部时要将配置切入点写在配置切面的前面。

    <!-- 配置Logger类 -->
    <bean id="logger" class="yonmin.utils.Logger"></bean>
    
    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
    </aop:config>
    
  4. <aop:config>标签内部配置切面,即切入点和通知(引介)的结合。切面的ref属性是导入Ioc容器中的切面类的id

    <!-- 配置AOP -->
    <aop:config>
    
        <!-- 配置切入点 -->
        <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
        
        <!-- 配置切面 -->
        <aop:aspect id="logAdive" ref="logger">
            ...
        </aop:aspect>
    </aop:config>
    
  5. <aop:aspect>标签内部配置通知(增强)。
    例如配置前置通知:<aop:before method="" pointcut-ref=""></aop:before>
    其中method属性是切面类中的方法名,pointcut-ref属性是配置的切入点的id。也可以不使用配置好的切入点,见下文。
    注意配置环绕通知后可能会发生切入点方法没有被执行,但是环绕通知被执行了。此时则需要修改切面类中的环绕通知方法,具体方法在下文。

    <!-- 配置Logger类 -->
    <bean id="logger" class="yonmin.utils.Logger"></bean>
    
    <!-- 配置AOP -->
    <aop:config>
    
        <!-- 配置切入点 -->
        <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
        
        <!-- 配置切面 -->
        <aop:aspect id="logAdive" ref="logger">
            <!-- 前置通知:在切入点方法执行之前执行 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
            <!-- 后置通知:在切入点方法正常执行之后通知 -->
            <aop:after-returning method="afterPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <!-- 异常通知:在切入点方法执行产生异常后通知 -->
            <aop:after-throwing method="exceptPrintLog" pointcut-ref="pt1"></aop:after-throwing>
            <!-- 最终通知:无论切入点方法是否正常运行都通知 -->
            <aop:after method="finalPrintLog" pointcut-ref="pt1" ></aop:after>
            <!-- 配置环绕通知 -->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>
    

项目中的角色分工

  • 开发阶段(我们做的)
    编写核心业务代码(开发主线):大部分程序员都能做,要求熟悉业务需求。
    把公用代码抽取出来,制作成通知。(开发阶段最后再做):A0P编程人员来做。
    在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。

  • 运行阶段( Spring框架完成的)
    Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

基于XML的AOP配置

Spring中基于XML的AOP配置步骤

  1. 把通知Bean交给spring管理

  2. 使用<aop:config>标签表明开始AOP配置

  3. 使用<aop:aspect>标签表明配置切面

    • id:给切面提供一个唯一标识
    • ref:指定通知类bean的id
  4. <aop:aspect>标签内部使用对应标签来配置通知的类型

    • aop:before:表示配置前置通知

      1. method:用于指定Logger类中哪个方法是前置通知
      2. pointcut:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
    • 切入点表达式写法(需导入aspectjweaver):

      • 关键字:execution(表达式)
      • 表达式:
        访问修饰符 返回值 包名.包名…类名.方法名(参数列表)
      • 标准的表达式写法
        public void yonmin.service.impl.AccountServiceImpl.saveAccount()
      • 访问修饰符可以省略
        void yonmin.service.impl.AccountServiceImpl.saveAccount()
      • 返回值可以使用返回值通配符
        * yonmin.service.impl.AccountServiceImpl.saveAccount()
      • 包名可以使用通配符表示任意包。但是有几级包,就需要写几个*.
        * *.*.*.*.AccountServiceImpl.saveAccount()
      • 包名可以使用…表示当前包及其子包
        * *..AccountServiceImpl.saveAccount()
      • 类名和方法名都可以使用*实现通配
        * *..*.*()
      • 参数列表
        • 可以直接写数据类型:
          • 基本类型直接写名称,如:int
          • 引用类型写包名.类名的方式,如:java.lang.String
        • 类型可以使用通配符 * 来表示任意类型,但必须有参数
        • 可以使用 … 表示有无参数均可,有参数可以是任意类型
      • 全通配写法(最好不要这样写):
        * *..*.*(..)
      • 实际开发中切入点表达式的通常写法
        例如切到业务层实现类下的所有写法
        * yonmin.service.impl.*.*(..)
<!-- 配置Logger类 -->
<bean id="logger" class="yonmin.utils.Logger"></bean>

<!-- 配置AOP -->
<aop:config>
    <!-- 配置切面 -->
    <aop:aspect id="logAdive" ref="logger">
        <aop:before method="beforePrintLog" pointcut="execution(* yonmin.service.impl.*.*(..))"></aop:before>
    </aop:aspect>
</aop:config>

  • 配置切入点表达式
    • id属性用于指定表达式的唯一标识。
    • expression属性用于指定表达式内容
      此标签写在aop:aspect标签内部只能当前切面可用。
      它还可以写在aop:aspect外面,此时就变成了所有切面可用,但必须要写在aop:aspect之前
<!-- 配置Logger类 -->
<bean id="logger" class="yonmin.utils.Logger"></bean>

<!-- 配置AOP -->
<aop:config>

    <!-- 配置切入点 -->
    <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
    
    <!-- 配置切面 -->
    <aop:aspect id="logAdive" ref="logger">
        <!-- 前置通知:在切入点方法执行之前执行 -->
        <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
        <!-- 后置通知:在切入点方法正常执行之后通知 -->
        <aop:after-returning method="afterPrintLog" pointcut-ref="pt1"></aop:after-returning>
        <!-- 异常通知:在切入点方法执行产生异常后通知 -->
        <aop:after-throwing method="exceptPrintLog" pointcut-ref="pt1"></aop:after-throwing>
        <!-- 最终通知:无论切入点方法是否正常运行都通知 -->
        <aop:after method="finalPrintLog" pointcut-ref="pt1" ></aop:after>
        <!-- 配置环绕通知 -->
        <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
    </aop:aspect>
</aop:config>

环绕通知:
在日志代码中添加环绕通知并且在bean.xml中配置好,运行程序发现环绕通知执行了,但是切入点的方法没有执行
在Logger代码中如此配置环绕通知后切入点方法即被执行。
重点是使用 ProceedingJoinPoint,明确调用业务层方法(切入点方法)

public Object aroundPrintLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try {
        Object[] args = pjp.getArgs();

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

        rtValue = pjp.proceed(args); // 明确调用业务层方法(切入点方法)

        System.out.println("后置通知");

    } catch (Throwable throwable) {
        System.out.println("异常通知");

        throwable.printStackTrace();
    }finally {
        System.out.println("最终通知");

    }
    return rtValue;
}

基于注解的AOP配置

保留bean.xml情况

bean.xml文件中如此配置使代码支持AOP注解。

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

    <!-- 配置spring创建容器时扫描的包 -->
    <context:component-scan base-package="yonmin"></context:component-scan>

    <!-- 使AOP支持注解 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

使用@Aspect表示修饰类是一个切面类。

在切面类中的方法上使用:
@Before:表示前置通知
@AfterReturning:表示后置通知
@AfterThrowing:表示异常通知
@After:表示最终通知

在切面类中使用如下代码创建名为pt1的切面对象,注意在诸如@Before("pt1()")引用时,一定要在名字后面加上括号。

//创建切面对象
@Pointcut("execution(* yonmin.service.impl.*.*(..))")
private void pt1(){}

此时运行程序发现输出如下

Log类中的beforePrintLog方法开始记录日志了。。。
执行了保存
Log类中的finalPrintLog方法开始记录日志了。。。
Log类中的afterReturningPrintLog方法开始记录日志了。。。

发现顺序出现错误,这是Spring中AOP自带的问题,解决方法是使用环绕通知
在环绕通知的方法上添加注解@Around

@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try {
        Object[] args = pjp.getArgs();

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

        rtValue = pjp.proceed(args); // 明确调用业务层方法(切入点方法)

        System.out.println("后置通知");

    } catch (Throwable throwable) {
        System.out.println("异常通知");

        throwable.printStackTrace();
    }finally {
        System.out.println("最终通知");

    }

    return rtValue;
}

去除其它通知注解后运行,输出

前置通知
执行了保存
后置通知
最终通知

显然没有出现顺序错误的问题。

删除bean.xml情况

新建一个配置类,内容如下

package yonmin.configration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("yonmin")
@EnableAspectJAutoProxy
public class SpringConfigration {

}

@Configuration说明此类是Spring的配置类
@ComponentScan(“yonmin”) 相当于XML文件中扫描包的代码
@EnableAspectJAutoProxy表明能够使用AOP注解

在test代码中使用Junit进行测试

package test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import yonmin.configration.SpringConfigration;
import yonmin.service.IAccountService;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfigration.class)
public class AOPTest {

    @Resource(name = "accountService")
    private IAccountService as;

    @Test
    public void test(){
        as.saveAccount();
    }
}

使用@Resource(name = "accountService")获取Ioc容器中的accountService对象。
此时删除bean.xml文件运行即可

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值