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

文章目录


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工程

Spring内置框架Aspectj实现AOP(面向切面编程)
创建Maven工程选择archetype创建,选择图中的组件,next
Spring内置框架Aspectj实现AOP(面向切面编程)
输入包名和文件名,next
Spring内置框架Aspectj实现AOP(面向切面编程)
finish创建完成
Spring内置框架Aspectj实现AOP(面向切面编程)

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 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可
以使用以下符号:
Spring内置框架Aspectj实现AOP(面向切面编程)

切入点表达式的例子
举例:
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);
     *   }
     */
例子

增加业务方法:

Spring内置框架Aspectj实现AOP(面向切面编程)
方法实现:
Spring内置框架Aspectj实现AOP(面向切面编程)
定义切面:
Spring内置框架Aspectj实现AOP(面向切面编程)

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

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

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

增加方法:
Spring内置框架Aspectj实现AOP(面向切面编程)

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

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

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框架获取对象的方法一样

上一篇:SQL锁机制和事务隔离级别(转)


下一篇:Spring AOP 入门