草庐IT

spring aop详解

hanbin1998 2023-07-28 原文

1.前言

spring aop是一个面向切面的编程,在自己第一遍学习的时候,感觉aop没有什么作用,但是真实接触下来,感觉spring aop还是很有用途的,感觉自己之前的想法太年轻了。

2.概念

Spring 提供了两种AOP 的实现:基于注解式配置和基于XML配置,我这里主要就是介绍一下,基于注解式配置。

2.1 AOP 即 Aspect Oriented Program 面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

  • 所谓的核心业务,工作中做的最多的就是增删改查,增删改查都叫核心业务。

  • 所谓的周边功能,比如性能统计,日志记录,事务管理等等

周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP

2.2 AOP 的目的

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

2.3 AOP 当中的概念:

  • 切面 (Aspect)可以理解成,就是一个特殊的类(包含的都是增强核心业务的代码),切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!

  • 连接点(join point)通俗理解就是整个系统的所有方法都可以称为连接点

  • 切入点(Pointcut) 就是被选中的连接点,可以通过execution来确定选中的连接点有哪些

  • 通知(Advice)在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能),就是切面这个类中的代码块

  • 织入(Weaving) 把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

2.4 Spring Aop中的通知类型:

  • 前置通知(Before Advice): 在目标方法被调用前调用通知功能;相关的类org.springframework.aop.MethodBeforeAdvice

  • 后置通知(After Advice): 在目标方法被调用之后调用通知功能;相关的类org.springframework.aop.AfterReturningAdvice

  • 返回通知(After-returning): 在目标方法成功执行之后调用通知功能;

  • 异常通知(After-throwing): 在目标方法抛出异常之后调用通知功能;相关的类org.springframework.aop.ThrowsAdvice

  • 环绕通知(Around): 把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptor

2.5 spring Aop实现的基础

spring aop实现是通过动态代理的方式实现的,动态代理避免了静态代理需要定义冗余的代理类,实现类,动态代理分为两种,第一种就是jdk 动态代理,第二种就是cglib 动态代理,aop 实现同时采用两种代理模式。

两种动态代理的区别:

jdk动态代理模式 :采用反射的方式,只能对实现接口的类生成代理,具有加载速度快,执行效率低的特点。

cglib动态代理模式:采用的asm,通过字节码形式实现,是针对类实现代理,具有加载速度慢,执行效率高的特点。

2.6 基于AspectJ实现基础上需要连接的几个内置注解

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选) 返回类型 方法名(参数) 异常模式(可选))

参数部分允许使用通配符:

* 匹配任意字符,但只能匹配一个元素

.. 匹配任意字符,可以匹配任意多个元素(零到若干个都可以),必须和*联合使用

"execution(public * com.qli.controller.TestController.*(..))"
@Pointcut(value = "@annotation(com.qli.config.RequestLog)")任何方法使用RequestLog注解都会触发该切面,进入切点

3.实现

具体的案例,在spring boot中实现spring aop,包含的内容有通过aop实现自定义注解,听起来就很高大上,其实懂了之后,感觉就那样,不过在新入门的程序员面前还是可以装起来的,还有就是多个切面的时候的执行顺序

3.1导入依赖

 <!--引入父依赖
         当前工程 继承 父类工程  spring-boot-starter-parent pom
    -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>
​
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
​
​
    <dependencies>
​
    <!-- web相关依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
​
    </dependency>
​
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

3.2创建切面

package com.qli.config;
​
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
​
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
​
@Aspect
@Component
@Order(2)
public class LogAspect {
    @Pointcut("execution(public * com.qli.controller.TestController.*(..))")
    public void webLog(){}
​
    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
​
    }
​
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }
​
    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }
​
    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }
