Spring AOP

通知:通知定义了切面是什么以及何时使用,除了描述切面要完成的工作,还解决了何时执行这个工作的问题,Spring切面定义了5中通知类型:

前置通知(Before):在目标方法被调用之前调用通知功能

后置通知(After):在目标方法完成之后调用通知功能,此时不会关心方法的输出是什么

返回通知(AfterReturning):在目标方法成功执行之后调用通知

异常通知(AfterThrowing):在目标方法抛出异常后调用通知

环绕通知(Around):在被通知的方法调用之前和调用之后执行自定义的行为

连接点:在应用执行过程中能够插入切面的一个点

切点:定义何处调用通知,切点的定义会匹配通知所有织入的一个或多个连接点,通常使用明确的类、方法名称,或利用正则表达式定义所要匹配的类和方法名来指定这些切点,Spring Aop所支持的AspectJ切点指示器有:

execution():用于匹配是连接点的执行方法

arg():限制连接点匹配参数为指定类型的执行方法

@args():限定连接点匹配参数由指定注解标注的执行方法

this():限定连接点匹配AOP代理的bean引用为指定类型的类

target:限定连接点匹配目标对象为指定类型的类

@target:限定连接点匹配特定的执行对象,这些对象对应的类要具有特定类型的注解

within():限制连接点匹配指定的类型

@within():限制连接点匹配指定注解所标注的类型

@annotation:限制匹配带有指定注解的连接点

在切点定义中支持以下运算符进行复合运算:

运算符说明
&&与运算。
!非运算。
||或运算。

在xml中定义切点时运算符“&&”也可以用“and”代替,“!”可以用“not”代替,“||”可以用“or”代替

切点表达式定义常用的可以参考:https://blog.csdn.net/w605283073/article/details/82999927

切面:切面是通知和切点的结合,通知和切面共同定义了切面的全部内容:它是什么,在何时何处完成其功能。

引入

织入:织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中,一个连接点可以同时匹配多个切点,且切点所对应的增强在连接点上织入顺序的规则为:

1、如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序织入;

2、如果是不同切面类中声明的增强,如果切面类都实现了org.springframework.core.Ordered 接口,则按照order方法的顺序号织入;

3、如果是不同切面类中声明的增强,切面类没有实现org.springframework.core.Ordered 接口,则织入顺序是不确定的。

因为Spring基于动态代理,所以Spring只支持方法连接点,不支持字段和构造器接入点,所以我们也无法在bean创建时应用通知

下面写一个简单的切面类:

public interface DanceService {

    public void dance();

    public void danceCount(int count);
}


@Service
public class DanceServiceImpl implements DanceService {
    public void dance() {
        System.out.println("dance");
    }

    public void danceCount(int count) {
        System.out.println("count:" + count);
    }
}


public interface SingService {
    public void sing();
}


@Service
public class SingServiceImpl implements SingService {
    public void sing() {
        System.out.println("sing");
    }
}


Aspect类:
@Aspect
public class AudienceAspect {

    @Before("execution(public * com.mybatis.service.*.*(..))")
    public void beforePerformance(){
        System.out.println("方法调用前:");
        System.out.println("Silencing cell phones");
    }

    @After("execution(public * com.mybatis.service.*.*(..))")
    public void afterPerformance(){
        System.out.println("方法调用后:");
        System.out.println("谢幕");
    }

}


applicationContext.xml配置文件:

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

  <!-- Aspect驱动器,启用自动代理功能 -->
  <aop:aspectj-autoproxy/>
  <context:component-scan base-package="com.mybatis.*"/>
  <bean class="com.mybatis.aspact.AudienceAspect"/>
</beans>


测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectTest {

    @Autowired
    private SingService singService;

    @Autowired
    private DanceService danceService;

    @org.junit.Test
    public void testAspect(){
        singService.sing();
        danceService.dance();
    }
}


执行结果:
方法调用前:
Silencing cell phones
sing
方法调用后:
谢幕
方法调用前:
Silencing cell phones
dance
方法调用后:
谢幕

上述是使用xml配置文件,如果使用javaConfig配置类启用自动代理功能,则通过加@EnableAspectJAutoProxy注解来实现

如果一个切面类中有多个相同的切点表达式,如上面的例子,两个切点的表达式是一样的,我们可以用@Pointcut注解定义一个可重用的切点,如下:

@Aspect
public class AudienceAspect {

    @Pointcut("execution(public * com.mybatis.service.*.*(..))")
    public void performance(){
    }

    @Before("performance()")
    public void beforePerformance(){
        System.out.println("方法调用前:");
        System.out.println("Silencing cell phones");
    }

    @After("performance()")
    public void afterPerformance(){
        System.out.println("方法调用后:");
        System.out.println("谢幕");
    }
}

上面的例子也可以通过@Around实现:

@Aspect
public class AudienceAspect {

    @Pointcut("execution(public * com.mybatis.service.*.*(..))")
    public void performance(){
    }

