当前位置: 首页 > news >正文

南京网站建设cneeseo体系

南京网站建设cnee,seo体系,centos wordpress安装,双语网站方法目录 前言 什么是AOP 什么是Spring AOP? 使用Spring AOP 引入依赖 编程AOP程序 优势 详解Spring AOP Spring AOP核心概念 切点 连接点 通知 切面 通知类型 结论 切点提取 切面优先级 Order 注解 切点表达式 execution切点表达式 annotation 自定义…

目录

前言

什么是AOP

什么是Spring AOP?

使用Spring AOP

引入依赖

编程AOP程序

优势

详解Spring AOP

Spring AOP核心概念

切点

连接点

通知

切面

 通知类型

结论

 切点提取

 切面优先级 @Order 注解

切点表达式

execution切点表达式

@annotation

自定义注解


前言

在前面我们介绍了Spring有两大核心:IoC和AOP,我们已经讲解了什么是IoC,以及如何使用IoC,那么本篇我们就来讲解什么是AOP,以及如何使用Spring AOP。

什么是AOP

AOP(Aspect Oriented Programming) 是一种编程范型,即面向切面编程。

什么是面向切面编程?

切面就是指某一类特定问题,所以AOP也可以理解为面向特定方法编程。面向编程旨在通过分离横切关注点来提高代码的模块化和可维护性,允许开发者将那些与业务逻辑无关,但又需要在多个地方使用的功能(如日志记录、事务管理、安全性等)从业务逻辑中分离出来。在前面我们的登录校验,用了拦截器,其实也是对AOP思想的一种实现。统一数据返回格式和统一异常处理也是AOP的一种实现。

结论:AOP是一种思想,是对某一类事情的集中处理

AOP是一种思想,实现它的方法有很多,如Spring AOP,以及AspectJ、CGLIB等。

接下来我们就来学习一下Spring AOP。

什么是Spring AOP?

Spring AOP是Spring框架对面向切面编程的支持,允许开发者将横切关注点从业务逻辑中分离出来,从而提高代码的模块化、可维护性和可扩展性。

Spring AOP 是基于动态代理实现的,支持通过注解或者XML配置来定义切面逻辑。那什么是动态代理,我们下一篇讲。

我们在前面学的拦截器、统一数据返回格式以及统一异常处理这些还不够吗?

拦截器作用的维度是URL(一次请求和响应),@ControllerAdvice 的应用场景主要是全局异常处理,数据绑定,数据预处理等,而AOP的作用维度更加细致(可以根据包、类、方法名、参数等进行拦截),能够实现更加复杂的业务逻辑

假如我们现在有一个项目,想要对其中一些业务功能进行优化,那么我们就需要知道它的耗时时长,需要对每个接口都进行添加计算耗时的逻辑,这样就太麻烦了,而且成本高。

但如果我们使用AOP,可以对原始接口不修改的情况下,对特定的方法进行功能增强。

使用Spring AOP

引入依赖

在使用AOP之前,我们需要先引入AOP的依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

编程AOP程序

我们这里来编写一个AOP类,定义一个记录方法执行耗时的方法。

需要用 @Aspect 注解修饰类,同时我们需要将这个AOP类交给Spring容器来管理,需要用类注解修饰@Component:

package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class TimeAspect {//切面是由切点+通知组成的@Around("execution(* com.example.demo.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//1、起始时间long start = System.currentTimeMillis();log.info("around处理前");//2、执行目标方法Object result=pjp.proceed();//3、终止时间long end=System.currentTimeMillis();//4、返回计算结果log.info("around处理后");log.info("耗时:{}ms",end-start);return result;}
}
  • @Aspect:用这个注解修饰,表示这个类是一个切面类;
  • @Around:环绕通知,在目标方法执行前后都会被执行。后面的表达式对哪些方法进行增强;
  • pjp.proceed():让目标方法执行。

整个方法可以分为三部分: 

package com.example.demo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@Slf4j
@RequestMapping("/aop")
public class AopController {@RequestMapping("/t1")public String t1(){log.info("t1方法执行");return "t1";}}

 我们运行一下:

可以看到,我们对调用的方法不比修改,就可以做出额外的一些工作。

