Java EE-Spring AOP 面向切面编程

Spring AOP

https://www.cnblogs.com/joy99/p/10941543.html

超级详细版:Chapter 6. 使用Spring进行面向切面编程(AOP)


AOP 原理

面向切面

( Aspect Orient Programming ) 面向切面编程,是面向对象编程(OOP) 的一种补充。

在 Java 程序自上而下处理主业务时,也会经常处理一些和主业务逻辑无关的问题(比如在接收用户访问请求时,计算程序响应该请求的运行时间)。这些代码如果和主逻辑代码混淆,会导致后期难以维护。

AOP 就是将这些横切性问题和主逻辑解耦。保证开发者不修改主逻辑代码的前提下,能为系统中的业务组件添加删除、或复用某种功能。

代理模式

AOP 的本质是修改业务组件实际执行方法的源代码。即代理类 A 封装了目标类 B ,外部调用 B 的目标方法时会被代理类 A 拦截,代理类 A 一方面执行切面逻辑,一方面把调用转发给目标类 B ,执行目标方法。

该过程是代理模式的实现,代理方式有以下两种:

  • 静态 AOP :在编译阶段对程序源代码进行修改,生成静态的 AOP 代理类(字节码文件已被修改)。性能更好。

  • 动态 AOP :在运行阶段动态生成代理对象。灵活性更好。

动态代理

Spring 中的 AOP 是通过动态代理实现的,有以下两种方式:

  • JDK 动态代理

利用反射机制生成一个实现代理接口的类,在调用具体方法前调用 InvokeHandler 来处理。

JDK 代理只能对实现接口的类生成代理。代理生成的是一个接口对象,因此代理类必须实现了接口,否则会抛出异常。

  • CGlib 动态代理

直接操作字节码对代理对象类的字节码文件加载并修改,生成子类来处理。

CGlib 代理针对类实现代理,对指定的类生成一个子类并覆盖其中的方法,因此不能代理 final 类。


AOP 注解详解

配置

对负责扫描组件的配置文件类(@Configuration) 添加 @EnableAspectJAutoProxy 注解,启用 AOP 功能。

默认通过 JDK 动态代理方式进行织入。但必须代理一个实现接口的类,否则会抛出异常。

注解改为 @EnableAspectJAutoProxy(proxyTargetClass = true)

通过 cglib 的动态代理方式进行织入。但如果拓展类的方法被 final 修饰,则织入无效。

