ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Spring内置框架Aspectj实现AOP(面向切面编程)

2022-02-04 14:03:38  阅读:235  来源: 互联网

标签:org Spring 切入点 Aspectj AOP 返回值 import 方法 public


文章目录


AOP是JDK动态代理的规范化。

AOP术语解释

术语:
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面
是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不
能被增强的。
(4) 目标对象(Target)
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的
StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,
不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理
解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方
法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。

Aspectj实现AOP

学习aspectj框架的使用。
1)切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)

在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签
1)@Before
2)@AfterReturning
3)@Around
4)@AfterThrowing
5)@After

2)表示切面执行的位置,使用的是切入点表达式。

1. 创建Maven工程

在这里插入图片描述
创建Maven工程选择archetype创建,选择图中的组件,next
在这里插入图片描述
输入包名和文件名,next
在这里插入图片描述
finish创建完成
在这里插入图片描述

2. 向pom.xml中加入Spring依赖

1)spring依赖
2)aspectj依赖
3)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>

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

因为aspectj框架的底层实现是通过JDK动态代理实现的,所以必须要声明接口。目标类(需要功能增强的类)

创建接口

如果创建了接口类则是JKD动态代理(接口实现),如果没有则是CGLIB动态代理(继承实现),当然有接口也可以使用CGLIB动态代理,只要可以被基础就可以使用CGLIB

package com.tmp.service;

public interface SomeService {
    void doSome();
}

创建接口实现类(被增强)

package com.tmp.service.impl;

import com.tmp.service.SomeService;
public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome() {
        System.out.println("执行doSome方法");
    }

}

4.创建切面类:普通类

切面类里面定义了增强要到的非业务代码。
1)在类的上面加入 @Aspect
2)在类中定义方法, 方法就是切面要执行的功能代码
在方法的上面加入aspectj中的通知注解,例如@Before
有需要指定切入点表达式execution()

(1) 切入点表达式(表示需要增强的函数)

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 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可
以使用以下符号:
在这里插入图片描述

切入点表达式的例子
举例:
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)也是。

使用辅助注解@Pointcut实现切入点表达式的别名

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

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

@Pointcut例子
    @Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))")
    private void bieming(){
        //空方法
    }

在使用切入点表达式"execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))"可以用bieming()来代替
完整代码

package com.tmp.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspectj {

    @Around(value = "bieming()")
    public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable {
        //执行目标方法doSome
        Object res = null;
        System.out.println("doOther方法执行前");
        res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值
        System.out.println("doOther方法执行之后");
        //修改函数返回值
        res = "修改了返回值";
        return res;
    }


    @Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))")
    public void bieming(){
        //空方法
    }

}

(2)表示时间的注解

这些注解方法都有一个JoinPoint的参数,可以获取目标类,参数,目标方法等数据,这个参数必须要放在方法参数列表的第一位.

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

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

方法定义
    /**
     * 定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法可以有参数,也可以没有参数。
     *   如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */
例子
package com.tmp.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspectj {

    @Before("execution(public void com.tmp.service.Impl.SomeServiceImpl.doSome(..))")
    public void doSomeAdd(JoinPoint jp){
        System.out.println(jp.getSignature());
        System.out.println("在doSome之前");
    }
}


特点
    /**
     * @Before: 前置通知注解
     *   属性:value ,是切入点表达式,表示切面的功能执行的位置。
     *   位置:在方法的上面
     * 特点:
     *  1.在目标方法之前先执行的
     *  2.不会改变目标方法的执行结果
     *  3.不会影响目标方法的执行。
     */
2)@AfterReturning 后置通知-注解有 returning 属性

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

方法定义
    /**
     * 后置通知定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法有参数的,推荐是Object ,参数名自定义(参数名要与切入点表达式的returning参数的值一样)
     */
例子
package com.tmp.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspectj {
	
    @AfterReturning(value = "execution(public void com.tmp.service.Impl.SomeServiceImpl.doOther(..))",
            returning="res")
    public void doSomeAdd(JoinPoint jp, Object res){
        //res参数为目标方法的返回值
        System.out.println("后置通知,在doSome之后");
    }

}
特点
   /**
     * @AfterReturning:后置通知
     *    属性:1.value 切入点表达式
     *         2.returning 自定义的变量,表示目标方法的返回值的。
     *          自定义变量名必须和通知方法的形参名一样。
     *    位置:在方法定义的上面
     * 特点:
     *  1。在目标方法之后执行的。
     *  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *      Object res = doOther();
     *  3. 可以修改这个返回值
     *
     */
