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

Spring aop讲解+动态代理思想+事务注解原理

Spring aop讲解+动态代理思想+事务注解原理

aop是什么? aop有什么用? aop具体怎么做? aop原理是什么?(按这个思路思考问题)

aop是什么?

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

(简单理解版:aop是在一段代码的前后加入部分通用的逻辑,不改变原本代码,添加对应功能(比喻:汉堡加一片生菜))

补充:

AOP 切面编程涉及到的一些专业术语:

术语含义
目标(Target)被通知的对象
代理(Proxy)向目标对象应用通知之后创建的代理对象
连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut)被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice)增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect)切入点(Pointcut)+通知(Advice)
Weaving(织入)将通知应用到目标对象,进而生成代理对象的过程动作

(我的理解:切面可以分成切和面,切就是添加通用逻辑代码,也叫通知或增强(Advice),而面就是原本的代码,也叫切入点(Pointcut))

aop有什么用?
aop具体怎么做?

例子:

自定义注解打印日志:

添加依赖:

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

注解类:

package org.wujiangbo.annotation;
•
import java.lang.annotation.*;
•
/*** 自定义注解记录系统操作日志*/
//Target注解决定 MyLog 注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ ElementType.PARAMETER, ElementType.METHOD })
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog
{/*** 模块标题*/String title() default "";/*** 日志内容*/String content() default "";
}

切面类:

