aspectJ框架的使用

目录

切面三个关键的要素

从三要素使用aspectJ框架

使用aspectj的基本步骤

    1. 新建maven

    2. 加入依赖

    3. 创建目标类 : 接口和它的实现类

    4. 创建切面类 : 就是一个普通类

    5. 创建spring的配置文件 : 声明对象, 把对象交给容器统一管理

    6. 创建测试类, 从spring的容器中获得代理对象(实际上是目标对象)

@After前置通知

    JoinPoint作为切面功能方法参数

@AfterReturning后置通知

@Around环绕通知

使用 @Pointcut 定义和管理切入点


切面三个关键的要素

    1. 切面的功能代码:切面是干什么的

    2. 切面的执行时间:使用通知(Advice)表示, 是在目标方法之前还是之后

    3. 切面的执行位置:使用切入点(Pointcut)表示切面执行位置

从三要素使用aspectJ框架

    1. 切面的功能代码, 这个由开发人员自己定义

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

        注解表示

        1. @Before        前置通知

        2. @AfterReturning    后置通知

        3. @Around        环绕通知

        4. @AfterThrowing    异常通知(了解)出现目标方法出现异常时执行

        5. @After        最终通知(了解)怎么都执行, 目标方法出现异常也执行

    3. 切面的执行位置, 使用切入点表达式

         1. AspectJ定义的表达式

            原形为:

        execution(1.[方法访问限定符]   2.方法返回值类型  
            3.[方法所在包名和类名]方法名(方法参数内容)
            4.[抛出异常信息])

            每一个小部分都可以使用通配符

        表达式参数由四大部分组成(已经添加序号), 每个部分由空格隔开

        []包裹的代表可以没有

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

        其中 方法返回值 和 方法声明 是必须的, 实际开发中用这两个部分就足够了

        例子:

execution(public void org.example.ba01.SomeService.doSome(String, Integer))

        表达式的参数由四大部分组成, 其中第3部分又有3个小部分

        每个小部分都可以使用通配符

        通配符            意义
        ----------------------------------------
        *            0至多个任意字符

        ..          用在方法的参数之中, 表示任意多个参数
                    用在包名之后, 表示当前包和其子包路径

        + (了解)     用在类名之后, 表示当前类及其子类
                    用在接口之后, 表示当前接口及其实现类

 
        例如:
 

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

        execution(* set*(..))            指定切入点为:任何一个以set开头的方法

        execution(* com.xyz.service.*.*(..))    指定切入点为:com.xyz.service中的任意类中的任意方法

         2. 使用 @Pointcut 管理切入点

使用aspectj框架实现aop

使用aop:目的是给已经存在的一些类和方法增加额外的功能, 前提是不改变原来的类的代码

使用aspectj的基本步骤

    1. 新建maven

    2. 加入依赖

        1. spring依赖

            <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
              <version>5.2.10.RELEASE</version>
            </dependency>

        2. aspectj依赖

            <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
              <version>5.2.5.RELEASE</version>
            </dependency>

       3. junit依赖

    3. 创建目标类 : 接口和它的实现类

        要做到是给类中的方法增加功能

    4. 创建切面类 : 就是一个普通类

        1. 在类的上面加入 @Aspect

        2. 在类中定义方法表示切面是干啥的(给原来方法新添加的功能)

            在方法上加入aspectj中的通知注解, 例如 @Before

            还需要指定切入点表达式execution()

    5. 创建spring的配置文件 : 声明对象, 把对象交给容器统一管理

        1. 声明目标对象

        2. 声明切面对象

        3. 声明aspectj框架中的自动代理生成器标签

            自动代理生成器 : 用来完成代理对象的自动创建功能

    6. 创建测试类, 从spring的容器中获得代理对象(实际上是目标对象)

        声明自动代理生成器, 是使用aspectj框架内部的功能, 创建目标对象的代理对象

        创建代理对象是在内存中完成的, 修改目标对象的内存结构, 创建出代理对象

        所以目标对象就是被修改后的代理对象

        通过代理执行方法, 实现aop的功能增强

 

