天天看点

六、aspectJ框架实现AOP(注解)六、aspectJ框架实现AOP(注解)

六、aspectJ框架实现AOP(注解)

1、切面执行时机(Advice)

  1. @Before:前置增强
  2. @AfterReturning:后置增强
  3. @Around:环绕增强
  4. @AfterThrowing:异常增强
  5. @After:最终增强

追加一个额外注解:@Pointcut:定义切入点

2、切面执行位置(Pointcut,使用切入点表达式)

aspectJ定义了专门的表达式用于指定切入点,表达式的原型:

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

解释:

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

以上表达式共4个部分:execution(访问权限 方法返回值 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以, execution表达式中明显就是方法的签名。

**注意:**表达式中各部分间用空格分开。在其中可以使用以下符号:

符号 意义
* 任意个数
..

用在方法参数中,表示任意多个参数

用在包名后,表示当前包及其子包路径

+

用在类名后,表示当前类及其子类

用在接口后,表示当前接口及其实现类

栗子:

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

// 指定切入点为:任意一个以“set”开始的方法
execution(* set*(..))

// 指定切入点为:home.wang.service包下任意类的任意方法
execution(* home.wang.service.*.*(..))

// 指定切入点为:home.wang.service包及其子包下任意类的任意方法
execution(* home.wang.service..*.*(..))

// 指定切入点为:所有包下的service包下任意类的任意方法
execution(* *..service.*.*(..))
           

3、步骤

  1. 新建maven项目
  2. 加入依赖
    1. spring依赖
    2. aspectJ依赖
    3. junit单元测试
  3. 创建目标类:接口+实现类(给实现类方法的功能增强)
  4. 创建切面类:普通类
    1. 类上加注解:@Aspect
    2. 在类中定义方法(切面要执行的增强代码)
      1. 在方法上要加aspectJ中的增强注解:@Before…
      2. 还有要指定切入点表达式:execution(…)
  5. 创建Spring配置文件:声明对象(xml/注解),把对象交给容器统一管理
    1. 声明目标对象
    2. 声明切面类对象
    3. 声明aspectJ框架中的自动代理生成器标签
      1. 自动代理生成器:用来完成代理对象的自动创建功能
  6. 创建测试类,从Spring容器中获取目标对象(代理对象)
    1. 通过代理对象执行方法,实现AOP的功能增强

4、项目结构

六、aspectJ框架实现AOP(注解)六、aspectJ框架实现AOP(注解)

pom.xml

<!--追加aspectJ依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.20</version>
</dependency>
           

service.impl.SomeServiceImpl.java

package hom.wang.service.impl;
import hom.wang.service.SomeService;
/*目标类*/
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSomeSimple(String str, Integer num) {
        System.out.println("===目标方法doSomeSimple(str=" + str + ", num=" + num + ")===");
        return str + num;
    }

    @Override
    public String doSomeAround(String str, Integer num) {
        System.out.println("===目标方法doSomeAround(str=" + str + ", num=" + num + ")===");
        return str + num;
    }

    @Override
    public Integer doSomeAfterThrowing(Integer num0, Integer num1) {
        System.out.println("===目标方法doSomeAfterThrowing(num0="
                           + num0 + ", num1=" + num1 + ")===");
        return num0 / num1;
    }
}
           

service.impl.CglibServiceImpl.java

package hom.wang.service.impl;
public class CglibServiceImpl {
    public void doSome(){
        System.out.println("------CglibServiceImpl.doSome()------");
    }
}
           

MyAspect.java

package hom.wang;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
 * @Aspect:aspectJ框架中的注解
 *  表示:当前类是切面类(用来给业务代码增强功能的类,这里写有切面的功能代码)
 *  位置:类上
 */
@Aspect
public class MyAspect {

