【Spring】(6)AOP面向切面编程:使用AspectJ 实现 AOP(干货收藏!)

8 篇文章 0 订阅

两个月前跟着b站动力节点王鹤老师的Spring框架教程视频学习完了Spring框架,在学习过程中我将视频中涉及的代码都一行一行手敲了一遍,并且把Spring入门相关的资料整理了一下,在这里记录一下我在Spring框架学习过程中的笔记和一些心得,希望能帮助到想要通过文字教程入门Spring这一框架的小伙伴哦!

视频地址:2020最新Spring框架教程【IDEA版】-Spring框架从入门到精通

AspectJ 实现 AOP

AOP 是动态的一个规范化、一个标准。

AOP的技术实现框架:

spring:spring 在内部实现了 aop 规范,能做 aop 的工作。
spring 主要在事务处理时使用 aop。

项目开发中很少使用 spring 的 aop 实现。 因为 spring 的 aop 比较笨重。通常,我们都会选择实现方式更为简捷,使用更为方便,而且还支持注解式开发的 AspectJ

aspectJ:一个开源的专门做 aop 的框架,它扩展了 Java 语言,提供了强大的切面实现。spring 框架中集成了 aspectj 框架,通过 spring 就能使用 aspectj 的功能。

a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台
的面向切面编程的语言)
Java platform compatible(兼容 Java 平台,可以无缝扩展)
easy to learn and use(易学易用)

官网地址: http://www.eclipse.org/aspectj/

Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

aspectJ 框架实现 aop 有两种方式:

  1. 使用 xml 的配置文件 : 配置全局事务
  2. 使用注解,在项目中要做 aop 功能,一般都使用注解, aspectj 有5个注解。

1. AspectJ 的通知类型

切面的执行时间,在规范中叫做 Advice (通知,增强)

在 aspectj 框架中使用注解表示的。当然,也可以使用 xml 配置文件中的标签。

常用的通知有五种类型:

​ 1)@Before 前置通知
​ 2)@AfterReturning 后置通知
​ 3)@Around 环绕通知
​ 4)@AfterThrowing 异常通知
​ 5)@After 最终通知

2. AspectJ 的切入点表达式

表示切面执行的位置(用来指定切入点),使用的是切入点表达式。

表达式原型

execution(modifiers-pattern? ret-type-pattern 
declaring-type-pattern?name-pattern(param-pattern)
 throws-pattern?)

解释:

modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
? 表示可选的部分

即表达式共有 4 部分

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中非粗体部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6Zorfuk-1634223063673)( https://static01.imgkr.com/temp/cd2e0ac36fcb4edab97b80469712e8af.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)也是。

3. AspectJ 基于注解的 AOP 实现

3.1 初步实现

用 maven 工具创建一个 quickstart 模板下的普通 java 工程

a. 配置 Maven 环境
  <dependencies>
    <!--junit依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

  <build>
    <!--插件-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

以下每一个 asp 包用于讲解一种注解

b. 定义业务接口与实现类(目标类)
package com.kaho.asp01;

public interface SomeService {
    void doSome(String name,Integer age);
}
package com.kaho.asp01;
//目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        //给doSome方法增加一个功能,在doSome执行之前,输出方法的执行时间
        System.out.println("====目标方法doSome()====");
    }
}
c. 定义切面类

类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

package com.kaho.asp01;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 *   @Aspect : 是 AspectJ 框架中的注解
 *       作用:表示当前类是切面类
 *       切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *       位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 定义方法:方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法可以有参数,也可以没参数
     *      如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */

    /**
     * @Before : 前置通知注解
     *    属性:value ,是切入点表达式,表示切面功能执行的位置
     *    位置:在方法的上面
     *  特点:
     *      1.在目标方法之前执行的
     *      2.不会改变目标方法的执行结果
     *      3.不会影响目标方法的执行
     */
    @Before(value = "execution(public void com.kaho.asp01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        //切面要执行的功能代码
        System.out.println("前置通知-切面功能:在目标方法执行之前输出执行时间:" + new Date());
    }
}
d. 声明目标对象切面类对象

先在 main 目录下创建 resources 文件夹,在其中创建一个Spring配置文件:applicationContext.xml

通过 <bean> 标签声明目标对象和切面类对象:

<!--把对象交给Spring容器,由Spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.kaho.asp01.SomeServiceImpl" />

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.kaho.asp01.MyAspect" />
e. 注册 AspectJ 的自动代理

在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成 “目标类 + 切面” 的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到 @Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。

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

    <!--把对象交给Spring容器,由Spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.kaho.asp01.SomeServiceImpl" />

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.kaho.asp01.MyAspect" />

    <!--声明自动代理生成器: 使用aspectj框架内部的功能,创建目标对象的代理对象。
        创建代理对象是在内存中实现的,实际上是修改目标对象在内存中的结构。将目标
        对象变成代理对象,所以这时,目标对象就是被修改后的代理对象。

        <aop:aspectj-autoproxy/> : 会把spring容器中的所有目标对象,一次性都生成代理对象。
    -->
    <aop:aspectj-autoproxy/>
</beans>

<aop:aspectj-autoproxy/> 的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。

其工作原理是 : <aop:aspectj-autoproxy/> 通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

f. 测试类中使用目标对象的 id
	@Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象——>目标对象此时就是代理对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强功能
        proxy.doSome("zhangsan",20);
    }
前置通知-切面功能:在目标方法执行之前输出执行时间:Mon Aug 02 16:07:59 CST 2021
====目标方法doSome()====

Process finished with exit code 0

记得以下每个注解的用例都需要在 applicationContext.xml 文件中修改相应的 class

3.2 @Before 前置通知-方法有 JoinPoint 参数

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

不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。

切面类 MyAspect :

package com.kaho.asp01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

@Aspect
public class MyAspect {
    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:要加入切面功能的业务方法
     *    作用:可以在通知方法中获取方法执行的信息,例如方法名称,方法的实参。
     *    如果你的切面功能中需要用到方法的信息,就加入JoinPoint。
     *    这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(public void com.kaho.asp01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义(签名)
        System.out.println("方法的签名(定义) = " + jp.getSignature());
        System.out.println("方法的名称 = " + jp.getSignature().getName());
        //获取方法的实参
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println("参数 = " + arg);
        }

        //切面要执行的功能代码
        System.out.println("前置通知-切面功能:在目标方法执行之前输出执行时间:" + new Date());
    }
}

测试类:

	@Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象——>目标对象此时就是代理对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强功能
        proxy.doSome("zhangsan",20);
    }
方法的签名(定义) = void com.kaho.asp01.SomeService.doSome(String,Integer)
方法的名称 = doSome
参数 = zhangsan
参数 = 20
前置通知-切面功能:在目标方法执行之前输出执行时间:Tue Aug 03 10:50:08 CST 2021
====目标方法doSome()====

Process finished with exit code 0
3.3 @AfterReturning 后置通知-注解有 returning 属性

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

接口增加方法:

package com.kaho.asp02;

public interface SomeService {
    void doSome(String name,Integer age);
    String doOther(String name,Integer age);
}

实现类实现方法:

package com.kaho.asp02;

    @Override
    public String doOther(String name, Integer age) {
        System.out.println("====目标方法doOther()====");
        return "abcd";
    }

切面类:

