详解Spring AOP

目录
  • 什么是AOP?
  • AOP术语
    • 通知(Advice)
    • 连接点(Join point)
    • 切点(Pointcut)
      • 连接点和切点的区别
    • 切面(Aspect)
    • 引入(Introduction)
    • 织入(Weaving)
  • SpringAOP
    • SpringAOP的特点
    • SpringBoot集成SpringAOP
      • - 依赖引入
      • - 创建注解
      • - 定义切面
      • - 设置切点
      • - 业务接口编写
      • - 测试
    • 通知时机
      • - 正常情况
      • - 异常情况
  • 总结

什么是AOP?

​AOP,即我们平时经常提到的面向切面编程。首先我们要理解一个叫横切关注点(cross-cutting concern)的概念,它其实是描述我们应用中的功能,假如有一个功能,它在应用程序中很多个地方都用了,那么我们把这样的功能称之为横切关注点。

​日常开发中,我们都会将不同的业务场景抽象出对应的模块进行开发,而不同的模块,除了那些针对特定领域的核心功能外,还有一些相同的辅助功能,比如日志管理、安全管理、事务管理等等。横切关注点这个概念其实就点明了:类似这样的功能就是我们面向切面编程需要关注的地方。这也是面向切面编程的意义所在:它帮助我们实现横切关注点和他们所影响的对象之间的解耦。

​面向切面编程的实质,就是讲横切关注点模块化成称为切面的特殊的类。

AOP术语

以下讨论都基于SpringAOP

通知(Advice)

切面的工作被称为通知。也就是定义了切面的要做什么,以及何时做的问题。下面是Spring切面有5种类型的通知,以及相对应的在SpringBoot中的五个注解

  • 前置通知(Before):方法调用之前,对应@Before
  • 后置通知(After):方法调用之后(不关心方法输出是什么)对应@After
  • 返回通知(After-returning):目标方法成功执行之后,对应@AfterReturning
  • 异常通知(After-throwing):目标方法抛出异常之后,对应@AfterThrowing
  • 环绕通知(Around):方法调用之前和之后,对应@Around
  • 后置通知和返回通知的区别

后置通知应用时机在返回通知之后,任何情况下都会应用,而返回通知只有方法正常执行

正常返回后执行

  • 环绕通知与其它通知的区别

不同于其它的通知,环绕通知有目标方法的执行权,能够控制目标方法是否执行。而其它的通知更多是对目标方法的一个增强,无法影响目标方法的执行

以上两点,我们下面会通过一个例子更好的体会其中的差异。

连接点(Join point)

程序中那些我们想要应用通知的地方,就是连接点。这个点可以是我们调用方法时、抛出异常时或甚至是修改某一个字段的时候。切面代码(通知)可以通过这些点插入到应用的正常流程中,是原本的功能增添新的行为。

切点(Pointcut)

我们的应用程序可能会有很多个连接点需要我们应用通知,所以我们有必要把连接点的分类汇总,抽象出相同的特点,好让正确的切面切入到正确的地方去,各司其职,而不是切入所有的连接点。切点定义 了一个切面需要在哪里进行切入。是一堆具有特定切面切入需求的连接点的共性抽象。

我们通常通过明确类和方法名、或者匹配正则表达式的方式来指定切点

连接点和切点的区别

切点是对的拥有相同特点的连接点集合的一个抽象。切点定义了连接点的特性,是一个描述性的归纳,由于在SpringAOP中切点的切入级别是方法,所以那些符合切点定义的方法,都是一个连接点,连接点是切点的具象表现。

切面(Aspect)

切面是通知和切点的结合。通知和切点共同定了切面的全部内容——它想要干什么,在何时何地完成功能。

引入(Introduction)

引入能够让我们在不修改原有类的代码的基础上,添加生的方法或属性。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。

SpringAOP

SpringAOP的特点

SpringAOP是通过动态代理实现的。不同于AspectJ等其他aop框架,SpringAOP只支持方法级别的切点,不支持类构造器或字段级别的切点。因此只能拦截方法进行通知,而在对象创建或对象变量的修改时,都无法应用通知

SpringBoot集成SpringAOP

SpringBoot引入AOP依赖后,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy

现在我们来设想一个场景:我有若干个业务场景,每个场景对应一个服务,有些服务需要我在调服务的时候,打印开始和结束信息,并输入服务处理的时长。

- 依赖引入

首先在项目中引入SpringAOP的依赖:

<!--引入AOP依赖-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

- 创建注解

创建一个自定义注解@ApiLog,用boolean类型的字段来决定是否开启日志输入功能,代码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
    boolean isOn() default true;
}

- 定义切面

定义一个切面类MyAspect.java,代码如下

@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
    private void onePointcut(){}

    @Around("onePointcut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{

        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        /* 获取方法上的注解 */
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        if (apiLog != null && apiLog.isOn()){
            System.out.println("ApiLog is on!");
        }

        Object[] objects = point.getArgs();
        String bussinessName = (String) objects[0];
        System.out.println("-- " + bussinessName + " start --");
        long begin = System.currentTimeMillis();
        Object object = point.proceed();
        long end = System.currentTimeMillis();
        System.out.println("-- " + bussinessName + " end  耗时" + (end - begin) + "ms --");
        return object;
    }
}

