AspectJ, Spring 于Aop的关系

没有关系,但是他们都是Aop的实现者。
基于Spring环境下的AspectJ对Aop的实现

搭建AspectJ编程环境(引入aop约束)

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  12. http://code.alibabatech.com/schema/dubbo
  13. ">
  14. <!-- <注册目标对象>-->
  15. <bean id="someService" class="com.abc.dao.ISomeServiceimpl"/>
  16. <!-- 注册切面,通知 ;-->
  17. <bean id="myAdvice" class="com.abc.dao.MyAspect"/>
  18. <aop:aspectj-autoproxy/>
  19. </beans>
  1. package com.abc.dao;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Before;
  5. @Aspect //表示当前类为切面
  6. public class MyAspect {
  7. //前置通知
  8. @Before("execution(* *..dao.ISomeService.doFirst(..))")
  9. public void myBefore(){
  10. System.out.println("这是执行前置通知方法");
  11. }
  12. //前置通知
  13. @Before("execution(* *..dao.ISomeService.doFirst(..))")
  14. public void myBefore(JoinPoint jp){
  15. System.out.println("这是执行前置通知方法 jp=" + jp); jp是具体实现类即com.abc.dao.ISomeService.doFirst()
  16. }
  17. }

(一)AspectJ的通知

  1. 前置通知,后置通知,环绕通知,异常顾问,最终通知

(二)切入点表达式

      用于指定切入点,execution {    <br />                                                               访问权限,返回值类型,全限定性类名(接口名),方法名(参数列表),抛出异常     }
execution(public * *(..)) 
指定切入点为:任意公共方法。

execution(* set *(..)) 
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..)) 
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。
execution(* *.service.*.*(..)) 
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.ISomeService.*(..)) 
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution(* com.xyz.service.IAccountService.*(..)) 
指定切入点为: IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..)) 
指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任
意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参
数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用
全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*))) 
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类
型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String 
s3)不是。
execution(* joke(String,..))) 
指定切入点为:所有的 joke()方法,该方法第 一个参数为 String,后面可以有任意个参数且
参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)
都是。
execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。 joke(Object 
ob)是,但,joke(String s)与 joke(User u)均不是。
execution(* joke(Object+))) 
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。
不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是

(三)两种实现

1,基于注解的AspectJ编程

package com.abc.dao;

import org.aspectj.ajdt.internal.compiler.ast.Proceed;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect   //表示当前类为切面
public class MyAspect {
    -----------------------------------------------------------------------------
    //前置通知
    @Before("execution(*  *..dao.ISomeService.doFirst(..))")
    public void myBefore() {
        System.out.println("这是执行前置通知方法");
    }

    //前置通知
    @Before("execution(*  *..dao.ISomeService.doFirst(..))")
    public void myBefore(JoinPoint jp) {
        System.out.println("这是执行前置通知方法 jp=" + jp);
    }
-----------------------------------------------------------------------------
    //后置通知
    //可以获取目标方法的执行结果,但不能改变其值
    @AfterReturning(value = "execution(*  *..dao.ISomeService.doSecond(..))", returning = "result")
    public void myAfterReturning(Object result) {
        System.out.println("这是执行后置通知方法result=" + result);
    }
----------------------------------------------------------------------------------
    //环绕通知
    //可以获取目标方法的执行结果,并且可以改变其值
    @Around("execution(*  *..dao.ISomeService.doSecond(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("执行环绕通知--目标方法执行之前");
        Object proceed = pjp.proceed();
        System.out.println("执行环绕通知--目标方法执行之后");
        if (proceed != null) {
            proceed = ((String) proceed).toUpperCase();
        }
        return proceed;
    }
-----------------------------------------------------------------------------
    //异常通知
    @AfterThrowing("execution(*  *..dao.ISomeService.doThird(..))")
    public void myAfterTjrowing() {
        System.out.println("执行异常通知");

    }
     //异常通知
    //异常通知可以获取异常信息
    @AfterThrowing(value = "execution(*  *..dao.ISomeService.doThird(..))", throwing = "ex")
    public void myAfterTjrowing(Exception ex) {
        System.out.println("执行异常通知ex=" + ex.getMessage());
    }
    -----------------------------------------------------------------------
       //最终通知
      //无论目标方法师傅有异常,都会执行
    @After("execution (*  *..dao.ISomeService.doThird(..))")
    public void myAfter() {
        System.out.println("执行最终通知");

    }
    @After("doThirdPointcut()")
    public void myAfter2() {
        System.out.println("执行最终通知");

    }