@After前置通知

    SomeService接口

    package org.example.ba01;

    public interface SomeService {
        void doSome();
    }

    接口实现类

    package org.example.ba01;

    public class SomeServiceImpl implements SomeService{

        @Override
        public void doSome() {
        // 在这里增加一个打印时间的功能
        System.out.println("目标方法");
        }
    }

    切面类

    package org.example.ba01;

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

    import java.util.Date;
    /*
        @Aspect
        这个注解是aspectj框架中的注解
        作用 : 表示当前类是切面类
        切面类 : 是用来给业务方法增加功能的类, 这个类中有切面功能代码
        位置 : 在类定义的上面
    */
    @Aspect
    public class MyAspect {
        /*
        定义切面功能的方法
        要求:
            1. 方法是公共的方法
            2. 方法没有返回值
            3. 方法名称自定义
            4. 方法可以有参数, 也可以没参数
            如果有参数, 参数不是自定义的
         */
        /*
        @Before : 前置通知注解
        属性 : value, value的值为切面表达式
            切面表达式中一些部分可以简写
            如果根据切面表达式找不到要代理的目标就不会代理
        特点 :
            1. 在目标方法之前先执行
            2. 不会改变目标方法的执行结果
            3. 不会影响目标方法的执行
         */
            @Before(value = "execution(public void org.example.ba01.SomeService.doSome())")
            public void myBefore() {
                // 切面功能代码 : 打印时间
                System.out.println("切面功能:输出时间" + new Date());
            }
    }

    配置文件

    <?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">


        <!--把对象交给容器统一管理-->
        <!--声明目标对象-->
        <bean id="someService" class="org.example.ba01.SomeServiceImpl" />

        <!--声明切面类对象-->
        <bean id="myAspect" class="org.example.ba01.MyAspect"/>

        <!--
        声明自动代理生成器, 是使用aspectj框架内部的功能, 创建目标对象的代理对象
        创建代理对象是在内存中完成的, 修改目标对象的内存结构, 创建出代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy 它会把spring容器中的所有的目标对象, 一次性都生成代理对象
        -->
        <!--
        扫描配置文件时, 执行到这里还只是由spring创建了对应的对象
        执行下面一句时, 会启用aspectj框架中的功能, 扫描当前容器中的对象的类,
        判断这些类中是否有相关注解, 找到注解之后根据切入点表达式找到切入点(需要添加功能的方法)
        找到切入点后也就意味着找到切入点的类, 之后在根据这个类在容器中找到目标对象
        对目标对象进行改造 

        也就是说, 切面是根据注解找到的, 目标是根据切入点表达式找到的
        -->
        <aop:aspectj-autoproxy />

    </beans>

    测试代码

    public class MyTest01 {

        @Test
        public void test01() {
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        SomeService proxy = (SomeService) ac.getBean("someService");

        proxy.doSome();
        }
    }

    打印结果如下:

    切面功能:输出时间Thu May 20 18:19:49 GMT+08:00 2021

    目标方法

 

    JoinPoint作为切面功能方法参数

    定义切面功能的方法

    要求:

        1. 方法是公共的方法

        2. 方法没有返回值

        3. 方法名称自定义

        4. 方法可以有参数, 也可以没参数

        如果有参数, 参数不是自定义的

        指定通知方法的参数 : JoinPoint

        JoinPoint : 代表要加入切面功能的业务方法

            作用是 : 可以在切面方法中获取执行方法的信息, 例如方法名称, 实参

            如果你的切面功能中需要用到方法的信息, 就加入JoinPoint

            这个JoinPoint参数的值是由框架赋予的, 必须是第一个位置的参数

           @Aspect
            public class MyAspect {
                /*
                定义切面功能的方法
                要求:
                    1. 方法是公共的方法
                    2. 方法没有返回值
                    3. 方法名称自定义
                    4. 方法可以有参数, 也可以没参数
                    如果有参数, 参数不是自定义的
                    指定通知方法的参数 : JoinPoint
                    JoinPoint : 代表要加入切面功能的业务方法
                        作用是 : 可以在切面方法中获取执行方法的信息, 例如方法名称, 实参
                        如果你的切面功能中需要用到方法的信息, 就加入JoinPoint
                        这个JoinPoint参数的值是由框架赋予的, 必须是第一个位置的参数
                 */
                    @Before(value = "execution(public void org.example.ba01.SomeService.doSome(String, Integer))")
                    public void myBefore(JoinPoint joinPoint) {
                    // 获取方法的完整定义
                    System.out.println("方法的完整签名 = " + joinPoint.getSignature());
                    System.out.println("方法的名称 = " + joinPoint.getSignature().getName());
                    // 获取方法的全部参数
                    Object[] args = joinPoint.getArgs();
                    for (Object o : args) {
                        System.out.println("参数 = " + o.toString());
                    }
                    // 切面功能代码 : 打印时间
                    System.out.println("切面功能:输出时间" + new Date());
                }
            }

            打印结果为:
            方法的完整签名 = void org.example.ba01.SomeService.doSome(String,Integer)
            方法的名称 = doSome
            参数 = 张三
            参数 = 20
            切面功能:输出时间Thu May 20 20:33:00 GMT+08:00 2021
            目标方法