package com.kaho.asp02;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    /**
     * 后置通知定义方法:方法是实现切面功能的。
     * 方法的定义
     * 要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法是有参数的,推荐是Object,参数名自定义
     */

    /**
     * @AfterReturning : 后置通知
     *      属性:1.value 切入点表达式
     *           2.returning 自定义的变量,表示目标方法的返回值的
     *             自定义变量名必须和通知方法的形参名一样。
     *      位置:在方法定义的上面
     *  特点:
     *   1.在目标方法之后执行
     *   2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理
     *   3.可以修改这个返回值
     *
     *  后置通知的执行:
     *      Object res = doOther();
     *      //注意: 值传递   引用传递
     *      myAfterReturning(res);
     *    res是字符串,具有不可变性,所以在后置通知方法中改变 res 并不会改变目标方法返回值
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
    public void myAfterReturning(JoinPoint jp, Object res){
        //Object res : 是目标方法执行后的返回值,可以根据返回值做切面的功能处理
        System.out.println("后置通知:方法的定义 : " + jp.getSignature());
        System.out.println("后置通知:在目标方法之后执行,获取的返回值是 : " + res);
        if (res.equals("abcd")){
            //做一些功能
        } else {
            //做其他功能
        }

        //修改目标方法的返回值,看一下是否会影响最后的方法调用结果
        if (res != null){
            res = "Hello AspectJ";
        }
    }
}

测试类:

    @Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强了功能
        String str = proxy.doOther("lisi", 28);
        System.out.println("str === " + str);
    }
====目标方法doOther()====
后置通知:方法的定义 : String com.kaho.asp02.SomeService.doOther(String,Integer)
后置通知:在目标方法之后执行,获取的返回值是 : abcd
str === abcd

Process finished with exit code 0

注意:

String 类型的数据虽然是引用类型,但是如果进行字符串拼接是在方法区内生成一个新的字符串对象,而不是在原来的字符串上修改,类似于基本数据类型的值传递。类似这种的还有基本数据类型以及他们的高级封装类,因为他们本身没有提供修改的方法,所以每次操作都是生成一个新的对象。它们都是 immutable 类型。
所以通过后置通知获得目标方法的返回值不是 immutable 类型类型的话是可以修改的。

加入目标方法的返回值用的是像 Student 类这样的引用类型,则是可以通过后置通知进行修改的。因为这是引用传递,引用传递是指向同一个对象的,所以修改的话也会修改传入的对象。

我们可以通过对比两个对象的 hashcode 来验证这一说法,String的值改变前后的两个 hashcode 不一样,而对同一个 Student 里面的参数改变后和改变前的 hashcode 是一样的。

以上对传入引用类型对象有一点需要提起注意:

这样直接new一个新对象,是不会改变返回值的。

   @AfterReturning(value = execution(* *..SomeServiceImpl.doStudent(..)),returning = res)
    public void afterDoStudent(Object res){
        System.out.println(修改引用类型的值);
        if (res != null){
            res = new Student(王五,25);
        }
    }

但是,这样调用set方法,是可以改变返回值里面的数据的。

  @AfterReturning(value = execution(* *..SomeServiceImpl.doStudent(..)),returning = res)
    public void afterDoStudent(Object res){
        System.out.println(修改引用类型的值);
        if (res != null){
            Student student = (Student) res;
            student.setName(王五);
            student.setAge(25);
        }
    }
3.4 @Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

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

接口增加方法:

package com.kaho.asp03;

public interface SomeService {
    void doSome(String name,Integer age);
    String doOther(String name,Integer age);
    String doFirst(String name,Integer age);
}

实现类实现方法:

package com.kaho.asp03;

    @Override
    public String doFirst(String name, Integer age) {
        System.out.println("====业务方法doFirst()====");
        return "doFirst";
    }

切面类:

package com.kaho.asp03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    /**
     * 环绕通知方法的定义格式:
     *  1.public
     *  2.必须有一个返回值,推荐使用Object
     *  3.方法名称自定义
     *  4.方法有参数,固定的参数 ProceedingJoinPoint
     */

    /**
     * @Around : 环绕通知
     *      属性:value 切入点表达式
     *      位置:在方法定义上面
     * 特点:
     *   1.它是功能最强的通知
     *   2.在目标方法的前和后都能增强功能
     *   3.控制目标方法是否被调用执行
     *   4.可以修改原来的目标方法的执行结果。影响最后的调用结果
     *
     *  环绕通知,等同于jdk动态代理的 InvocationHandler 接口
     *
     *    参数: ProceedingJoinPoint 等同于 Method
     *          作用:执行目标方法的
     *    返回值:就是目标方法的执行结果,可以被修改。
     *
     *  环绕通知经常用于做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //获取第一个参数值 —— 即name
        String name = "";
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 1){
            name = (String)args[0];
        }

        //实现环绕通知
        Object result = null;
        //2.在目标方法的前或后加入功能
        System.out.println("环绕通知-在目标方法之前,输出时间 : " + new Date());
        //1.目标方法调用
        if ("wangwu".equals(name)){//控制目标方法是否被调用执行
            //符合条件,调用目标方法
            result = pjp.proceed();//等同于method.invoke(); 即 Object result = doFirst();未增强的目标方法
        }
        System.out.println("环绕通知-在目标方法之后,提交事务");

        //修改目标方法的执行结果,影响方法最后的调用结果
        if (result != null){
            result = "Hello AspectJ Around";
        }
        //返回目标方法的执行结果
        return result;
    }
}

ProceedingJoinPoint 继承于 JoinPoint,所以它具有 JoinPoint 的功能,可以在通知方法中获取方法执行的信息,例如方法名称,方法的实参。

测试类:

    @Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强了功能
        String str = proxy.doFirst("wangwu",18); //实际上是执行了myAround()
        System.out.println("str === " + str);//证明目标方法返回值能被修改
    }
环绕通知-在目标方法之前,输出时间 : Tue Aug 03 14:35:07 CST 2021
====业务方法doFirst()====
环绕通知-在目标方法之后,提交事务
str === Hello AspectJ Around

Process finished with exit code 0
3.5 @AfterThrowing 异常通知-注解中有 throwing 属性(了解)

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

接口增加方法:

package com.kaho.asp04;

public interface SomeService {
    void doSome(String name,Integer age);
    String doOther(String name,Integer age);
    String doFirst(String name,Integer age);
    void doSecond();
}

实现类实现方法:

package com.kaho.asp04;

    @Override
    public void doSecond() {
        System.out.println("====业务方法doSecond()====" + (10/0));//引入一个异常
    }

切面类:

package com.kaho.asp04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;


@Aspect
public class MyAspect {
    /**
     * 异常通知方法的定义格式:
     *  1.public
     *  2.无返回值
     *  3.方法名称自定义
     *  4.方法有一个参数Throwing,也可以有JoinPoint
     */

    /**
     * @AfterThrowing : 异常通知
     *  	属性:1.value 切入点表达式
     *  		 2.throwing 自定义的变量,表示目标方法抛出的异常对象,变量名必须和方法的参数名一样
     * 特点:
     *   1.在目标方法抛出异常时执行的
     *   2.可以做异常的监控程序,监控目标方法执行时是不是有异常。如果有异常,可以发送邮件、短信进行通知
     *
     *	执行原理:
     *		try{
     *			SomeServiceImpl.doSecond(..);
     *		}catch(Throwable e){
     *			myAfterThrowing(e);
     *		}
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))" throwing="ex")
    public Object myAfterThrowing(Throwable ex) {
        System.out.println("异常通知-方法发生异常时,执行 : " + ex.getMessage);
        //发送邮件、短信通知开发人员                          
    }
}

测试类:

    @Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强了功能
        proxy.doSecond();
    }
异常通知-方法发生异常时,执行 : /by zero

Process finished with exit code 0
3.6 @After 最终通知(了解)

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

接口增加方法:

package com.kaho.asp05;

public interface SomeService {
    void doSome(String name,Integer age);
    String doOther(String name,Integer age);
    String doFirst(String name,Integer age);
    void doSecond();
    void doThird();
}

实现类实现方法:

package com.kaho.asp05;

    @Override
    public void doThird() {
        System.out.println("====业务方法doThird()====" + (10/0));//引入一个异常
    }

切面类:

package com.kaho.asp05;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    /**
     * 最终通知方法的定义格式:
     *  1.public
     *  2.无返回值
     *  3.方法名称自定义
     *  4.方法无参数,如果有可以用JoinPoint
     */

    /**
     * @After : 最终通知
     *  	属性:value 切入点表达式
     *  	位置:在方法的上面	 
     * 特点:
     *   1.总是会执行
     *   2.在目标方法之后执行的
     *
     * 执行原理:
     * 		try{
     *			SomeServiceImpl.doThird(..);
     *	 	}catch(Exception e){
     *			
     *		}finally{
     *			myAfter();
     *		}
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))" throwing="ex")
    public void myAfter() {
        System.out.println("最终通知-总是会被执行的代码");  
        //一般用来做资源清除工作的。
    }
}

测试类:

    @Test
    public void test(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时增强了功能
        proxy.doThird();
    }
最终通知-总是会被执行的代码

java.lang.AeithmeticException: / by zero
3.7 @Pointcut 定义切入点

当较多的通知增强方法使用相同execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了**@Pointcut** 注解,用于定义 execution 切入点表达式。

其用法是,将@Pointcut 注解在一个方法之上,**以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。**代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
在这里插入图片描述

4. 拓展

一般有接口的情况下,spring 内部用的是 jdk 的动态代理。无接口的情况下,用的是 cglib 的动态代理。

如果在有接口的情况下也想使用 cglib 动态代理,只需将自动代理生成器的声明改为:

<aop:aspectj-autoproxy proxy-target-class="true"/>

proxy-target-class=“true” 告诉框架,要使用 cglib 动态代理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kaho Wang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值