@Configuration
@ComponentScan(basePackageClasses = {com.company.project.service.Meal.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}Copy to clipboardErrorCopied

切面

对组件类(@component) 添加 @Aspect 注解,表示该类为切面类。

增强类型

前置通知

切面方法注解 @Before 表示目标方法调用前,执行该切面方法。

@Before("execution(* com.company.project.service.Meal.eat(..))")
public void cook() {System.out.println("cook");
}Copy to clipboardErrorCopied

后置通知

  • 切面方法注解 @After 表示目标方法返回或抛出异常后,执行该切面方法。
  • 切面方法注解 @AfterReturning 只在目标方法返回后,执行该切面方法。
  • 切面方法注解 @AfterThrowing 只在目标方法抛出异常后,执行该切面方法。
@AfterReturning("execution(* com.company.project.service.Meal.eat(..))")
public void clean() {System.out.println("clean");
}Copy to clipboardErrorCopied

环绕通知

切面方法注解 @Around 表示切面方法执行过程中,执行目标方法。

传入参数为 ProceedingJoinPoint 类对象,表示目标方法。在切面方法中调用其 proceed 方法来执行。

@Around("execution(* com.company.project.service.Meal.eat(..))")
public void party(ProceedingJoinPoint pj) {try {System.out.println("cook");pj.proceed();System.out.println("clean");} catch (Throwable throwable) {throwable.printStackTrace();}
}Copy to clipboardErrorCopied
切点声明

在切面方法中需要声明切面方法要切入的目标方法,execution 指示器是我们定义切点时最主要使用的指示器。

格式为: execution(返回数据类型 路径.类.方法(传入参数类型))

参数功能
execution(* com.company.project.service.Meal.eat(..))执行 Meal 类的 eat 方法时切入
execution(* com.company.project.service.Meal.eat(int,String))执行 Meal 类的 eat(int,String) 方法时切入
execution(* com.company.project.service.Meal.*(..))执行 Meal 类的所有方法时切入
execution(* com.company.project.service.*.*(..))执行 service 包内的任意方法时切入(不包含子包)
execution(* com.company.project.service..*.*(..))执行 service 包内的任意方法时切入(包含子包)
execution(public * *(..))执行所有目标类的所有 public 方法时切入
execution(* pre*(...))执行所有目标类所有以 pre 为前缀的方法时切入

还有一些其他指示器:

参数功能
within(com.company.project.service.*)执行 service 包内的任意方法时切入
this(com.company.project.service.AccountService)执行实现 AccountService 接口的代理对象的任意方法时切入
target(com.company.project.service.AccountService)执行实现 AccountService 接口的目标对象的任意方法时切入
args(java.io.Serializable)任何一个只接受一个参数,并且运行时所传入的参数是 Serializable 接口的方法
  • 多个匹配条件之间使用链接符连接: &&||! 。
  • within 指示器表示可以选择的包,bean 指示器可以在切点中选择 bean 。

如参数 execution(String com.company.project.service.test1.IBuy.buy(double)) && args(price) && bean(girl)

要求返回类型为 String ;参数类型为 double ;参数名为 price ;调用目标方法的 bean 名称为 girl 。

简化代码

对于类中要频繁要切入的目标方法,我们可以使用 @Pointcut 注解声明切点表达式,简化代码。

@Aspect
@Component
public class EatPlus {@Pointcut("execution(* com.company.project.service.Meal.eat(..))")public void point(){}@Before("point()")public void cook() {System.out.println("cook");}@Around("point()")public void party(ProceedingJoinPoint pj) {try {System.out.println("cook");pj.proceed();System.out.println("clean");} catch (Throwable throwable) {throwable.printStackTrace();}}@Pointcut("execution(String com.company.project.service.Meal.eat(double)) && args(price) && bean(people)")public void point2(double price) {}@Around("point2(price)")public String pay(ProceedingJoinPoint pj, double price){try {pj.proceed();if (price > 100) {System.out.println("can not afford");return "没有购买";}} catch (Throwable throwable) {throwable.printStackTrace();}return "购买";}
}Copy to clipboardErrorCopied

常用 AOP

异常处理

  • @ControllerAdvice / @RestControllerAdvice: 标注当前类为所有 Controller 类服务

  • @ExceptionHandler: 标注当前方法处理异常(默认处理 RuntimeException) @ExceptionHandler(value = Exception.class): 处理所有异常

@RestControllerAdvice
public class ControllerExceptionHandler {@ExceptionHandler(Throwable.class)public ResultBean handleOtherException(Throwable e) {String message = String.format("错误=%s,位置=%s", e.toString(), e.getStackTrace()[0].toString());return ResultBean.error(ErrorCode.UNKNOWN_ERROR.getErrorCode(), message);}@ExceptionHandler(StreamPlatformException.class)public ResultBean handleVenusException(StreamPlatformException e) {return ResultBean.error(e.getErrorCode(), e.getMessageToUser());}@ExceptionHandler(FormValidationException.class)public ResultBean handleFormValidationException(FormValidationException e) {StringBuilder message = new StringBuilder();e.getResult().getAllErrors().forEach(objectError -> {if (objectError instanceof FieldError) {FieldError fieldError = (FieldError) objectError;message.append("参数").append(fieldError.getField()).append("错误值为").append(fieldError.getRejectedValue()).append(fieldError.getDefaultMessage());} else {message.append(objectError.getDefaultMessage());}});return ResultBean.error(ErrorCode.PARAMETER_VALIDATION_ERROR.getErrorCode(),String.format(ErrorCode.PARAMETER_VALIDATION_ERROR.getMessage(), message));}
}Copy to clipboardErrorCopied

拦截器

  • 拦截器(Interceptor)

Java Web 中,在执行 Controller 方法前后对 Controller 请求进行拦截和处理。依赖于 web 框架,在 Spring 配置。在实现上基于 Java 的反射机制。

  • 过滤器(Filter)

Java Web 中,在 request/response 传入 Servlet 前,过滤信息或设置参数。依赖于 servlet 容器,在 web.xml 配置。在实现上基于函数回调。

两者常用于修改字符编码、删除无用参数、登录校验等。Spring 框架中优先使用拦截器:功能接近、使用更加灵活。

拦截器配置

// 在配置中引入拦截器对象(单独编写拦截器类)@Override
public void addInterceptors(InterceptorRegistry registry) {// 导入拦截器对象,默认拦截全部InterceptorRegistration addInterceptor = registry.addInterceptor(new myInterceptor());// 排除配置addInterceptor.excludePathPatterns("/error","/login","/user/login");               addInterceptor.excludePathPatterns("/asserts/**");                       addInterceptor.excludePathPatterns("/webjars/**");addInterceptor.excludePathPatterns("/public/**");// 拦截配置addInterceptor.addPathPatterns("/**");
}Copy to clipboardErrorCopied

拦截器类通过实现 HandlerInterceptor 接口或者继承 HandlerInterceptorAdapter 类。

// 定义拦截器
public class myInterceptor extends HandlerInterceptorAdapter {// Session keypublic final static String SESSION_KEY = "user";// preHandle 预处理@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 检查 sessionHttpSession session = request.getSession();if (session.getAttribute(SESSION_KEY) != null) return true;// 重定向到登录页面request.setAttribute("message","登录失败,请先输入用户名和密码。");request.getRequestDispatcher("login").forward(request,response);return false;}// postHandle 善后处理@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) {System.out.println("INTERCEPTOR POSTHANDLE CALLED");}}Copy to clipboardErrorCopied

过滤器类通过继承 Filter 类实现,直接添加注解即可。

@Component                                                                // 作为组件,交给容器处理
@ServletComponentScan                                                     // 扫描组件
@WebFilter(urlPatterns = "/login/*",filterName = "loginFilter")           // 设定过滤路径和名称
@Order(1)                                                                 // 设定优先级(值小会优先执行)
public class LoginFilter implements Filter{@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 过滤器前执行System.out.println("before");// 执行内部逻辑filterChain.doFilter(servletRequest,servletResponse);// 过滤器后执行System.out.println("after");}@Overridepublic void destroy() {}
}Copy to clipboardErrorCopied

调用顺序

filter

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/842251.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Qt的网上购物系统的设计与实现

企鹅:2583550535 代码和论文都有 第1章 绪论... 1 1.1 项目背景... 1 1.2 国内外研究现状... 1 1.3 项目开发意义... 3 1.4 报告主要内容... 3 第2章 关键技术介绍... 4 2.1 后端开发技术... 4 2.1.1 C. 4 2.1.2 Qt框架... 4 2.1.3 MySQL数据库... 5 2.2 …

德勤:中国、印度等对ChatGPT等生成式AI应用,处领先地位

全球四大会计事务所之一的德勤(Deloitte)在官网发布了一份,名为《Generative AI in Asia Pacific: Young employees lead as employers play catch-up》的深度调查报告。 主要查看中国、澳大利亚、印度、日本、新加坡、韩国、中国台湾等亚太…

Prompt Engineering Guide

本文转载自:Prompt Engineering Guide https://www.promptingguide.ai/zh/introduction/basics 文章目录 提示工程简介1、基本概念1)基础提示词2)提示词格式 2、提示词要素3、设计提示的通用技巧从简单开始指令具体性避免不精确做还是不做&am…

操作系统实验:进程和线程同步和互斥(生产者消费者问题,睡觉的理发师问题)

1.生产者消费者问题(信号量) 参考教材中的生产者消费者算法,创建5个进程,其中两个进程为生产者进程,3个进程为消费者进程。一个生产者进程试图不断地在一个缓冲中写入大写字母,另一个生产者进程试图不断地…

OrangePi Kunpeng Pro开发板初体验——家庭小型服务器

引言 在开源硬件的浪潮中,开发板作为创新的基石,正吸引着全球开发者的目光。它们不仅为技术爱好者提供了实验的平台,更为专业开发者带来了实现复杂项目的可能性。本文将深入剖析OrangePi Kunpeng Pro开发板,从开箱到实际应用&…

vue3父子组件通信,子组件修改父组件传过来的值

一、第一种,通过props方式传值: 父组件: 父组件调用子组件Child1时通过 :msg2 "msg2"把msg2的数据传给子组件,并且通过自定义事件接收子组件的emit通知,用来修改父组件的msg2数据。 源码: &l…

融资融券操作指南(附最低费率开户渠道)

部分朋友在开通融资融券后,对于融资融券的操作不是很熟练,并且存在很多问题。其实,融资融券核心就是一个“借”,你借券商的资金或者股票,看多时借入资金买入,看空时借入券源卖出。你不可能空口白牙的去借&a…

如何画泳道图?

一、绘制泳道图 1、新建一个绘图, 工具箱搜索“泳道图” 2、修改泳道图标题及风格 3、绘制基本的流程图 4、导出Visio格式 选择文件导出,visio格式

安全工程师考试摸拟试题

安全工程师考试摸拟试题安全工程师是指在工程项目中负责安全管理和安全技术服务的专业人员。他们需要具备扎实的理论知识和丰富的实践经验,能够有效预防和控制各类安全风险… 1 安全工程师考试摸拟试题 安全工程师是指在工程项目中负责安全管理和安全技术服务的专业…

实战14:粒子群算法pso优化机器学习集成学习进行数据预测-完整代码数据

视频演示: 效果演示: 主要代码: import numpy as np from sklearn.model_selection import train_test_split from sklearn.ensemble import GradientBoostingRegressor, AdaBoostRegressor from xgboost import XGBRegressor from lightgbm import LGBMRegressor from s…

MySQL|主从复制配置

我使用的是两个云服务器,如果读者使用的是虚拟机和本机,配置会简单很多。 关于云服务器安全组设置、防火墙端口等问题请参考文章: 使用华为云服务器进行项目部署(云服务器、防火墙配置) 条件:master 和 s…

SaaS增长三大策略:从用户获取到留存转化的全链路解析

在SaaS(软件即服务)行业中,增长是企业成功的关键。然而,要实现持续增长并非易事,需要一套从用户获取到留存转化的全链路策略。 首先,用户获取是SaaS增长的第一步。 这要求企业明确目标用户群体&#xff0…

【LeetCode】【9】回文数(1047字)

文章目录 [toc]题目描述样例输入输出与解释样例1样例2样例3 提示进阶Python实现 个人主页:丷从心 系列专栏:LeetCode 刷题指南:LeetCode刷题指南 题目描述 给一个整数x,如果x是一个回文整数,返回true;否…

C# VSTO读取Excel单元格Value、Value2

对单个单元格的值,需要用object 对象去接 object value (object)oneCellRange.Value; object value2 (object)oneCellRange.Value2; 对矩形范围的值,需要用object[,]去接 object[,] matrixValues (object[,])matrixRange.Value; object[,] matrixV…

【NumPy】关于numpy.sort()函数,看这一篇文章就够了

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

MongoDB数据库清理策略: 自动化过期数据删除实战

1、引言 随着应用程序和业务数据的持续增长,有效地管理数据库存储空间成为维护系统性能的关键。在MongoDB这类NoSQL数据库中,定期清理过期数据变得尤为重要,这不仅能释放宝贵的存储资源,还能优化查询性能,确保数据库运…

Qt 对话框或者QMainWindow等类中调用自定义QWidget继承组件

简单的方法如下所示 1、创建一个ui文件,界面布局放入QVBoxLayout或者QHBoxLayout 使用他来放入自定义组件,类似如下 2、代码如下: ui.setupUi(this); { //自定义组价如下 KwTable *Table new KwTable(this); ui.vertical…

为什么要有NOC

如果是AHB,注意AXI并不真的定义了总线,更多是接口。 那么如何实现上面的带宽,延迟需求呢。 在异构,多时钟;大芯片,物理面积很大,延迟太长;片上系统,IP众多,总…

包装设计的七个秘诀

大家都知道,包装设计的好与坏不等于企业的好,但是消费者会有先入为主的想法,如果一个企业连包装设计都不重视,那么谁来重视产品质量呢?无可否认,对产品的评价首先是质量,但质量之后,…

【OpenVINO™】在C#中使用 OpenVINO™ 部署 YOLOv10 模型实现目标

文章目录 1. 前言1.1 OpenVINO™ C# API1.2 YOLOv10 2. 模型获取2.1 源码下载2.2 配置环境2.3 下载模型 3. Yolov10 项目配置3.1 项目创建与环境配置3.2 定义模型预测方法3.2.1 定义目标检测模型方法3.2.2 使用OpenVINO™ 预处理接口编译模型 3.2 模型预测方法调用 4. 项目运行…