​
    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        System.out.println("方法环绕start.....");
        try {
            Object o =  pjp.proceed();
            System.out.println("方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

3.3 创建控制类

package com.qli.controller;
import com.qli.config.RequestLog;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
public class TestController {
​
    @RequestMapping("/requestLog")
    public String requestLog(){
        return "first controller";
    }
}

执行结果:

方法环绕start.....
URL : http://localhost:8088/requestLog
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.qli.controller.TestController.requestLog
ARGS : []
方法环绕proceed,结果是 :first controller
方法最后执行.....
方法的返回值 : first controller

上边是创建一个切面通过 @Pointcut("execution(public * com.qli.controller.TestController.*(..))")注解来限制切入点是controller包下TestController控制类下所有的方法都是切入点,那么congtroller包下其他控制类的方法就是连接点。

3.4创建自定义注解

3.4.1自定义注解之前需要了解的概念

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:

    public enum ElementType {
        /** 类,接口(包括注解类型)或枚举的声明 */
        TYPE,
    ​
        /** 属性的声明 */
        FIELD,
    ​
        /** 方法的声明 */
        METHOD,
    ​
        /** 方法形式参数声明 */
        PARAMETER,
    ​
        /** 构造方法的声明 */
        CONSTRUCTOR,
    ​
        /** 局部变量声明 */
        LOCAL_VARIABLE,
    ​
        /** 注解类型声明 */
        ANNOTATION_TYPE,
    ​
        /** 包的声明 */
        PACKAGE
    }
  • @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         * (注解将被编译器忽略掉)
         */
        SOURCE,
    ​
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         * (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
         */
        CLASS,
    ​
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }

  • @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

3.5 实现

3.5.1 创建自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RequestLog {
    String desc() default "无信息";
}

3.5.2 创建自定义注解时候后将会触发的切面

package com.qli.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1)
public class RequestAspect {
    @Pointcut(value = "@annotation(com.qli.config.RequestLog)")
    public void access() {

    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("second before");
    }

    @Around("@annotation(requestLog)")
    public Object around(ProceedingJoinPoint pjp, RequestLog requestLog) {
        //获取注解里的值
        System.out.println("second around:" + requestLog.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

3.5.3 在控制层添加

@RequestLog(desc = "second")
    @RequestMapping("/second")
    public Object second(){
        return "second controller";
    }

执行结果

second around:second
second before

如果将两个切面的切入点都包含有second这个方法的时候,浏览器访问http://localhost:8088/second的执行结果如下

second around:second
second before
方法环绕start.....
URL : http://localhost:8088/second
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.qli.controller.TestController.second
ARGS : []
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller

执行顺序:

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

有关spring aop详解的更多相关文章

  1. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  2. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  3. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  4. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  5. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

  6. IDEA 2022 创建 Spring Boot 项目详解 - 2

    如何用IDEA2022创建并初始化一个SpringBoot项目?目录如何用IDEA2022创建并初始化一个SpringBoot项目?0. 环境说明1.  创建SpringBoot项目 2.编写初始化代码0. 环境说明IDEA2022.3.1JDK1.8SpringBoot1.  创建SpringBoot项目        打开IDEA,选择NewProject创建项目。        填写项目名称、项目构建方式、jdk版本,按需要修改项目文件路径等信息。        选择springboot版本以及需要的包,此处只选择了springweb。        此处需特别注意,若你使用的是jdk1

  7. 【华为OD技术面试 | 真八股 】MySQL联合索引,谈springIOC的理解,谈springAOP的理解,Erika和zookeeper等问题 - 2

    文章目录华为OD面试流程1.mysql数据库建了两个字段,且设置了联合索引,如果其中有一个字段为空会出现什么问题?2.谈谈springIOC的理解,有什么好处,解决了什么问题3.谈谈springAOP的理解,切面编程有没有实际应用,有哪些注解,作用是什么,有那些应用场景?4.Erika和zookeeper有了解过吗,作用是什么,主要解决了什么问题5.谈谈JDK、JRE、JVM的理解,区别是什么6.谈谈对泛型的理解7.JVM的组成华为OD面试流程机试:三道算法题,关于机试,橡皮擦已经准备好了各语言专栏,可以直接订阅。性格测试:机试技术一面(本专栏核心)技术二面(本专栏核心)主管面试定级定薪发of

  8. 详解Unity中的粒子系统Particle System (二) - 2

    前言上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解。从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状态和全局属性的,以下是关于该模块的介绍,请大家指正。目录前言本系列提要一、粒子系统主模块1.阅读前注意事项2.参考图3.参数讲解DurationLoopingPrewarmStartDelayStartLifetimeStartSpeed3DStartSizeStartSize3DStartRotationStartRotationFlipRotationStartColorGravityModif

  9. VMware虚拟机与本地主机进行磁盘共享(详解) - 2

    VMware虚拟机与本地主机进行磁盘共享前提虚拟机版本为Windows10(专业版,不是可能有问题)本地主机为家庭版或学生版(此版本会有问题,但有替代方式)最好是专业版VMware操作1.关闭防火墙,全部关闭。2.打开电脑属性3.点击共享-》高级共享-》权限4.如果没有everyone,就添加权限选择完全控制,然后应用确定。5.打开cmd输入lusrmgr.msc(只有专业版可以打开)如果不是专业版,可以跳过这一步。点击用户-》administrator密码要复杂密码,否则不行。推荐admaiN@1234类型的密码。设置完密码,点击属性,将禁用解开。6.如果虚拟机的windows不是专业版,可

  10. ElasticSearch之 ik分词器详解 - 2

    IK分词器本文分为简介、安装、使用三个角度进行讲解。简介倒排索引众所周知,ES是一个及其强大的搜索引擎,那么它为什么搜索效率极高呢,当然和他的存储方式脱离不了关系,ES采取的是倒排索引,就是反向索引;常见索引结构几乎都是通过key找value,例如Map;倒排索引的优势就是有效利用Value,将多个含有相同Value的值存储至同一位置。分词器为了配合倒排索引,分词器也就诞生了,只有合理的利用Value,才会让倒排索引更加高效,如果一整个Value不进行任何操作直接进行存储,那么Value和key毫无区别。分词器Analyzer通常会对Value进行操作:一、字符过滤,过滤掉html标签;二、分

随机推荐