    //定义切入点
    @Pointcut("execution (*  *..dao.ISomeService.doThird(..))")
    private void doThirdPointcut(){}

    @Pointcut("execution (*  *..dao.ISomeService.doFirst(..))")
    private void doFirstPointcut(){}

    @Pointcut("execution (*  *..dao.ISomeService.doSecond(..))")
    private void doSecondPointcut(){}

    //前面的都可以用这个了
}

2,基于XML的AspectJ编程

<?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-4.3.xsd
       http://code.alibabatech.com/schema/dubbo
        ">
把注解全部清除,用xml完成注入

<!--    <注册目标对象>-->
  <bean id="someService" class="com.abc.dao.ISomeServiceimpl"/>
  <!--     注册切面,通知  ;-->
  <bean id="myAspect" class="com.abc.dao.MyAspect"/>
  <aop:config>
<!--    //定义切入点-->
    <aop:pointcut id="doFirstPointcut" expression="execution(* *..dao.ISomeService.doFirst(..))"/>
    <aop:pointcut id="doSecondPointcut" expression="execution(* *..dao.ISomeService.doSecond(..))"/>
    <aop:pointcut id="doThirdPointcut" expression="execution(* *..dao.ISomeService.doThird(..))"/>
    <aop:aspect ref="myAspect">
<!--      前置通知-->
      <aop:before method="myBefore" pointcut-ref="doFirstPointcut"/>
<!--      后置通知-->
      <aop:after-returning method="myAfterReturning" pointcut-ref="doSecondPointcut"/>
<!--      环绕通知-->
            <aop:around method="myAround" pointcut-ref="doThirdPointcut"/>
<!--      异常通知-->
      <aop:after-throwing method="myAfterThrowing" pointcut-ref="doThirdPointcut"/>
<!--      最终通知-->
      <aop:after method="myAfter" pointcut-ref="doSecondPointcut"/>

<!--      通过代入参数来确定具体通知-->
<!--     <aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doThirdPointcut" throwing="ex"/>-->
<!--    <aop:after-returning method="myAfterReturning(java.lang.Object)" pointcut-ref="doSecondPointcut" returning="result"/>-->
<!--    <aop:before method="myBefore(org.aspectj.lang.JoinPoint)" pointcut="execution(* *..dao.ISomeService.doThird(..))"/>-->
    </aop:aspect>
  </aop:config>
</beans>

详细步骤

1 AspectJ 简介

AspectJ 是一个面向切面的框架,它扩展了 Java 语言。AspectJ 定义了 AOP 语法,它有 一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。 —— 百度百科《AspectJ》

2 AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于 try..catch 中的 finally 代码块。

3 AspectJ 的切入点表达式

AspectJ 除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原 型是:
execution ( [modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型 )
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中 可以使用以下符号:
image.png
举例:

execution(public * *(..)) 
指定切入点为:任意公共方法。

execution(* set *(..)) 
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..)) 
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。
execution(* *.service.*.*(..)) 
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.ISomeService.*(..)) 
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution(* com.xyz.service.IAccountService.*(..)) 
指定切入点为: IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..)) 
指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任
意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参
数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用
全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*))) 
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类
型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String 
s3)不是。
execution(* joke(String,..))) 
指定切入点为:所有的 joke()方法,该方法第 一个参数为 String,后面可以有任意个参数且
参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)
都是。
execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。 joke(Object 
ob)是,但,joke(String s)与 joke(User u)均不是。
execution(* joke(Object+))) 
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。
不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是

4 AspectJ 的开发环境

(1)导入两个 Jar 包

AspectJ 是专门针对 AOP 问题的,所以其运行是需要 AOP 环境的,即需要之前的 AOP 的两个 Jar 包。另外,还需要 AspectJ 自身的 Jar 包:在 Spring 支持库解压目录中的子目录 org.aspectj 下有两个子包:
image.png
一般情况下,使用 weaver 包中的 Jar 即可。tools 中的 Jar 除了包含 weaver 中类库外, 还包含了其它工具,但一般不用。所以,使用 weaver 包中的 Jar 即可。
image.png
当然,在 Spring 中使用 AspectJ,还需要将它们联系一起的整合 Jar 包。在 Spring 框架 解压目录的 libs 中。
image.png