@AfterReturning后置通知

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

    @Aspect
    public class MyAspect1 {

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

        /*
        @AfterReturning: 后置通知
            属性: 1. value 表示切入点表达式
              2. returning 自定义的变量, 表示目标方法的返回值
                 自定义变量名必须和通知方法的形参名一致
            位置 : 在方法定义的上面
            特点: 1. 在目标方法之后执行
              2. 能够获取到目标方法的返回值, 可以根据这个返回值做一些不同的处理功能
              3. 可以修改这个返回值

         后置通知的执行顺序:
            Object res = doSome1();
            这个是值传递或者引用传递的
            myAfterReturning(res);
            所以在myAfterReturning中修改res的内容可能会对结果有影响(引用传递时)
         */
            @AfterReturning(value = "execution(public int org.example.ba01.SomeService.doSome1(String, Integer))",
            returning = "res")
            public void myAfterReturning(JoinPoint joinPoint, Object res) {
            System.out.println("后置通知 : 在目标方法执行后执行, 获取的返回值为" + res);
            System.out.println("后置通知方法的定义为" + joinPoint.getSignature());
            // 修改目标方法执行的返回值
            res = 100;
            System.out.println("将返回值修改为100");
        }
    }

@Around环绕通知

在项目开发时候经常做事务, 方法执行前开启事务, 执行结束后关闭事务

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import sun.awt.SunHints;

    import java.util.Date;

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

        /*
        @Around : 环绕通知
            属性 : value 切入点表达式
            位置 : 方法定义的上方
            特点 :
            1. 它是功能最强的通知
            2. 在目标代码的前和后都能增强功能
            3. 能控制你的目标方法是否被调用执行
            4. 可以修改原来的目标方法的执行结果, 影响到最后的调用结果

            环绕通知就等同于JDK的动态代理

            参数 : ProceedingJoinPoint, 等同于JDK动态代理的Method
                作用是执行目标方法的
            返回值 : 就是目标方法的执行结果, 可以被修改
        */
            @Around(value = "execution(String doSome2(String))")
            public Object myAround(ProceedingJoinPoint point) throws Throwable {
            // 实现环绕通知
            Object res = null;
            System.out.println("环绕通知:目标方法之前, 打印时间 : " + new Date());
            // 1.目标方法调用, 可以根据某些规则控制是否调用这个方法
            // 现在的规则是如果参数列表中有"张三"就执行,
            // 可以使用ProceedingJoinPoint来获取业务方法信息, 这个类继承了JoinPoint
            // JoinPoint中可以获取业务方法的信息
            Object[] args = point.getArgs();
            if ("张三".equals((String) args[0])) {
                // 等于就执行
                res = point.proceed(); // 执行目标方法相当于 method.invoke()
            }
            System.out.println("环绕通知:目标方法之后, 追加事务");
            // 2.在目标方法的前后添加功能

            // 可以修改执行结果
            System.out.println("现在的执行结果为 : " + res.toString());
            System.out.println("修改结果为 我不好");
            res = "我不好";
            // 返回目标方法的执行结果
            return res;
        }
    }


使用 @Pointcut 定义和管理切入点

    import org.aspectj.lang.annotation.*;

    @Aspect
    public class MyAspect3 {

        @Before(value = "execution(String doSome3(String))")
        public void myBefore() {
            System.out.println("前置通知, 在目标方法之前执行");
        }

        @AfterReturning(value = "execution(String doSome3(String))")
        public void myAfterReturning() {
            System.out.println("后置通知, 在目标方法之后执行");
        }

    }

    上面这两个切入点表达式表示的都是一个方法, 这样写比较麻烦
    可以使用这样的方式管理切入点

    @Aspect
    public class MyAspect3 {

        @Before(value = "myPointcut()")
        public void myBefore() {
            System.out.println("前置通知, 在目标方法之前执行");
        }

        @AfterReturning(value = "myPointcut()")
        public void myAfterReturning() {
            System.out.println("后置通知, 在目标方法之后执行");
        }

        /*
        @Pointcut : 定义和管理切入点的
                如果你的表达式有多个切入点表达式是重复的, 可以复用的
                就可以使用这个注解
            属性 : value 切入点表达式
            位置 : 在自定义的方式上面
            特点 :
               当使用 @Pointcut定义在一个方法上, 此时这个方法的名称就是value属性的切入表达式的别名
               其他的通知中, value属性就可以使用这个方法名称代替切入点表达式了
         */

        @Pointcut(value = "execution(String doSome3(String))")
        public void myPointcut() {
        // 无需任何代码, 只是使用这个方法的名字作为别名
        // 现在execution(String doSome3(String))就等价于myPointcut(), 注意有括号
        }
    }

    这样统一管理, 如果切入点更新只需要改变 @Pointcut 之后的内容就可以完成所有切入点的更新

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值