面试总结之Spring篇

news2024/11/25 21:34:17

一、AOP

1、什么是AOP

1.1、概述

  • AOP(Aspect-Oriented Programming):面向切面编程,即把一些业务逻辑中的相同代码抽取出来,让业务逻辑更加简练清爽
    在这里插入图片描述
  • 如果要CRUD写一堆业务,可如何实现业务代码前后进行打印日志和参数的校验?
    可以把日志记录和数据校验可重用的功能模块分离出来,然后在程序合适的位置动态地植入这些代码并执行,如此,让业务逻辑只包含核心的业务代码,而没有通用逻辑的代码,使业务模块更简洁,实现了业务逻辑和通用逻辑的代码分离,便于维护和升级,降低了业务逻辑和通用逻辑的耦合性
    在这里插入图片描述
  • AOP可以将遍布应用的功能分离出来形成可重用的组件,在编译期间、装载期间或运行期间实现给原程序动态添加功能(在不修改源代码的情况下),从而实现对业务逻辑的隔离,提高代码的模块化能力
    在这里插入图片描述
  • AOP的核心是动态代理,如果实现了接口,就使用JDK的动态代理,不然就使用CGLIB代理,主要应用于处理具有横切性质的系统级功能,如日志收集、事务管理、安全检查、缓存、对象池管理等

1.2、AOP的核心概念

  • 目标对象(Target):代理的目标对象
    在这里插入图片描述
  • 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象,在Spring中,通过@Aspect注解声明当前类为切面,一般要在切面定义切入点和通知
  • 连接点(JoinPoint):被拦截的点,由于Spring只支持方法类型的连接点,所以在Spring中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切点(PointCut):带有通知的连接点,在程序中主要体现为书写切入点表达式
    在这里插入图片描述
// 以自定义注解 @CustomLog为切点
@Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
public void logPcut() {
}
  • 通知(Advice):拦截到连接点之后要执行的代码,也称作增强
  • 织入(Weave):将切面/ 切面类和目标类动态接入
    编译器织入:切面在目标类编译时织入
    类加载期织入:切面在目标类加载到JVM时织入,需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码,AspectJ采用编译期织入和类加载器织入
    运行期织入:切面在应用运行的某时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,这也是SpringAOP织入切面的方式
  • 增强器(advisor):筛选类中的哪些方法是连接点(哪些方法需要被拦截)
  • 引介(introduction):⼀种特殊的增强,可以动态地为类添加⼀些属性和方法

1.3、AOP的环绕方式

AOP有五种通知的方式:

  • 前置通知 (@Before):在切入点方法执行之前执行
  • 环绕通知 (@Around):手动调用切入点方法并对其进行增强的通知方式
  • 后置通知 (@After):在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行
  • 异常通知 (@AfterThrowing):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行
  • 返回通知 (@AfterReturning):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行

当有多个切面的情况下,可以通过 @Order指定先后顺序,数字越小,优先级越高

2、AOP在项目中的运用

2.1、日志输出

  • 在SpringBoot项目中,使用AOP 打印接口的入参和出参日志,以及执行时间
    1)引入依赖
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  • 2)自定义注解 作为切入点
import java.lang.annotation.*;

@Target({ElementType.METHOD})   // 指定注解使用在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomLog {

    String info();
}
  • 3)配置AOP切面
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect   // 标识当前类为切面
@Component
public class CustomLogAspect {

    // getLogger(Class<?> clazz)
    public static final Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);

    // 以自定义注解 @CustomLog为切点
    @Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
    public void logPcut() {
    }

    // 前置通知: 在切点之前织入
    @Before("logPcut()")
    public void doBefore(JoinPoint joinPoint) throws JsonProcessingException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("========== 开始打印请求参数 ===========");
        logger.info("URL: {}", request.getRequestURL().toString());
        logger.info("HTTP Method: {}", request.getMethod());
        logger.info("Controller的全路径 和 执行方法: {} , {}方法", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        logger.info("请求入参:{}", new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
    }

    // 后置通知,在切入点之后织入
    @After("logPcut()")
    public void doAfter() {
        logger.info("======== 请求日志输出完毕 ========");
    }

    /**
     * 环绕通知: ProceedingJoinPoint对象调用proceed方法,实现 原本目标方法的调用
     *   ProceedingJoinPoint 只支持环绕通知,如果其他通知也采用ProceedingJoinPoint作为连接点,就会出现异常
     *   ==> Caused by: java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
     * */
    @Around("logPcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        logger.info("请求结果: {}", new ObjectMapper().writeValueAsString(result));
        logger.info("请求处理耗时: {} ms", (end - start));
        return result;
    }
}
  • 4)在接口上添加自定义注解