package org.wujiangbo.aop;
•
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.wujiangbo.annotation.MyLog;
import org.wujiangbo.domain.OperLog;
import org.wujiangbo.service.IOperLogService;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
•
/*** 切面处理类,记录操作日志到数据库*/
@Aspect
@Component
public class OperLogAspect {
•@Autowiredprivate IOperLogService operLogService;
•//为了记录方法的执行时间ThreadLocal<Long> startTime = new ThreadLocal<>();
•/*** 设置操作日志切入点,这里介绍两种方式:* 1、基于注解切入(也就是打了自定义注解的方法才会切入)*    @Pointcut("@annotation(org.wujiangbo.annotation.MyLog)")* 2、基于包扫描切入*    @Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")*/@Pointcut("@annotation(org.wujiangbo.annotation.MyLog)")//在注解的位置切入代码//@Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")//从controller切入public void operLogPoinCut() {}
•@Before("operLogPoinCut()")public void beforMethod(JoinPoint point){startTime.set(System.currentTimeMillis());}
•/*** 设置操作异常切入点记录异常日志 扫描所有controller包下操作*/@Pointcut("execution(* org.wujiangbo.controller..*.*(..))")public void operExceptionLogPoinCut() {}
•
•/*** 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行** @param joinPoint 切入点* @param result      返回结果*/@AfterReturning(value = "operLogPoinCut()", returning = "result")public void saveOperLog(JoinPoint joinPoint, Object result) {// 获取RequestAttributesRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 从获取RequestAttributes中获取HttpServletRequest的信息HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);try {// 从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 获取切入点所在的方法Method method = signature.getMethod();// 获取操作MyLog myLog = method.getAnnotation(MyLog.class);
•OperLog operlog = new OperLog();if (myLog != null) {operlog.setTitle(myLog.title());//设置模块名称operlog.setContent(myLog.content());//设置日志内容}// 将入参转换成jsonString params = argsArrayToString(joinPoint.getArgs());// 获取请求的类名String className = joinPoint.getTarget().getClass().getName();// 获取请求的方法名String methodName = method.getName();methodName = className + "." + methodName + "()";operlog.setMethod(methodName); //设置请求方法operlog.setRequestMethod(request.getMethod());//设置请求方式operlog.setRequestParam(params); // 请求参数operlog.setResponseResult(JSON.toJSONString(result)); // 返回结果operlog.setOperName("张三"); // 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)operlog.setIp(getIp(request)); // IP地址operlog.setIpLocation("湖北武汉"); // IP归属地(真是环境中可以调用第三方API根据IP地址,查询归属地)operlog.setRequestUrl(request.getRequestURI()); // 请求URIoperlog.setOperTime(new Date()); // 时间operlog.setStatus(0);//操作状态(0正常 1异常)Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间(单位:毫秒)operlog.setTakeTime(takeTime);//插入数据库operLogService.insert(operlog);} catch (Exception e) {e.printStackTrace();}}
•/*** 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行*/@AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {// 获取RequestAttributesRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 从获取RequestAttributes中获取HttpServletRequest的信息HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
•OperLog operlog = new OperLog();try {// 从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 获取切入点所在的方法Method method = signature.getMethod();// 获取请求的类名String className = joinPoint.getTarget().getClass().getName();// 获取请求的方法名String methodName = method.getName();methodName = className + "." + methodName + "()";// 获取操作MyLog myLog = method.getAnnotation(MyLog.class);if (myLog != null) {operlog.setTitle(myLog.title());//设置模块名称operlog.setContent(myLog.content());//设置日志内容}// 将入参转换成jsonString params = argsArrayToString(joinPoint.getArgs());operlog.setMethod(methodName); //设置请求方法operlog.setRequestMethod(request.getMethod());//设置请求方式operlog.setRequestParam(params); // 请求参数operlog.setOperName("张三"); // 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)operlog.setIp(getIp(request)); // IP地址operlog.setIpLocation("湖北武汉"); // IP归属地(真是环境中可以调用第三方API根据IP地址,查询归属地)operlog.setRequestUrl(request.getRequestURI()); // 请求URIoperlog.setOperTime(new Date()); // 时间operlog.setStatus(1);//操作状态(0正常 1异常)operlog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息//插入数据库operLogService.insert(operlog);} catch (Exception e2) {e2.printStackTrace();}}
•/*** 转换异常信息为字符串*/public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {StringBuffer strbuff = new StringBuffer();for (StackTraceElement stet : elements) {strbuff.append(stet + "\n");}String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();message = substring(message,0 ,2000);return message;}
•/*** 参数拼装*/private String argsArrayToString(Object[] paramsArray){String params = "";if (paramsArray != null && paramsArray.length > 0){for (Object o : paramsArray){if (o != null){try{Object jsonObj = JSON.toJSON(o);params += jsonObj.toString() + " ";}catch (Exception e){e.printStackTrace();}}}}return params.trim();}
•//字符串截取public static String substring(String str, int start, int end) {if (str == null) {return null;} else {if (end < 0) {end += str.length();}
•if (start < 0) {start += str.length();}
•if (end > str.length()) {end = str.length();}
•if (start > end) {return "";} else {if (start < 0) {start = 0;}
•if (end < 0) {end = 0;}return str.substring(start, end);}}}
•/*** 转换request 请求参数* @param paramMap request获取的参数数组*/public Map<String, String> converMap(Map<String, String[]> paramMap) {Map<String, String> returnMap = new HashMap<>();for (String key : paramMap.keySet()) {returnMap.put(key, paramMap.get(key)[0]);}return returnMap;}
•//根据HttpServletRequest获取访问者的IP地址public static String getIp(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

aop原理是什么?

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。

aop的aop的底层原理就是Java的实现与继承(实现与继承不需要改变原有代码就能增加方法功能),JDK动态代理基于实现来完成,cglib动态代理基于继承来完成,而动态代理就是spring来实现这些操作,例如给指定范围(动态)实现接口或创建子类。

事务注解原理

spring的声明式注解@Transactional就是基于aop(或动态代理)实现的,在方法执行之前开启事务,方法执行之后提交事务,遇到异常则进行事务回滚。

通过动态代理为标注了@Transactional注解的方法增加切面逻辑,而事务的上下文包括数据库链接都是通过ThreadLocal来传递,在这个切面逻辑里主要做这几个事情:

1、获取方法上标注的注解的元数据,包括传播级别、异常配置等信息 2、通过ThreadLocal获取事务上下文,检查是否已经激活事务 3、如果已经激活事务,则根据传播级别配置,看是否需要新建事务(如果新建事务,会生成一个新的事务上下文对象TransactionInfo,并将上一个事务上下文赋值到新上下文的oldTransactionInfo属性上)代码位置在TransactionAspectSupport类prepareTransactionInfo方法里的bindToThread方法里 4.开启事务,先通过数据库连接池获取链接,关闭链接的autocommit,然后在try catch里反射执行真正的dao操作,通过异常情况来决定是commit还是rollback


http://www.mrgr.cn/news/54448.html

相关文章:

  • 7、Nodes.js包管理工具
  • 《嵌入式最全面试题-Offer直通车》目录
  • Qt调用Yolov11导出的Onnx分类模型开发分类检测软件
  • 【机器学习】VQ-VAE(Vector Quantized Variational Autoencoder)
  • xlnt加载excel报错:xl/workbook.xml:2:2581: error: attribute ‘localSheetId‘ expected
  • Java基础12-特殊文件和日志技术
  • Unity3D VisionPro 环境扫描 空间理解 网格扫描 AR Mesh
  • YOLOv11改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
  • VASCO:增减材混合制造的体积和表面共分解
  • String类的基本用法
  • Vue学习
  • MongoDB安装配置及配置和启动服务
  • <Project-11 Calculator> 计算器 0.2 工时计算器 WorkHours Calculator HTTP + JS
  • aws(学习笔记第七课) 私有子网使用NAT服务器
  • C++数据结构-红黑树全面解读(进阶篇)
  • 【洛谷】P6319
  • 【华为HCIP实战课程十五】OSPF的环路避免及虚链路,网络工程师
  • 图像捕捉---Base On NCC
  • neo4j 中日期时间 时间戳处理
  • 正确使用内部类
  • 是什么决定了我们毕业后的能力增长?
  • 【Python-AI篇】人工智能python基础-计算机组成原理
  • armbian 青龙面板
  • 超详细介绍bash脚本相关细节
  • 运算符优先级有没有通用原则?
  • 点餐小程序实战教程20广告管理