【Spring】spring AOP的总结

17 篇文章 3 订阅

目录

一、AOP的概念

1.1 术语

1.2 AspectJ

1.3 AOP和spring AOP有什么关系

二、spring AOP的使用

0、首先要引用AspectJ语法依赖

1、spring AOP开启AspectJ语法支持

2、声明一个切面(Aspect)

2.1、声明一个Pointcut切点

2.2 声明一个advice通知

四、advice通知的位置


一、AOP的概念

1.1 术语

AOP就是将那些横切性的问题抽象出来形成一个切面,然后面对这些切面去编程。

  • join point:连接点,在Spring AOP中连接点往往就是指一个方法,也就是目标对象中的方法
  • pointcut: 切点,在spring中,切点就是连接点的集合,就可以将切点类比成一张表,连接点是这张表里面的记录。
  • target object:目标对象(原始对象),通过AOP进行代码增强前的对象
  • AOP proxy:代理对象,通过AOP将目标对象进行增强之后的对象,包含了原始对象的代码和增强代码。
  • weaving:织入,其实就是代码增强的过程,将解决横切性问题的代码插入到目标对象的方法,进而生成代理对象的方法,这个过程叫做织入。要注意区分,目标对象变为代理对象的过程叫代理,目标对象的方法变成代理对象的方法的过程叫织入
  • advice:通知,它表示两个含义,一个是表示解决横切性问题的代码内容,也就是织入进目标对象的方法中的代码内容是什么,另一个是表示将这个代码织入到目标对象中方法中的什么位置(方法前/方法后/方法前后),总结来说就是在方法执行的什么时机(when:方法前/方法后/方法前后)做什么(what:增强的功能)
  • aspect:切面,就是切点和advice的载体,我们要将advice织入到对应的连接点中,切点是连接点的集合,所以在编写代码的时候我们要进行织入的代码(advice)和要将代码插入到哪个连接点位置,都是要先定义到aspect(切面)中的。切面就是切点和advice的载体,切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!在aspectJ框架中切面用一个类来表示,在spring的XML中以一个配置标签来表示。

 

1.2 AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。也就是说AspectJ是一种AOP技术。AspectJ是一种静态代理的框架。AspectJ 是静态代理的增强,所谓的静态代理就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强。

 

1.3 AOPspring AOP有什么关系

AOP是一种思想,spring AOP是对AOP思想的实现

AspectJ也是对AOP思想的实现。

在之前spring AOP是有自己的实现AOP的语法的,但是很难记,后来spring AOP就引入了AspectJ的语法,但是实现方法还是spring AOP自己的实现方法。spring AOP是一个动态代理的框架

 

二、spring AOP的使用

0、首先要引用AspectJ语法依赖

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.0</version>
</dependency>

 

1spring AOP开启AspectJ语法支持

JavaConfig方式:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

XML方式:

<aop:aspectj-autoproxy/>

 

2、声明一个切面(Aspect

声明一个切面就是创建一个切面bean交给spring容器去管理

用注解的话就是先用@Aspect注解将一个类定义为切面类,然后这个切面的bean交给spring容器去管理,将bean装配给spring容器使用注解或者XML都可以

 

JavaConfig:

/**
 * 这个类就是一个切面
 * 切面就是切点+通知
 * 所在在这个类里面还需要定义切点和通知
 */
@Component
@Aspect
public class AppAspect {
}

 

2.1、声明一个Pointcut切点

/**
 * 这个类就是一个切面
 * 切面就是切点+通知
 * 所在在这个类里面还需要定义切点和通知
 */
@Component
@Aspect
public class AppAspect {

    /**
     * 将@Pointcut注解作用于一个方法,这就定义了一个切点,注解中表示的是priv.cy.dao包下面的所有类的所有任意修饰符的方法都是连接点(方法括号里面的..表示的是任意参数)
     * 将这些连接点集中到一起的集合就是切点
     *
     * 方法名任意,以后这个方法名就代表这个切点
     */
    @Pointcut("execution(* priv.cy.dao.*.*(..))")
    public void pointCut() {
    }
}

 

2.2 声明一个advice通知

package priv.cy.app;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;
/**
 * 这个类就是一个切面
 * 切面就是切点+通知
 * 所在在这个类里面还需要定义切点和通知
 */
@Component
@Aspect
public class AppAspect {
    /**
     * 将@Pointcut注解作用于一个方法,这就定义了一个切点,注解中表示的是priv.cy.dao包下面的所有类的所有任意修饰符的方法都是连接点(方法括号里面的..表示的是任意参数)
     * 将这些连接点集中到一起的集合就是切点
     *
     * 方法名任意,以后这个方法名就代表这个切点
     */
    @Pointcut("execution(* priv.cy.dao.*.*(..))")
    public void pointCut() {
    }


    /**
     * 下面来定义advice(通知)
     * 通知包括 通知的代码逻辑以及要将通知织入到什么位置
     */


    /**
     * 这就是一个advice
     * advice=代码逻辑+织入位置
     * 织入位置就是通过开头的注解表示的,将上面定义的代表切点的方法作为参数传入注解,下面这个注解表示,插入到上面pointCut()方法所代表的切点中所有的连接点的前面
     * 连接点就是一个方法,也就是说要插入到方法前
     *
     * 要插入的内容(代码逻辑)就是写道这个注解标识的方法中,方法名任意
     */
    @Before("pointCut()")
    public void before() {
        System.out.println("before");
    }
}

 

dao:

@Repository
public class IndexDao {
    public void query() {
        System.out.println("query");
    }
}

 

测试类:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext
                = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = annotationConfigApplicationContext.getBean("indexDao", IndexDao.class);
        dao.query();
    }
}

