spring boot 使用 AOP 的正确姿势 --- 心得
1.前言
向spring boot转型,所有的配置基本上是用注解完成 ,以前使用spring MVC 需要写一大堆xml文件来配置。
基本上没什么变化,但是有些地方需要注意:
- 环绕通知不要使用异常捕获,否则出现异常后,异常通知不会执行,而返回通知仍然会执行,
- 同时返回结果为null,
可以使用 throws Throwable 配合pjp.proceed(); 即可,这样就不会出现红色下划线!
如果在同一个方法引入多个切面,那么需要定义使用顺序,先开始执行的切面将最后结束,呈包含关系,
在切面类使用注解@Order(整数) 来定义顺序 ,数字越小,执行的越早。
2.AOP术语解释
- 通知、增强处理(Advice) 就是你想要的功能,也就是上说的安全、事物、日子等。
你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码
- 连接点(JoinPoint) 这个就更好解释了,就是spring允许你是通知(Advice)的地方,
那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,
spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那
不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。
- 切入点(Pointcut) 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,
那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),
你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定
义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
- 切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了
让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过
方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
- 引入(introduction) 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
- 目标(target) 引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,
他可以在毫不知情的情况下,被咋们织入切面。二自己专注于业务本身的逻辑。
- 代理(proxy) 怎么实现整套AOP机制的,都是通过代理,这个一会儿给细说。
- 织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程,有三种方式,但spring采用的是运行时
- 目标对象 – 项目原始的Java组件。
- AOP代理 – 由AOP框架生成java对象。
- AOP代理方法 = advice + 目标对象的方法。
3.操作
(1)目录结构
(2)随意做一个业务层


- package com.example.javabaisc.service;
- public interface FoodService {
- public String food();
- }
接口


- package com.example.javabaisc.service.serviceImpl;
- import com.example.javabaisc.service.FoodService;
- import org.springframework.stereotype.Service;
- @Service
- public class FoodServiceImpl implements FoodService {
- @Override
- public String food() {
- System.out.println("=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy");
- System.out.println("吃什么");
- System.out.println("=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy");
- return "apple and tea";
- }
- }
实现类
(3)做一个controller层接口


- package com.example.javabaisc.controller;
- import com.example.javabaisc.service.FoodService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- public class VVController {
- @Autowired
- private FoodService foodService;
- @RequestMapping("/")
- public String gg(int id) {
- if (id == 1) {
- // 强制抛出异常
- throw new RuntimeException();
- } else {
- return foodService.food();
- }
- }
- }
这已经完整了一个功能业务了,
AOP面向切面编程 不会影响业务程序的执行编码,但是可以影响输入参数与返回结果,因此controller和service该干嘛就干嘛,不会因为
有了AOP就需要其他特别的改动,当然,一些辅助操作可以放在切面 完成,如日志、发短信、发邮件等。【如果是分布式可以使用消息中间件啦】
(4)现在需要配置 aspect 【面向切面编程】类