- 设置切点

@Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
private void onePointcut(){}

@Pointcut注解表示声明一个切点,然后我们要配置execution指示器,它用来匹配所有符合条件的连接点。已上面的指示器配置* com.acelin.hello.study.springTest.aop.AspectController.*(..)为例,分析一下指示器的写法

  • 第一个*号:表示的不关心方法的返回值类型
  • 中间的com.acelin.hello.study.springTest.aop.AspectController.*指定方法的特点
  • 第二个*号:表示AspectController类下的任意方法
  • (..):表示不关心参数个数和类型

- 业务接口编写

我们写一个简单的接口,然后注释我们自定义的注解,开启相关日志的输出。

@RestController
@RequestMapping("/aop")
public class AspectController implements IAspect{

    @RequestMapping("/test/1/{businessName}")
    @ApiLog(isOn = true)
    public void testAop(@PathVariable("businessName") String businessName){
        System.out.println("This is a controller about aop test!");
    }
}

- 测试

测试结果如下:

ApiLog is on!
-- 业务1 start --
This is a controller about aop test!
-- 业务1 end  耗时5ms --

通知时机

以上我们通过一个简单的例子,初步体验了aop的魅力。接下来我们来讨论以下各类通知的特点以及应用时机问题。我们对的上面的切面类进行了修改,加上所有类型的通知。

@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
    private void onePointcut(){}

    @Before("onePointcut()")
    public void before(){
        System.out.println("-- before");
    }

    @After("onePointcut()")
    public void after(JoinPoint joinPoint) throws NoSuchMethodException{
        System.out.println("-- after");
    }

    @AfterThrowing("onePointcut()")
    public void afterThrowing(){
        System.out.println("-- afterThrowing");
    }

    @AfterReturning("onePointcut()")
    public void afterReturning(){
        System.out.println("-- afterReturning");
    }

    @Around("onePointcut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{

        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        /* 获取方法上的注解 */
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        if (apiLog != null && apiLog.isOn()){
            System.out.println("ApiLog is on!");
        }

        Object[] objects = point.getArgs();
        String bussinessName = (String) objects[0];
        System.out.println("-- " + bussinessName + " start");
        long begin = System.currentTimeMillis();
        Object object = point.proceed();
        long end = System.currentTimeMillis();
        System.out.println("-- " + bussinessName + " end  耗时" + (end - begin) + "ms");
        return object;
    }
}

然后修改接口,怎加业务名称的一个长度判断,如果名称太长,抛出异常,来模拟方法执行过程中法僧异常

@RequestMapping("/test/1/{businessName}")
@ApiLog
public void testAop(@PathVariable("businessName") String businessName){
    System.out.println("This is a controller about aop test!");
    if (businessName.length() > 4){
        throw new RuntimeException("业务名称太长!");
    }
}

- 正常情况

来看一下当方法正常执行的情况下,前置通知、后置通知、返回通知和环绕通知的应用时机。

再次调用测试接口,可看到如下结果:

ApiLog is on!
-- 业务 start
-- before
This is a controller about aop test!
-- afterReturning
-- after
-- 业务 end  耗时6ms

- 异常情况

然后我们修改业务名称为,使其超过规定长度导致触发异常抛出,结果如下:

ApiLog is on!
-- 超级复杂的业务 start
-- before
This is a controller about aop test!
-- afterThrowing
-- after

java.lang.RuntimeException: 业务名称太长!

at com.acelin.hello.study.springTest.aop.AspectController.testAop(AspectController.java:16)

从上面结果可以看出的,前置通知应用于目标方法执行前,然后当方法正常执行时,会在方法结束前执行返回通知,如果发生异常,执行异常通知,返回通知不执行(因为方法已经中断,不正常返回),而后置通知的是不管方法执行情况,都会在方法结束后执行。环绕通知则包裹着以上所有流程。

总结

以上就是对AOP相关知识的入门,相关术语的解释,以及SpringBoot集成SpringAOP的简单应用,,希望大家以后多多支持我们!

(0)