    @Around("performance()")
    public void aroundPerformance(ProceedingJoinPoint point){
        System.out.println("方法调用前:");
        System.out.println("Silencing cell phones");
        try {
            point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("方法调用后:");
        System.out.println("谢幕");
    }
}

@Around 被通知的方法调用之前和调用之后执行自定义的行为,ProceedingJoinPoint是使用@Around必须接受的参数,因为通知中通过它调用被通知的方法,通知方法中可以做任何的事情,当将控制权交给被通知的方法时,需要调用proceed()方法。ProceedingJoinPoint接口继承JoinPoint接口,这两个接口主要有哪些方法呢:

org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义以下这些主要方法:

Object getTarget():获取连接点所在的目标对象

Object getThis():获取代理对象

Signature getSignature():获取连接点的方法签名对象

Object[] getArgs():获取连接点方法运行时的入参列表

org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)

Object proceed() : 通过反射执行目标对象连接点处的方法

Object proceed():使用新的入参,通过反射执行目标对象连接点处的方法

通过它我们可以清楚的了解到连接点的目标对象,方法以及入参等信息,如下所示:

@Aspect
public class AudienceAspect {

    @Pointcut("execution(public * com.mybatis.service.*.*(..))")
    public void performance(){
    }

    @Around("performance()")
    public void aroundPerformance(ProceedingJoinPoint point){
        System.out.println("方法调用前:");
        System.out.println("Silencing cell phones");
        try {
            System.out.println("point.getTarget() 目标对象为:" + point.getTarget());
            System.out.println("point.getThis() 代理对象为:" + point.getThis());
            System.out.println("point.getSignature() 方法签名对象为:" + point.getSignature());
            Object[] objects = point.getArgs();
            if (objects.length > 0){
                for (int i = 0; i < objects.length; i++){
                    System.out.println("该方法第" + (i+1) + "个参数值为:" + objects[i]);
                }
            }

            point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("方法调用后:");

        System.out.println("谢幕");
    }
}

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectTest {

    @Autowired
    private SingService singService;

    @Autowired
    private DanceService danceService;

    @org.junit.Test
    public void testAspect(){
        singService.sing();
        danceService.dance();
        danceService.danceCount(1);
    }
}

测试结果:

方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.SingServiceImpl@1e04fa0a
point.getThis() 代理对象为:com.mybatis.service.impl.SingServiceImpl@1e04fa0a
point.getSignature() 方法签名对象为:void com.mybatis.service.SingService.sing()
sing
方法调用后:
谢幕
方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getThis() 代理对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getSignature() 方法签名对象为:void com.mybatis.service.DanceService.dance()
dance
方法调用后:
谢幕
方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getThis() 代理对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getSignature() 方法签名对象为:void com.mybatis.service.DanceService.danceCount(int)
该方法第1个参数值为:1
count:1
方法调用后:
谢幕

上面的例子是使用注解实现的,但面向注解的切面声明有一个劣势,就是必须能够为通知类添加注解,为了做到这一点就必须有源码,如果没有源码或者不想将AspectJ注解放到代码中,则可以使用另一种实现方式:在SpringXML配置文件中声明切面

                     AOP配置元素                                                 用途
<aop:config>顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:aspect>定义一个切面
<aop:advisor>定义AOP通知器
<aop:before>定义一个AOP前置通知
<aop:after>定义一个AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning>定义一个AOP返回通知(只有被通知的方法执行成功时才执行)
<aop:after-throwing>定义一个AOP异常通知(捕获被通知的方法的异常情况)
<aop:around>定义AOP环绕通知
<aop:pointcut>定义一个切点
<aop:aspectj-autoproxy>启用@Aspect注解驱动的切面
<aop:declare-parents>以透明的方式为被通知的对象引入额外的接口

那如何使用这些元素实现上述例子实现的结果呢?

//定义切面的类
public class AudienceXmlAspect {

    public void beforePerformance(){
        System.out.println("方法调用前:");
        System.out.println("Silencing cell phones");
    }

    public void afterPerformance(){
        System.out.println("方法调用后:");
        System.out.println("谢幕");
    }

}


//applicationContext.xml

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

  <context:component-scan base-package="com.mybatis.*"/>
  <bean id="audienceXmlAspect" class="com.mybatis.aspact.AudienceXmlAspect"/>

  <aop:config>
    <aop:aspect ref="audienceXmlAspect">
      <aop:pointcut id="performance" expression="execution(public * com.mybatis.service.*.*(..))"/>
      <aop:before pointcut-ref="performance" method="beforePerformance"/>
      <aop:after pointcut-ref="performance" method="afterPerformance"/>

      <!--<aop:before pointcut="execution(public * com.mybatis.service.*.*(..))" method="beforePerformance"/>-->
      <!--<aop:after pointcut="execution(public * com.mybatis.service.*.*(..))" method="afterPerformance"/>-->
    </aop:aspect>
  </aop:config>
</beans>



 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值