- package com.example.javabaisc.aspect;
- 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;
- //aop切面注解,作用是把当前类标识为一个切面,供容器读取
- @Aspect
- @Component
- //定义切面执行顺序,如果在同一个方法有多个切面,那么需要定义使用顺序,先开始直送的切面将最后结束,呈包含关系
- @Order(1)
- public class myAspect {
- //定义一个公用方法
- // 切入点,表示切入的点,即程序中通用的逻辑业务,这里是请求的路径
- //
- //参数意思是 公共的 任意返回类型 该类里的所有方法 任意输入参数
- //参数public可要可不要
- @Pointcut("execution(public * com.example.javabaisc.controller.VVController.*(..))")
- public void log() {
- }
- //前置通知 ,表示当前方法是在具体的请求方法之前执行 【权限控制(权限不足抛出异常)、记录方法调用信息日志 】
- @Before("log()")
- public void doBefore(JoinPoint joinPoint) {
- System.out.println("====================");
- System.out.println("前置通知---before");
- System.out.println("====================");
- //如果是controller层 可使用这两句获取 参数 ,当然如果是restful 参数得自己解析路径
- // ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- // HttpServletRequest request = attributes.getRequest();
- //
- //获取请求的url
- // String url = request.getRequestURI();
- //
- //获取请求的方法
- // String method =request.getMethod();
- //
- //获取请求的ip
- // String ip = request.getRemoteAddr();
- //
- //获取请求参数,返回来的是个object 数组
- // Object[] args = joinPoint.getArgs();
- //也可以转成 list
- // List<Object> args = Arrays.asList(joinPoint.getArgs());
- //
- //获取该切入点所在方法的路径,getDeclaringTypeName()获取类名,joinPoint.getSignature().getName()获取方法名
- // String ss = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
- // System.out.println(ss);
- //打印com.example.javabaisc.controller.VVController.gg
- //
- //可以获取目标class
- // Class clazz = joinPoint.getSignature().getDeclaringType();
- }
- //异常通知 :在目标方法出现异常时才会进行执行的代码。【处理异常(一般不可预知),记录日志 】
- //方法体Exception参数:用来接收连接点抛出的异常。Exception类匹配所有的异常,可以指定为特定的异常 例如NullPointerException类等
- @AfterThrowing(pointcut = "log()", throwing = "ex")
- public void throwss(JoinPoint joinPoint, Exception ex) {
- System.out.println("====================");
- System.out.println("异常通知.....afterthrowing");
- System.out.println("====================");
- }
- //返回通知: 在目标方法正常结束时,才执行的通知 【 如银行在存取款结束后的发送短信消息 】
- @AfterReturning(returning = "obj", pointcut = "log()")
- public void doAfterReturnig(JoinPoint joinPoint, Object obj) {
- //处理完请求,返回内容
- System.out.println("====================");
- System.out.println("返回通知,reponse参数:");
- System.out.println(obj);
- System.out.println("返回通知---afterreturening");
- System.out.println("====================");
- }
- //后置通知,不管是抛出异常或者正常退出都会执行 ,都会执行(类似于finally代码功能) 【释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 】
- @After("log()")
- public void doAfter(JoinPoint joinPoint) {
- System.out.println("====================");
- System.out.println("后置通知---adfer");
- System.out.println("====================");
- }
- //环绕通知,环绕增强 【日志、缓存、权限、性能监控、事务管理 】
- @Around("log()")
- public Object arround(ProceedingJoinPoint pjp) throws Throwable {
- System.out.println("====================");
- System.out.println("环绕通知--开始--在前置通知之前");
- Object result = null;
- //获取参数
- // String methodName = pjp.getSignature().getName();
- // List<Object> args = Arrays.asList(pjp.getArgs());
- // try {
- //获取结果
- result = pjp.proceed();
- System.out.println("环绕通知around,结果是 :" + result);
- System.out.println("环绕通知------在方法结束之后");
- System.out.println("====================");
- // } catch (Throwable e) {
- // System.out.println("环绕通知---在环绕通知的方法里的 异常捕获 导致服务降级,不再触发异常通知方法,也就是说如果需要使用环绕通知,不允许在这里使用 catch ,可使用throws Throwable即可");
- // e.printStackTrace();
- // }
- System.out.println("环绕通知---在后置通知之前 ");
- return result;
- }
- }
关于切入点公共方法 注解里面的参数 说明:
public可要可不要
==============================================================================================
==============================================================================================
想象不到吧 ,spring boot搭建 aspect 竟然一个配置类就足够了,
回想一下 spring MVC ,不仅需要配置xml,还要配置相应的通知方法 ,
简直就是暗无天日的样子。
==============================================================================================
==============================================================================================
3.测试
(1)启动spring boot ,测试正常执行业务,查看控制台打印结果
网址访问 http://localhost:8080/?id=22
(2)测试抛出异常,,查看控制台打印结果
网址访问 http://localhost:8080/?id=1
(3)注意,千万不要在环绕通知使用异常捕获 catch ,否则会导致服务降级,异常通知不会执行,
而返回通知仍会执行,返回结果会是null。
否则会出现下图的打印顺序
spring boot 使用 AOP 的正确姿势 --- 心得的更多相关文章
- Spring Boot使用AOP的正确姿势
一.为什么需要面向切面编程? 面向对象编程(OOP)的好处是显而易见的,缺点也同样明显.当需要为多个不具有继承关系的对象添加一个公共的方法的时候,例如日志记录.性能监控等,如果采用面向对象编程的方法, ...
- spring boot 中AOP的使用
一.AOP统一处理请求日志 也谈AOP 1.AOP是一种编程范式 2.与语言无关,是一种程序设计思想 面向切面(AOP)Aspect Oriented Programming 面向对象(OOP)Obj ...
- Spring Boot学习——AOP编程的简单实现
首先应该明白一点,AOP是一种编程范式,是一种程序设计思想,与具体的计算机编程语言无关,所以不止是Java,像.Net等其他编程语言也有AOP的实现方式.AOP的思想理念就是将通用逻辑从业务逻辑中分离 ...
- spring boot使用AOP切面编程
spring boot使用AOP 1.在pom文件中添加依赖: <!--spring boot aop切面--> <dependency> <groupId>org ...
- Spring Boot 使用 Aop 实现日志全局拦截
前面的章节我们学习到 Spring Boot Log 日志使用教程 和 Spring Boot 异常处理与全局异常处理,本章我们结合 Aop 面向切面编程来实现全局拦截异常并记录日志. 在 Sprin ...
- spring boot+自定义 AOP 实现全局校验
最近公司重构项目,重构为最热的微服务框架 spring boot, 重构的时候遇到几个可以统一处理的问题,也是项目中经常遇到,列如:统一校验参数,统一捕获异常... 仅凭代码 去控制参数的校验,有时候 ...
- Spring Boot 使用 AOP 实现页面自适应
鉴于复杂页面自适应的难度,一般会做几套模板分别适应手机.平板.电脑等设备.使用 Spring Boot 开发单体应用时,一般会使用 Thymeleaf 模板,那么可以使用 AOP 技术来实现页面自适应 ...
- Spring Boot使用AOP在控制台打印请求、响应信息
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等. AOP简介 AOP全称Aspect Oriented Programming,面向切面,AOP主要实现的 ...
- Spring Boot系列——AOP配自定义注解的最佳实践
AOP(Aspect Oriented Programming),即面向切面编程,是Spring框架的大杀器之一. 首先,我声明下,我不是来系统介绍什么是AOP,更不是照本宣科讲解什么是连接点.切面. ...
随机推荐
- 拉格朗日乘子法(Lagrange Multiplier) 和KKT条件
参考文献:https://www.cnblogs.com/sddai/p/5728195.html 在求解最优化问题中,拉格朗日乘子法(Lagrange Multiplier)和KKT(Karush ...
- 微软开源的Web测试和自动化神器 Playwright
Playwright 是微软开源的一个用于 Web 测试和自动化的框架, 提供了可靠的端到端测试, 功能非常强大, 可以在测试, 爬虫,自动化场景中使用. 跨浏览器 Playwright 支持所有现代 ...
- 04 - Vue3 UI Framework - 文档页
官网的首页做完了,接下来开始做官网的文档页 返回阅读列表点击 这里 路由设计 先想想我们需要文档页通向哪些地方,这里直接给出我的设计: 所属 子标题 跳转路径 文件名(*.vue) 指南 介绍 /do ...
- 『学了就忘』Linux系统管理 — 84、Linux中进程的管理
目录 1.Linux系统中的信号 2.杀掉进程的命令 (1)kill命令 (2)killall命令 (3)pkill命令 1.Linux系统中的信号 Linux系统中可以识别的信号较多,我们可以使用命 ...
- 联盛德 HLK-W806 (九): 软件SPI和硬件SPI驱动ST7789V液晶LCD
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- CF250A Paper Work 题解
Content 有 \(n\) 个数,要分成若干堆,要求每堆中的负数最多只能有两个.试求出分成的堆数最少是多少,并求出每一堆里面的数的个数. 数据范围:\(1\leqslant n\leqslant ...
- LuoguB2034 计算 2 的幂 题解
Content 给定整数 \(n\),求 \(2^n\). 数据范围:\(0\leqslant n<31\). Solution 第一种各位都能想得到的,直接循环 \(n\) 次,往答案里面乘以 ...
- word里搜狗输入法出不来,可以按ctrl+空格键
word里搜狗输入法出不来,可以按ctrl+空格键
- Python pyecharts绘制折线图
一.pyecharts绘制折线图line.add()方法简介 line.add()方法简介 add(name,x_axis,y_axis,is_symbol_show=True, is_smooth= ...
- App调试的几个命令实践
1.logcat命令这个命令最简单常用,可查看帮助 第一种事例:adb logcat > /sdcard/mylogcat.txt 第二种事例:adb logcat > D:/Temp/1 ...