    /**
     * @Before:前置增强
     *  属性:
     *      value:切入点表达式
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
     */
    @Before(value = "execution(public String hom.wang.service.impl.SomeServiceImpl.doSomeSimple(String,Integer))")
    public void myBefore(){
        // 书写切面要执行的功能增强代码
        System.out.println("切面前置增强:输出执行开始时间:" + new Date());
    }

    /**
     * 指定参数中的给定参数:JoinPoint(业务方法)
     *  作用:可在增强里获得业务方法的信息
     *  使用须知:JoinPoint是由框架赋予的,必须放在参数列表第一位
     */
    @Before(value = "execution(String do*Simple(..))")
    public void myBefore_02(JoinPoint point){
        // 书写切面要执行的功能增强代码
        // 获取业务方法完整定义
        System.out.println("业务方法定义:" + point.getSignature());
        System.out.println("业务方法名称:" + point.getSignature().getName());
        System.out.println("业务方法参数列表:");
        Arrays.stream(point.getArgs()).forEach(System.out::println);

        System.out.println("切面前置增强02:输出执行开始时间:" + new Date());
    }

    /**
     * @AfterReturning:后置增强
     *  属性:
     *      1、value:切入点表达式
     *      2、returning:自定义变量表示方法返回值(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、有参数(推荐Object)
     */
    @AfterReturning(value = "execution(* do*Simple(..))", returning = "res555")
    public void myAfterReturning(Object res555){
        // 书写切面要执行的功能增强代码
        System.out.println("切面后置增强:业务方法执行后返回值为:" + res555);
    }

    /**
     * @Around:环绕增强
     *  属性:
     *      1、value:切入点表达式
     *  要求:
     *      1、公共方法 public
     *      2、方法必须有返回值(推荐Object)
     *      3、方法名称自定义
     *      4、有固定参数(ProceedingJoinPoint extends JoinPoint)
     *
     * 环绕增强等同于JDK动态代理的InvocationHandler接口
     * 参数ProceedingJoinPoint等同于Method
     */
    @Around(value = "execution(* do*Around(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 定义返回值
        Object result = null;
        // 1、环绕增强之前置增强
        System.out.println("环绕增强之前置增强:业务方法执行前记录的时间:" + new Date());
        System.out.println("环绕增强之前置增强:业务方法入参:[");
        Arrays.stream(pjp.getArgs()).forEach(System.out::println);
        System.out.println("]");
        // 2、目标方法调用
        result = pjp.proceed(); // 相当于method。invoke()
        // 3、环绕增强之后置增强
        System.out.println("环绕增强之后置增强:业务方法执行结束提交事务");
        // 返回执行结果
        return result;
    }

    /**
     * @AfterThrowing:异常增强
     *  属性:
     *      1、value:切入点表达式
     *      2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、有一个参数是Exception,如果还有就是JoinPoint
     *  特点:
     *      1、在目标方法抛出异常时执行
     *      2、可作异常监控
     */
    @AfterThrowing(value = "execution(* do*AfterThrowing(..))", throwing = "exception666")
    public void myAfterThrowing(Exception exception666){
        // 书写切面要执行的功能增强代码
        System.out.println("切面异常增强:业务方法运行时抛出异常:" + exception666.getMessage());
    }

    /**
     * @After:最终增强
     *  属性:
     *      1、value:切入点表达式
     *      2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
     *  要求:
     *      1、公共方法 public
     *      2、方法没有返回值
     *      3、方法名称自定义
     *      4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
     *  特点:
     *      1、总是执行
     *      2、可作异常监控
     */
    @After(value = "execution(* do*After*(..))")
    public void myAfter(){
        // 书写切面要执行的功能增强代码
        System.out.println("切面最终增强:总是会被执行");
        // 一般做资源清除工作的
    }

    /**
     * @Pointcut:定义和管理切入点(复用:多处使用同一个切入点时定义一个公用的切入点)
     *  属性:
     *      1、value:切入点表达式
     *  特点:
     *      当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
     *      其它的通知中, value属性就可以使用这个方法名称,代替切入点表达式
     */
    @Pointcut(value = "execution(* do*(..))")
    private void myPointcutComm(){
        //无需代码
    }