相关推荐

  • 详解springboot+aop+Lua分布式限流的最佳实践

    一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,

  • 如何解决SpringBoot 加入AOP后无法注入的问题

    SpringBoot 开启AOP后 出现无法注入的问题 真是坑的很啊~ 提示错误 org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type 或者 java.lang.ClassCastException: com.sun.proxy.$Proxy12 cannot be cast to cn.edu.nuc.SpringTest.service.impl.DemoServiceI

  • 解决springboot的aop切面不起作用问题(失效的排查)

    检查下springboot的启动类是否开启扫描 @SpringBootApplication @ComponentScan(basePackages = {"com.zhangpu.springboot"}) 另外springboot默认开启的EnableAspectJAutoProxy为true 如果不放心可以增加: @EnableAspectJAutoProxy(proxyTargetClass=true) 第二种可能: 没有导入 相关的jar <dependency>

  • SpringBoot Aop 详解和多种使用场景解析

    前言 aop面向切面编程,是编程中一个很重要的思想本篇文章主要介绍的是SpringBoot切面Aop的使用和案例 什么是aop AOP(Aspect OrientedProgramming):面向切面编程,面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 使用场景 利用AOP可以对我们边缘业务进行隔离,降低无关业务逻辑耦

  • Springboot+AOP实现返回数据提示语国际化的示例代码

    前言 本篇内容: 提示语的国际化返回,自定义多语言. 本文使用aop方式,拦截接口返回的数据,进行转换. 正文 先看这次示例教学的项目 目录结构: (当然resource里面的i18n文件夹和三个properties文件也是要我们自己建的,但是 那个Resource Bundle 不用管,这个在yml加上对应配置项自动生成的. 不清楚的继续往下看教学就好) 开始敲(CV)代码: pom.xml 依赖: <dependencies> <dependency> <groupId&

  • 详解Spring AOP自定义可重复注解没有生效问题

    目录 1. 问题背景 2. 不啰嗦,上代码 3. 问题排查 3.1 是不是切点写得有问题,于是换成如下形式: 3.2 是不是使用的地方不是代理对象 4. 问题原因 1. 问题背景 工作中遇到这样的场景:某个方法需要在不同的业务场景下执行特定的逻辑,该方法已经上生产,不想改变原来的代码,因此决定用AOP做个切面执行逻辑. 2. 不啰嗦,上代码 以下为核心代码: 定义注解: @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(Rete

  • 详解Spring AOP 实现“切面式”valid校验

    why: 为什么要用aop实现校验? answer: spring mvc 默认自带的校验机制 @Valid + BindingResult, 但这种默认实现都得在Controller方法的中去接收BindingResult,从而进行校验. eg: if (result.hasErrors()) { List<ObjectError> allErrors = result.getAllErrors(); List<String> errorlists = new ArrayList

  • 详解Spring AOP 实现主从读写分离

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写分离」并不是多么神奇的东西,也带不来多么大的性能提升,也许更多的作用的就是数据安全的备份吧. 从一个库到读写分离,从理论上对服务器压力来说是会带来一倍的性能提升,但你仔细思考一下,你的应用服务器真的很需要这一倍的提升么?那倒不如你去试着在服务器使用一下缓存系统,如 Memcached.Redis 这

  • 详解Spring AOP 拦截器的基本实现

    一个程序猿在梦中解决的 Bug 没有人是不做梦的,在所有梦的排行中,白日梦最令人伤感.不知道身为程序猿的大家,有没有睡了一觉,然后在梦中把睡之前代码中怎么也搞不定的 Bug 给解决的经历?反正我是有过. 什么是 AOP ? AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生

  • 详解Spring Aop实例之AspectJ注解配置

    上篇<Spring Aop实例之xml配置>中,讲解了xml配置方式,今天来说说AspectJ注解方式去配置spring aop. 依旧采用的jdk代理,接口和实现类代码请参考上篇博文.主要是将Aspect类分享一下: package com.tgb.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aft

  • 详解Spring AOP

    目录 什么是AOP? AOP术语 通知(Advice) 连接点(Join point) 切点(Pointcut) 连接点和切点的区别 切面(Aspect) 引入(Introduction) 织入(Weaving) SpringAOP SpringAOP的特点 SpringBoot集成SpringAOP - 依赖引入 - 创建注解 - 定义切面 - 设置切点 - 业务接口编写 - 测试 通知时机 - 正常情况 - 异常情况 总结 什么是AOP? ​AOP,即我们平时经常提到的面向切面编程.首先我们

  • 详解Spring Aop实例之xml配置

    AOP的配置方式有2种方式:xml配置和AspectJ注解方式.今天我们就来实践一下xml配置方式. 我采用的jdk代理,所以首先将接口和实现类代码附上 package com.tgb.aop; public interface UserManager { public String findUserById(int userId); } package com.tgb.aop; public class UserManagerImpl implements UserManager { publ

  • 详解spring中aop不生效的几种解决办法

    先看下这个问题的背景:假设有一个spring应用,开发人员希望自定义一个注解@Log,可以加到指定的方法上,实现自动记录日志(入参.出参.响应耗时这些) package com.cnblogs.yjmyzz.springbootdemo.aspect; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy

  • 一篇文章从无到有详解Spring中的AOP

    前言 AOP (Aspect Orient Programming),直译过来就是 面向切面编程.AOP 是一种编程思想,是面向对象编程(OOP)的一种补充.面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面. 从<Spring实战(第4版)>图书中扒了一张图: 从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块. <?xml version="1.0" encoding="UTF-8&qu

  • 详解spring与shiro集成

    Shiro的组件都是JavaBean/POJO式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web应用的集成. 在示例之前,需要导入shiro-spring及spring-context依赖,具体请参考pom.xml. spring-beans.xml配置文件提供了基础组件如DataSource.DAO.Service组件的配置. JavaSE应用  spring-shiro.xml提供了普通JavaSE独立应用

随机推荐