(2)引入 AOP 约束 在配置文件头部

要引入关于 aop 的约束。在 Spring 框架的解压目录中, \docs\spring-framework-reference\html 下的 xsd-configuration.html 文件中
image.png
在前面 Spring 实现 AOP 时,并未引入 AOP 的约束,而在 AspectJ 实现 AOP 时,才提出 要引入 AOP 的约束。说明,配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的, 而非 Spring 框架本身在实现 AOP 时使用的。 AspectJ 对于 AOP 的实现有两种方式:
(1)注解方式
(2)XML 方式

5 AspectJ 基于注解的 AOP 实现

AspectJ 提供了以注解方式对于 AOP 的实现。

(1)实现步骤

Step1:定义业务接口与实现类

image.png
image.png

Step2:定义切面 POJO 类

该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知 方法。
image.png

Step3:在切面类上添加@Aspect 注解

在定义的 POJO 类上添加@Aspect 注解,指定当前 POJO 类将作为切面
image.png

Step4:在 POJO 类的普通方法上添加通知注解

切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增 强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要 通过 execution 表达式指定具体应用的目标类与目标方法,即切入点。
image.png

Step5:注册目标对象与 POJO 切面类

image.png

Step6:注册 AspectJ 的自动代理

在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类 + 切面”的代理 对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的 自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并 生成代理。
image.png
< aop:aspectj-autoproxy/ > 的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。 从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
其工作原理是,< aop:aspectj-autoproxy/ > 通过扫描找到@Aspect 定义的切面类,再由切 面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

Step7:测试类中使用目标对象的 id

image.png
举例:aop_aspectj 项目的 annotation 包

(2)@Before 前置通知-方法有 JoinPoint 参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该 参数

image.png

(3)@AfterReturning 后置通知-注解有 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回 值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
image.png

(4)@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法 的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
image.png

(5)@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象
image.png
image.png

(6)@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行。
image.png

(7)@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 executeion 的 value 属性值 均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcute 注解的方法一般使用 private 的标识方法,即没有实际作用的方法
image.png

6 AspectJ 基于 XML 的 AOP 实现

AspectJ 除了提供了基于注解的 AOP 的实现外,还提供了以 XML 方式的实现。
切面就是一个 POJO 类,而用于增强的方法就是普通的方法。通过配置文件,将切面中 的功能增强织入到了目标类的目标方法中。

(1)实现步骤

Step1:定义业务接口与实现类

image.png
image.png

Step2:定义切面 POJO 类

该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知 方法。
image.png

Step3:注册目标对象与 POJO 切面类

image.png

Step4:在容器中定义 AOP 配置

image.png
配置文件中,除了要定义目标类与切面的 Bean 外,最主要的是在中进行 aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。
通过其子标签< aop:pointcut />定义切入点,该标签有两个属性,id 与 expression。分别 用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。
image.png
通过子标签< aop:aspect />定义具体的织入规则:根据不同的通知类型,确定不同的织入 时间;将 method 指定的增强方法,按照指定织入时间,织入到切入点指定的目标方法中。
< aop:aspect /> 的 ref 属性用于指定使用哪个切面。
< aop:aspect />的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但 也有共同的属性。
method:指定该通知使用的切面中的增强方法。
pointcut-ref:指定该通知要应用的切入点。
AspectJ 的 6 种通知的 XML 标签如下:
:前置通知
< aop:after-returning/> : 后置通知
:环绕通知
:异常通知
< aop:after/ >:最终通知
:引入通知
image.png

Step5:测试类中使用目标对象的 id

image.png
举例:aop_aspectj 项目的 xml 包

(2)前置通知

image.png
选择重载的方法:在 method 属性赋值时,不仅要放方法名,还要放入方法的参数类型全类名
image.png

(3)< aop:after-returning/>后置通知

其 XML 的配置中,有一个属性 returning,指定用于接收目标方法的返回值所使用的变 量名。其可作为增强方法的参数出现。
image.png

(4) 环绕通知

环绕通知的增强方法一般返回类型为 Object,是目标方法的返回值。并且可以包含一个 参数 ProceedingJoinPoint,其方法 proceed()可执行目标方法。
image.png
image.png

(5)异常通知

其 XML 的配置中,有一个属性 throwing,指定用于接收目标方法所抛出异常的变量名。 其可作为增强方法的参数出现,该参数为 Throwable 类型。
image.png

(6)< aop:after/ >最终通知

image.png
image.png