@RestController
@RequestMapping("/aop")
public class CustomAspectController {

    @GetMapping("/hello")
    @CustomLog(info = "hello,使用AOP实现请求日志输出")
    public String hello(String uname) {
        return "Hello,welcome to studing AOP, your name is " + uname;
    }
}

执行结果:
在这里插入图片描述

3、JDK和CGLIB的动态代理

  • 动态代理主要有JDK动态代理和CGLIB的动态代理

1)JDK动态代理

  • Interface:对于JDK动态代理,目标类需要实现一个Interface
  • InvocationHandler:通过实现InvocationHandler接口,定义横切逻辑,再通过反射机制(invoke)调用目标类的方法,在此过程,可能包装逻辑,对目标方法进行前置/ 后置处理
  • Proxy:利用InvocationHandler动态创建一个符合目标类实现接口的实例,生成目标类的代理对象

我们来看⼀个常见的⼩场景,客服中转,解决⽤户问题:
在这里插入图片描述
代码实现:
在这里插入图片描述

  • 接口
public interface ISolver {
    public String solve();
}
  • 目标类:需要实现对应接口
public class ProblemSolver implements ISolver{
    @Override
    public String solve() {
        System.out.println("ProblemSolver,solve方法 ==> 问题正在解决中...");
        return "OKK";
    }
}
  • 动态代理工厂:ProxyFactory,直接用反射生成一个目标对象的代理对象,如下是用匿名内部类的方式重写了InvocationHandler的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {

    // 维护一个目标对象
    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }

    // 为目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("请描述您的问题:");
                        // 通过反射机制 调用目标对象方法
                        Object result = method.invoke(target, args);
                        System.out.println("问题已经得到解决!!");
                        // 返回 目标对象方法的返回值
                        return result;
                    }
                });
    }
}
  • 客户端:Client,生成一个代理对象实例,通过代理对象 调用目标对象的方法
public class Client {
    public static void main(String[] args) {
        ISolver developer = new ProblemSolver();
        // 创建代理对象实例
        ISolver instance = (ISolver) new ProxyFactory(developer).getProxyInstance();
        // 代理对象调用目标对象方法,得到目标方法的返回值并输出
        String res = instance.solve();
        System.out.println(res);
    }
}

执行结果:
在这里插入图片描述

2)CGLIB动态代理

  • 目标类(不需要像JDK动态代理一样实现接口):
public class CglibSolver {

    public String solve(){
        System.out.println("Testing implement proxy by cglib");
        return "CglibSolver ==> solve方法";
    }
}
  • 动态代理工厂:
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor, Callback {

    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxyInstance(){
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数,用于监听当前事件
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("请问有什么可以帮到您?");
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        System.out.println("问题得到解决啦哈!");

        return result;
    }
}
  • 客户端:Client
public class CgClient {
    public static void main(String[] args) {
        CglibSolver solver = new CglibSolver();
        // 创建代理对象
        CglibSolver proxy = (CglibSolver) new ProxyFactory(solver).getProxyInstance();
        // 通过代理对象实例调用目标对象方法
        String result = proxy.solve();
        System.out.println("result : " + result);

    }
}

执行结果(代理对象替目标对象执行调用方法):
在这里插入图片描述

4、Spring AOP和AspectJ AOP的区别

1) Spring AOP

Spring AOP属于运行时增强,主要具有如下特点:

  • 基于动态代理来实现,默认如果使用接口的方式来实现,则使用JDK提供的动态代理;如果是方法,则使用CGLIB来实现
  • Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
  • 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如Aspect好

2)AspectJ

  • AspectJ是功能强大的AOP框架,属于编译时增强,可以单独使用,也可以整合到其他框架中,是AOP编程的完全解决方案

  • AspectJ属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,生成的类没有额外运行时开销,可织入时机如下:
    A、编译期织入(Compile-time weaving):如 A类使用AspectJ添加了某属性,B类引用了A类,该场景就需要编译期进行织入,否则没法编译B类
    B、编译后织入(Post-compile weaving):在已生成了字节码/ class文件,或已经打包成jar包后,该情况需要增强,就需要使用到编译后织入
    C、类加载后织入(Load-time weaving):在加载类时进行织入

  • 两者整体对比如下:
    在这里插入图片描述