执行结果:

 

官方文档例子

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples

 

advice的注解还支持使用表达式:

@Component
@Aspect
public class AppAspect {
    @Pointcut("execution(* priv.cy.dao.*.*(..))")
    public void pointCutExecution() {
    }
    @Pointcut("within(priv.cy.dao.*)")
    public void pointCutWithin() {
    }
    @Pointcut("args(java.lang.String)")
    public void pointCutArgs() {
    }

    @Before("pointCutWithin()&&!pointCutArgs()")
    public void before() {
        System.out.println("before");
    }
}

含义就是织入dao包下面参数不是String类型的方法,很好理解,就是普通的与逻辑表达式。

 

四、advice通知的位置

@Before

在方法执行前执行

 

@AfterReturning

在方法返回结果之后执行

 

@AfterThrowing

在方法抛出异常之后执行

 

@After

只要是方法结束,就会执行

 

@Around

使用环绕通知,就需要引入一个连接点类ProceedingJoinPoint,这个类继承的JoinPoint这个类。

 

连接点类表示的就是一个连接点,也就是一个方法。我们可以通过JoinPoint获得连接点的所在类,目标对象,代理对象,方法参数,方法返回值类型等。当然所有的通知方法都可以将这个类的对象作为参数传入。

 

ProceedingJoinPoint与JoinPoint最大的不同就是它提供了proceed()方法,可以用来执行连接点方法。

 

通过这个方法就可以实现环绕通知(around)

@Component
@Aspect
public class AppAspect {
    @Pointcut("target(priv.cy.dao.IndexDao)")
    public void pointCutTarget() {
    }

    @Around("pointCutTarget()")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("a");
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("b");
    }
}

这样就在连接点方法的前后输出了a和b

执行结果

proceed()除了无参构造方法之外,还实现了一个有参构造方法

public Object proceed(Object[]args) throws Throwable;

这个有参方法可以让你设置传入连接点方法的参数

 

例子:

dao:

@Repository
public class IndexDao implements Dao{
    @Override
    public void query(String str) {
        System.out.println("query 1");
        System.out.println(str);
    }
    @Override
    public void query() {
        System.out.println("query 2");
    }
}

 

aspect:

@Component
@Aspect
public class AppAspect {
    @Pointcut("target(priv.cy.dao.IndexDao)")
    public void pointCutTarget() {
    }

    @Around("pointCutTarget()")
    public void around(ProceedingJoinPoint joinPoint) {
        Object args[] = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                args[i] += " hahaha";
            }
        }
        System.out.println("a");
        try {
            joinPoint.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("b");
    }
}@Component
@Aspect
public class AppAspect {
    @Pointcut("target(priv.cy.dao.IndexDao)")
    public void pointCutTarget() {
    }

    @Around("pointCutTarget()")
    public void around(ProceedingJoinPoint joinPoint) {
        Object args[] = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                args[i] += " hahaha";
            }
        }
        System.out.println("a");
        try {
            joinPoint.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("b");
    }
}

 

Test:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext
                = new AnnotationConfigApplicationContext(AppConfig.class);

        Dao dao = (Dao) annotationConfigApplicationContext.getBean("indexDao");
        System.out.println(dao instanceof IndexDao);
        dao.query("test");

        System.out.println("=========================");
        dao.query();
    }
}

 执行结果:

由上面的代码可以看出,传入的参数是test,但是因为JoinPoint提供了可以获取连接点方法参数的方法getArgs(),ProceedingJoinPoint对象就通过了这个方法获取到传入连接点方法的参数,然后对参数进行了修改,再将修改后的参数传入proceed(Object[]args)也就实现了对方法参数的修改,最终的执行结果是test hahaha。

args[]数组中的每一个元素是表示方法中的一个参数。

上面讲解的aspect都是基于注解来讲解的,使用XML的用法可以去看官方文档。使用XML时,aspect就是一个标签。XML的好处就是如果源码你是不能修改的,也就无法使用注解,这个时候就需要使用XML了


相关文章:【Spring】史上最全的spring IoC使用讲解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值