3)@Around 环绕通知-增强方法有 ProceedingJoinPoint参数(功能最强大)

与jdk动态代理相似.可以修改行数的返回值

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

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

     /*  环绕通知,等同于jdk动态代理的,InvocationHandler接口
     *
     *  参数:  ProceedingJoinPoint 就等同于 Method
     *         作用:执行目标方法的
     *  返回值: 就是目标方法的执行结果,可以被修改。
     *
     */
例子
package com.tmp.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspectj {

    @Around(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))")
    public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable {
        //执行目标方法doSome
        Object res = null;
        System.out.println("doOther方法执行前");
        res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值
        System.out.println("doOther方法执行之后");
        //修改函数返回值
        res = "修改了返回值";
        return res;
    }

}
特点

    /**
     * @Around: 环绕通知
     *    属性:value 切入点表达式
     *    位置:在方法的定义什么
     * 特点:
     *   1.它是功能最强的通知
     *   2.在目标方法的前和后都能增强功能。
     *   3.控制目标方法是否被调用执行
     *   4.修改原来的目标方法的执行结果。 影响最后的调用结果
     *  
     *   环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
     *
4) [了解]@AfterThrowing 异常通知-注解中有 throwing 属性

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

定义
    /**
     * 异常通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法有个一个Exception, 如果还有是JoinPoint,
     */

特点
    /**
     * @AfterThrowing:异常通知
     *     属性:1. value 切入点表达式
     *          2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
     *             变量名必须和方法的参数名一样
     * 特点:
     *   1. 在目标方法抛出异常时执行的
     *   2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
     *      如果有异常,可以发送邮件,短信进行通知
     *
     *  执行就是:
     *   try{
     *       SomeServiceImpl.doSecond(..)
     *   }catch(Exception e){
     *       myAfterThrowing(e);
     *   }
     */
例子

增加业务方法:

在这里插入图片描述
方法实现:
在这里插入图片描述
定义切面:
在这里插入图片描述

5) [了解]@After 最终通知

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

方法定义
    /**
     * 最终通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法没有参数,  如果还有是JoinPoint,
     */
特点
    /**
     * @After :最终通知
     *    属性: value 切入点表达式
     *    位置: 在方法的上面
     * 特点:
     *  1.总是会执行
     *  2.在目标方法之后执行的
     *
     *  try{
     *      SomeServiceImpl.doThird(..)
     *  }catch(Exception e){
     *
     *  }finally{
     *      myAfter()
     *  }
     *
     */
例子

增加方法:
在这里插入图片描述

方法实现:
在这里插入图片描述

定义切面:
在这里插入图片描述

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

声明对象你可以使用注解或者xml配置文件
下面是采用注解的方法(需要在配置文件中加入组件扫描器)
1 )声明目标对象

package com.tmp.service.Impl;

import com.tmp.service.SomeService;
import org.springframework.stereotype.Component;

@Component("someService")
public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome() {
        System.out.println("执行doSome方法");
    }

    @Override
    public String doOther(String name) {
        System.out.println("执行doOther");
        return name;
    }
}

2)声明切面类对象

package com.tmp.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspectj {

    @Around(value = "bieming()")
    public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable {
        //执行目标方法doSome
        Object res = null;
        System.out.println("doOther方法执行前");
        res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值
        System.out.println("doOther方法执行之后");
        //修改函数返回值
        res = "修改了返回值";
        return res;
    }


    @Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))")
    public void bieming(){
        //空方法
    }

}

3)声明aspectj框架中的自动代理生成器标签。
自动代理生成器:用来完成代理对象的自动创建功能的。
<aop:aspectj-autoproxy />

这个标签的参数proxy-target-class表示动态代理的方式, “true” 表示CGLIB, false表示JKD. 默认方式是JKD动态代理

完整的配置配置文件

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.tmp.service"/>
    <aop:aspectj-autoproxy />
</beans>

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

通过代理执行方法,实现aop的功能增强。
获取的目标对象实际已经变成了代理对象

package com.tmp;

import com.tmp.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    
    @Test
    public void test02(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        SomeService someService = (SomeService)applicationContext.getBean("someService");
        someService.doSome();
    }
}

获取对象的方法和Spring框架获取对象的方法一样

标签:org,Spring,切入点,Aspectj,AOP,返回值,import,方法,public
来源: https://blog.csdn.net/weixin_53475254/article/details/122765697

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有