事务

1、Spring事务的种类

Spring支持编程式事务声明式事务管理两种方式:
1)编程式事务管理:使用TransactionTemplate,需要显示地执行事务
2)声明式事务管理:建立在AOP之上,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,即在目标方法开始之前启动事务,在执行完目标方法之后根据执行情况 进行提交或回滚事务

  • 优点:不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中进行相关的事务规则声明或通过 @Transactional注解声明事务(以及在启动类上添加@EnableTransactionManagement注解开启事务管理),将事务规则应用到业务逻辑中,减少业务代码的侵入
  • 缺点:最细粒度只能作用到方法级别,无法做到像编程式事务那样作用到代码块级别

2、声明式事务的失效情况

3、声明式事务的实现原理

4、Spring事务的隔离级别

  • Spring的接口TransactionDefinition定义了表示隔离级别的常量,主要是对应后端数据库的事务隔离级别:
  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认可重复读,Oracle默认读已提交
  • ISOLATION_READ_UNCOMMITTED:读未提交
  • ISOLATION_READ_COMMITTED:读已提交
  • ISOLATION_REPEATABLE_READ:可重复读
  • ISOLATION_SERIALIZABLE:串行化

5、Spring事务的传播机制

未完待续…

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

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

相关文章

自动化测试工具之Selenium IDE录制教程

一、下载Selenium IDE 下载传送带&#xff1a;Selenium IDE Open source record and playback test automation for the web 这里Darren洋以firefox火狐浏览器为例&#xff0c;将以上下载url直接在firefox浏览器中打开&#xff0c;点击对应下载按钮后&#xff0c;就会进入添加…

【网络协议】TCP

TCP协议全称为传输控制协议(Transmission Control Protocol).要理解TCP就要从他的特性开始说&#xff0c;这些特性各自之间或多或少各有联结&#xff0c;需要以宏观视角来看待。 目录&#xff1a; 1.TCP报文格式 因为报文解释过于繁琐&#xff0c;具体内容请看这篇文章TCP报文…

架构案例2022(四十二)

促销管理系统 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管理方式的灵活性&#xff0c;由于当前用户规模不大&#xff0c;业务也相对…

PDF文件超出上传大小?三分钟学会PDF压缩

PDF作为一种流行的文档格式&#xff0c;被广泛用于各种场合&#xff0c;然而有时候PDF文件的大小超出了上传限制&#xff0c;这时候我们就需要采取一些措施来减小PDF文件的大小&#xff0c;下面就给大家分享几个方法&#xff0c;一起来学习下吧~ 方法一&#xff1a;嗨格式压缩大…

windows WSL配置cuda,pytorch和jupyter notebook

机器配置 GPU: NVIDIA Quadro K2000 与 NVIDIA 驱动程序捆绑的CUDA版本 但按照维基百科的描述&#xff0c;我的GPU对应的compute capability3.0&#xff0c;允许安装的CUDA最高只支持10.2&#xff0c;如下所示。 为什么本地会显示11.4呢&#xff1f;对此&#xff0c;GPT是这…

R语言分析:如何轻松地把数据分为三、四、五等份?

有网友问了&#xff0c;我如何对连续型资料进行分组&#xff0c;常见的有按照中位数分组、四分位数分组&#xff0c;甚至分为5组。 这个问题其实很简单的了。 用两个函数&#xff0c;一个是quantile函数&#xff0c;另外一个是cut函数 1. quantile()函数的应用 该函数目的是获得…

白盒 SDK 加密 —— Go 语言中直调 C 动态库实现

文章目录 1.背景2.实现方式2.1.C 库 .so 文件生成2.2.C 库 .h 文件2.3.Goland 调用实现2.3.1 整体2.3.2 注释块部分2.3.3 逻辑实现部分 3.小结 1.背景 在重构的历史项目中&#xff0c;有一点是语言转换&#xff1a;从 PHP 转至 Goland &#xff0c;在压缩资源的同时&#xff0…

SpringMVC+统一表现层返回值+异常处理器