    // 使用@Pointcut定义的切入点
    @After(value = "myPointcutComm()")
    public void myAfterCommon(){
        // 书写切面要执行的功能增强代码
        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: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="hom.wang.service.impl.SomeServiceImpl" />
    <bean id="cglibService" class="hom.wang.service.impl.CglibServiceImpl" />
    <!--切面对象-->
    <bean id="myAspect" class="hom.wang.MyAspect" />
    <!--声明自动代理生成器:使用aspectJ框架内部功能创建目标对象的代理对象
        创建代理对象是在内存中实现的,修改目标对象在内存中的结构来创建代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy:会把Spring容器中所有目标对象,一次性都生成代理对象

        默认没有接口使用cglib,有接口使用jdk
    -->
    <aop:aspectj-autoproxy />

    <!--
        如果期望目标类有接口还依然使用cglib动态代理
        proxy-target-class="true":告诉框架死活给我用cglib代理
    -->
    <!--<aop:aspectj-autoproxy proxy-target-class="true" />-->
</beans>
           

MyTest.java

package hom.wang;
import hom.wang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
    @Test
    public void test(){
        ApplicationContext applicationContext = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("==============没有接口时是CGLIB动态代理=====================");
        CglibServiceImpl cglib = 
            (CglibServiceImpl) applicationContext.getBean("cglibService");
        System.out.println("没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + cglib.getClass().getName());

        SomeService proxy = (SomeService) applicationContext.getBean("someService");
        System.out.println("存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + proxy.getClass().getName());
        System.out.println("\n====================前置+后置===========================");
        System.out.println("proxy.doSomeSimple()最终执行结果==" + proxy.doSomeSimple("哦吼吼", 20));
        System.out.println("\n=====================环---绕===========================");
        System.out.println("proxy.doSomeAround()最终执行结果==" + proxy.doSomeAround("咚恰恰", 666));
        System.out.println("\n=====================异常+最终===========================");
        System.out.println("proxy.doSomeAfterThrowing()最终执行结果==" + proxy.doSomeAfterThrowing(12, 0));
    }
}
           

cout_control(控制台输出)

D:\Development\Environment\Java\jdk11\bin\java.exe...
==============没有接口时是CGLIB动态代理=====================
没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==hom.wang.service.impl.CglibServiceImpl$$EnhancerBySpringCGLIB$$6ae78cf8
存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==com.sun.proxy.$Proxy13

====================前置+后置===========================
切面前置增强:输出执行开始时间:Sun May 29 18:26:01 CST 2022
业务方法定义:String hom.wang.service.SomeService.doSomeSimple(String,Integer)
业务方法名称:doSomeSimple
业务方法参数列表:
哦吼吼
20
切面前置增强02:输出执行开始时间:Sun May 29 18:26:01 CST 2022
===目标方法doSomeSimple(str=哦吼吼, num=20)===
切面后置增强:业务方法执行后返回值为:哦吼吼20
***通用的切面最终增强:总是会被执行***
proxy.doSomeSimple()最终执行结果==哦吼吼20

=====================环---绕===========================
环绕增强之前置增强:业务方法执行前记录的时间:Sun May 29 18:26:01 CST 2022
环绕增强之前置增强:业务方法入参:[
咚恰恰
666
]
===目标方法doSomeAround(str=咚恰恰, num=666)===
***通用的切面最终增强:总是会被执行***
环绕增强之后置增强:业务方法执行结束提交事务
proxy.doSomeAround()最终执行结果==咚恰恰666

=====================异常+最终===========================
===目标方法doSomeAfterThrowing(num0=12, num1=0)===
切面异常增强:业务方法运行时抛出异常:/ by zero
***通用的切面最终增强:总是会被执行***
切面最终增强:总是会被执行

java.lang.ArithmeticException: / by zero...