优势

  •  代码不侵入:不修改原始的业务方法,就可以堆原始的业务方法进行功能增强或是功能上的改变;

  • 减少重复代码

  • 提高开发效率

  • 方便维护

详解Spring AOP

既然我们知道了Spring AOP的初始用法,那么就下来我们就来学习一下Spring AOP几个核心概念:

  • 切点
  • 连接点
  • 切面
  • 通知

Spring AOP核心概念

切点

切点(PointCut),也叫做“切入点”,提供一组规则(使用 Aspect pointCut expression language来描述),告诉程序哪些方法需要增强

前面代码中在注解@Around后面的 execution(* com.example.demo.controller.*.*(..))

字符串就是切点表达式:

切点表达式中的含义我们后面讲。

连接点

满足切点表达式规则的方法,就是连接点,也就是可以被AOP控制的方法。例如上面例子中也就是在 com.example.demo.controller 包下的所有类都叫做连接点。

在上面的例子中,pjp就是一个连接点:

通过执行该参数的proceed() 方法就可以执行目标方法。 

通知

通知指的就是具体做的工作,需要重复执行的逻辑,也就是共性功能(最终体现为一个方法)

我们在上面例子中计算方法执行耗时的逻辑,就是通知。

 

切面

切面(Aspect)是由切点(PointCut)+通知(Advice)组成的

切面封装了横切关注点的逻辑,横切关注点指的是那些影响多个类或模块的逻辑,如日志记录、事务管理等。

通过切面就能够描述当前AOP程序需要对哪些方法,在什么时候执行什么样的操作。切面既包含了通知逻辑的定义,也包括了连接点的定义。切面所在的类,我们一般称为切面类(被@Aspect修饰的类)。

 通知类型

在Spring AOP中的通知类型,不仅仅只有我们上面例子中用到的 @Around (环绕通知)注解,还有以下其它几种通知类型,总结起来:

  • @Around:环绕通知,此注解修饰的通知方法在目标方法执行前后都被执行;
  • @Before:前置通知,此注解标注的通知方法在目标方法执行前被执行;
  • @After:后置通知,此注解标注的通知方法在目标方法执行后被执行;
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法执行前被执行,有异常不会执行;
  • @AfterThrowing:异常后通知,此注解标注的通知方法在目标方法发送异常后执行

我们通过代码来加深一下对这几个通知的理解:

package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class TimeAspect {//切面是由切点+通知组成的@Around("execution(* com.example.demo.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//1、起始时间long start = System.currentTimeMillis();log.info("around处理前");//2、执行目标方法Object result=pjp.proceed();//3、终止时间long end=System.currentTimeMillis();//4、返回计算结果log.info("around处理后");log.info("耗时:{}ms",end-start);return result;}@After("execution(* com.example.demo.controller.*.*(..))")public void afterRecordTime(){log.info("执行After通知");}@Before("execution(* com.example.demo.controller.*.*(..))")public void beforeRecordTime(){log.info("执行Before通知");}@AfterReturning("execution(* com.example.demo.controller.*.*(..))")public void afterReturningRecordTime(){log.info("执行AfterReturning通知");}@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")public void afterThrowingRecordTime(){log.info("执行AfterThrowing通知");}}

测试类:

package com.example.demo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@Slf4j
@RequestMapping("/aop")
public class AopController {@RequestMapping("/t1")public String t1(){log.info("t1方法执行");return "t1";}@RequestMapping("/t3")public String t3(){int a = 1/0;return "t3";}
}

运行一下进行测试:

对于t1方法:

我们可以看到,在程序正常运行的情况下,由 @AfterThrowing 修饰的方法并不会被执行、。

同时,我们可以观察到, @Around 标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”。其中“前置逻辑”会先于 @Before 标识的通知方法执行,“后置逻辑”会晚于 @After 标识的通知方法执行。

 

对于t3方法:

t3方法中我们给了一个异常, 在程序发生异常的时候,@AfterReturning 标识的方法不会被执行,但@AfterThrowing 标识的通知方法被执行了。

@Arounf环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会被执行(因为此时原始方法调用出现了问题)

结论

如果目标方法中不出现异常:

 

目标方法中出现异常:

注意:

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑;
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的;
  • 一个切面类可以有多个切点

 切点提取

在前面的代码中,我们可以看到,每个通知的切点表达式都是一样的,那么我们有没有办法可以将这些公共的表达式提取出来,需要的使用引入即可?

在Spring中,提供了 @PointCut 注解,可以把公共的切点表达式提取出来

所以上面的代码我们可以修改为:

package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class TimeAspect {@Pointcut("execution(* com.example.demo.controller.*.*(..))")public void pointCut(){}//切面是由切点+通知组成的@Around("pointCut()")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//1、起始时间long start = System.currentTimeMillis();log.info("around处理前");//2、执行目标方法Object result=pjp.proceed();//3、终止时间long end=System.currentTimeMillis();//4、返回计算结果log.info("around处理后");log.info("耗时:{}ms",end-start);return result;}@After("pointCut()")public void afterRecordTime(){log.info("执行After通知");}@Before("pointCut()")public void beforeRecordTime(){log.info("执行Before通知");}@AfterReturning("pointCut()")public void afterReturningRecordTime(){log.info("执行AfterReturning通知");}@AfterThrowing("pointCut()")public void afterThrowingRecordTime(){log.info("执行AfterThrowing通知");}}

如果存在多个切面类时,其他切面类想要使用这个切点定义时,就需要将访问修饰符修改为public,其他切面类引用这个切点表达式的方法需要使用 全限定类名.方法名() 。如果只想要在该类中使用,就用private修饰。

package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
public class Aspectdemo {@After("com.example.demo.aspect.TimeAspect.pointCut()")public void after() {log.info("执行Aspectdemo的after方法");}@Before("com.example.demo.aspect.TimeAspect.pointCut()")public void before() {log.info(" 执行Aspectdemo的before方法");}
}

 切面优先级 @Order 注解

那么存在多个切面的时候,它们的执行顺序是怎么的?随机的?其实不是的,如果有多个切面类的多个切入点都匹配到同一个目标方法,当目标方法运行时,会执行以下两个原则:

  1. 前面通知类型的先后顺序
  2. 切面类的类名排序

这里怎么不是先执行完Aspectdemo中的通知方法再执行TimeAspect中的通知方法?

 切面类中的通知方法的执行顺序,我们可以看成以下这幅图:

那么如果我们想要改变一下切面类的执行顺序,就需要在想要优先执行的切面类上添加 @Order()注解。

可以看到,通过 @Order注解并填入value值,就能够改变切面类的执行顺序,value值越小的切面类优先级越高。

切点表达式

前面我们使用切点表达式来描述切点,下面我们就来介绍一下切点表达式的语法。

切点表达式常见有两种表达方式:

  • excution(......):根据方法的签名来匹配;
  • @annitation(......):根据注解匹配

execution切点表达式

execution()是最常用的一种切点表达式,用来匹配方法,语法:

execution(<访问修饰符>  <返回类型>  <包名.类名.方法(方法参数)>  <异常>)

 

 其中:访问修饰符合异常可以省略

切点表达式支持通配符表达:

  1. *匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
    1. 包名使用 * 表示任意包(一层包使用一个*)

    2. 类名使用 * 表示任意类

    3. 返回值使用 *表示任意返回值类型

    4. 方法名使用 * 表示任意方法

    5. 参数使用 * 表示一个任意类型的参数

  2. .. :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

    1. 使用 .. 配置包名,表示此包以及此包下的所有子包

    2. 可以使用 .. 配置参数,任意个任意类型的参数

示例:

AopController下public修饰,返回类型为String,方法名t1,无参方法:

execution(public String com.example.demo.controller.AopController.t1())

省略访问修饰符:

execution(String com.example.demo.controller.AopController.t1())

匹配所有返回类型:

execution(* com.example.demo.controller.AopController.t1())

匹配AopController下的所有无参方法:

execution(* com.example.demo.controller.AopController.*())

匹配AopController下的所有方法:

execution(* com.example.demo.controller.AopController.*(..))

匹配controller包下所有类的所有方法:

execution(* com.example.demo.controller.*.*(..))

匹配所有包下面的AopController:

execution(* com..controller.AopController.*(..))

匹配com.example.demo包下,子孙包下的所有类的所有方法:

execution(* com.example.demo..*(..))

@annotation

execution表达式更适用有规则的,但如果我们想要匹配多个无规则的方法,比如我们想要增强AopController中t1方法和t2方法,但由于返回值不同,所以使用execution不能同时增强这两个方法。

那么这里就需要用到我们的 @annotation 注解来捕获更多无规则的方法。

自定义注解

那么如何使用 @annotation 注解呢?

我们首先需要创建出一个自定义的注解:

package com.example.demo.aspect;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

这里我们创建了一个简单的注解,参考@Component注解:

  • @Target:标识了Annotation所修饰的对象范围,即该注解在什么地方可以用;
    • ElementType.TYPE:⽤于描述类、接⼝(包括注解类型)或enum声明
    • ElementType.METHOD:描述方法
    • ElementType.PARAMETER:描述参数
    • ElementType.TYPE_USE:可以标注任意类型
  • @Retention 指Annotation 被保留的时间长短,标明注解的⽣命周期:
    • RetentionPolicy.SOURCE表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使⽤。比如 @SuppressWarnings ,以及lombok提供的注解 @Data ,@Slf4j
    • RetentionPolicy.CLASS编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时⽆法获取。通常⽤于⼀些框架和⼯具的注解
    • RetentionPolicy.RUNTIME运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常⽤于⼀些需要在运行时处理的注解,如Spring的 @Controller @ResponseBody

我们自定完注解之后,就需要在切面类中使用@annotation切点表达式定义切点,只对@MyAspect生效。 

@annotation中需要填入我们自定义注解的全限定名。

package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
@Order(2)
public class Aspectdemo {@Around("@annotation(com.example.demo.aspect.MyAspect)")public Object around(ProceedingJoinPoint pjp) throws Throwable {log.info("执行Aspectdemo的around方法前");long start = System.currentTimeMillis();Object proceed = pjp.proceed();long end = System.currentTimeMillis();log.info("耗时:{}ms", end - start);log.info("执行Aspectdemo的around方法后");return pjp.proceed();}}

 

 

可以看到,我们只要在对应的方法上加上@MyAspect注解,就能够对对应方法进行增强。


以上就是本篇所有内容~

若有不足,欢迎指正~ 

http://www.cadmedia.cn/news/6200.html

相关文章:

  • 天猫网站建设的意义青岛官网优化
  • wordpress请求接口的方式北京seo排名优化网站
  • 龙岩网站设计网站交易
  • 网站制作哪里做得好百度安装免费下载
  • 沈阳网站优化怎么做动态网站建设
  • 企业网站类型有哪些如何做好线上营销
  • 网站建设设计780元全包推广软文代写
  • 铁道部建设监理协会网站查询百度快速排名化
  • 无锡市无锡市住房和城乡建设局网站附近的计算机培训班
  • 公司怎样建设阿里巴巴网站app开发制作
  • 新疆网站制作广州网站建设正规公司
  • java jsp做网站360优化大师下载官网
  • 免费一级域名网站搜索最多的关键词的排名
  • 寿光市建设局网站重庆网站建设公司
  • 网站服务器查找网站友情链接代码
  • 30天网站建设 视频教程免费学生网页制作成品
  • 建企业网站步骤个人免费网站创建入口
  • 广西省河池建设局网站国内最大的搜索引擎
  • 深圳专业的免费建站软文的概念
  • 高端大气公司名称seo草根博客
  • 涿州住房和城乡建设局网站seo博客
  • 专业建站lhznkj推广商
  • 上海十大b2c网站建设免费的个人网站html代码
  • 新手学做网站难吗友情链接有哪些作用
  • 宁波公司注册代理公司网站运营推广选择乐云seo
  • 广东粤建设计院网站推广计划怎么做
  • 手机app网站模板seo技术培训学校
  • 建设网站好学吗google广告
  • 郑州市人民政府官方网站郑州百度推广代运营
  • 招标公司网站建设方案怎么推广游戏代理赚钱