一、统一表现层返回值 根据我们不同的处理方法&#xff0c;返回的数据格式都会不同&#xff0c;例如添加只返回true|false&#xff0c;删除同理&#xff0c;而查询却返回数据。 Result类 为此我们封装一个result类来用于表现层的返回。 public class Result {//描述统一格式…

B. Sets and Union

题目&#xff1a; 样例&#xff1a; 输入 4 3 3 1 2 3 2 4 5 2 3 4 4 4 1 2 3 4 3 2 5 6 3 3 5 6 3 4 5 6 5 1 1 3 3 6 10 1 9 2 1 3 3 5 8 9 1 2 4 28输出 4 5 6 0 思路&#xff1a; 这里题目的意思是&#xff0c;要求合并尽可能多的集合&#xff0c;使它的集合大小最大&…

flink中不同序列化器性能对比

背景 flink有多种序列化方式&#xff0c;包括flink内置的以及fallback到kryo的&#xff0c;那么他们之间有多大的性能差距呢&#xff0c;本文就从https://flink.apache.org/2020/04/15/flink-serialization-tuning-vol.-1-choosing-your-serializer-if-you-can/这篇文章里摘录…

分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-CNN多特征分类预测&#xff0c;多特征输入模型&#xf…

【计算机视觉|人脸建模】PanoHead:360度几何感知的3D全头合成

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ 链接&#xff1a;[2303.13071] PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ (arx…

JavaScript 函数柯里化

&#x1f3b6;什么是柯里化 柯里化&#xff08;Currying&#xff09;是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数&#xff0c;并且返回接受余下的参数且返回结果的新函数的技术。 &#x1f3a1;简单的函数柯里化的实现 // ------------- 原函数…

Mac电脑强大的字体管理 RightFont for Mac

RightFont for Mac软件特色 速度有多快&#xff1f;RightFont可以在0.9秒以内加载30,000个字体&#xff01; 自动从Google字体/ Adobe Typekit集合&#xff08;通过Adobe Creative Cloud客户端&#xff09;同步字体。 轻松切换组视图以折叠/展开字体系列。 通过简单的拖放导入…

怎么保护苹果手机移动应用程序ipa中文件安全?

目录 前言 1. 对敏感文件进行文件名称混淆 2. 更改文件的MD5值 3. 增加不可见水印处理 3. 对html&#xff0c;js&#xff0c;css等资源进行压缩 5. 删除可执行文件中的调试信息 前言 ios应用程序存储一些图片&#xff0c;资源&#xff0c;配置信息&#xff0c;甚至敏感数…

raw图片处理软件:DxO PhotoLab 6 mac中文版支持相机格式

DxO PhotoLab 6 mac是一款专业的RAW图片处理软件&#xff0c;适用于Mac操作系统。它具有先进的图像处理技术和直观易用的界面&#xff0c;可帮助用户轻松地将RAW格式的照片转换为高质量的JPEG或TIFF图像。 DxO PhotoLab 6支持多种相机品牌的RAW格式&#xff0c;包括佳能、尼康、…

多叉树+图实现简单业务流程

文章目录 场景整体架构流程业务界面技术细节小结 场景 这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念. 整体架构流程 方案管理、预案管理构成任务流程的基础条…

Redis学习第九天

今天是Jedis&#xff01;作者的Redis在游戏本上&#xff0c;但是Java的IDEA总是下载不了&#xff0c;所以只能作为概念听一听了&#xff0c;目前无法做到实操。 Jedis概念 Jedis实操 首先要保证redis的服务器开启&#xff0c;然后引入jedis依赖&#xff0c;最后通过服务器的I…

【学习笔记】深度学习分布式系统

深度学习分布式系统 前言1. 数据并行&#xff1a;参数服务器2. 流水线并行&#xff1a;GPipe3. 张量并行&#xff1a;Megatron LM4. 切片并行&#xff1a;ZeRO5. 异步分布式&#xff1a;PATHWAYS总结参考链接 前言 最近跟着李沐老师的视频学习了深度学习分布式系统的发展。这里…

作用域 CSS 回来了

几年前&#xff0c;消失的作用域 CSS&#xff0c;如今它回来了&#xff0c;而且比以前的版本要好得多。 更好的是&#xff0c;W3C规范基本稳定&#xff0c;现在Chrome中已经有一个工作原型。我们只需要社区稍微关注一下&#xff0c;引诱其他浏览器构建它们的实现&#xff0c;并…