【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~

AOP - 面向切面编程
- 一、简述AOP
- 二、AOP底层原理
- 三、实现动态代理(案例)
- 四、AOP术语
- 五、AOP的注解方式实现
一、简述AOP
AOP —— 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的作用:
-
简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
-
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
二、AOP底层原理
AOP面向切面编程,在底层使用动态代理实现,其中分为两种情况:
- 有接口时,使用JDK动态代理
- 无接口时,使用CGLIB动态代理
JDK动态代理:创建接口实现类代理对象,增强类的方法;
CGLIB动态代理:创建子类的代理对象,增强类的方法;
三、实现动态代理(案例)
使用的相关Maven依赖:
<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.6.RELEASE</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency></dependencies>
① 声明计算器接口Calculator,包含加减乘除的抽象方法:
/*** @author .29.* @create 2023-02-04 15:01*/
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
② 创建接口实现类,实现加减乘除等方法:
import org.springframework.stereotype.Component;/*** @author .29.* @create 2023-02-04 15:02*///简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}
③ 编写被代理对象的工厂类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;/*** @author .29.* @create 2023-02-04 22:05*/
public class ProxyFactory {//目标对象private Object target;public ProxyFactory(Object target){this.target = target;}//返回代理对象public Object getProxy(){//动态代理返回对象//调用Proxy类中的newProxyInstance(),获取动态代理对象/** newProxyInstance()中需要传入三个参数* ① ClassLoader:加载动态生成代理类的 类加载器* ② Class[] interfaces: 目标对象实现的所有接口* ③ InvocationHandler: 设置代理对象实现目标对象方法的过程* */ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler(){//参数一:代理对象//参数二:需要重写目标对象的方法//参数三:method方法(参数二)中的参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法调用前输出的日志System.out.println("[动态代理][日志]"+method.getName()+",参数:"+ Arrays.toString(args));//调用目标方法Object result = method.invoke(target, args);//方法调用后输出的日志System.out.println("[动态代理][日志]"+method.getName()+",结果:"+ result);return result;}};//调用Proxy类中的newProxyInstance(),获取动态代理对象return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);}
}
④ 测试:
/*** @author .29.* @create 2023-02-04 22:24*/
public class TestCalculator {public static void main(String[] args) {//创建代理对象ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());//通过代理对象获取目标对象Calculator proxy = (Calculator) proxyFactory.getProxy();//调用目标对象方法proxy.add(1,1);System.out.println();proxy.div(1,1);System.out.println();proxy.mul(1,1);System.out.println();proxy.sub(1,1);System.out.println();}
}
四、AOP术语
1.横切关注点:
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
2.通知:
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:使用@Before注解标识,在被代理的目标方法前执行
- 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
- Spring版本5.3.x以前:
- 前置通知
- 目标操作
- 后置通知
- 返回通知或异常通知
- Spring版本5.3.x以后:
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
3.切面:
即:封装通知方法的类。
4.目标:
即:被代理的目标对象。
5.代理:
向目标对象应用通知之后创建的代理对象。
6.连接点:
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
7.切入点:
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
五、AOP的注解方式实现
须知:
切入点表达式语法:
execution(权限修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))

1.导入Maven依赖:
<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.6.RELEASE</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency></dependencies>
2.准备被代理的 接口+实现类:
/*** @author .29.* @create 2023-02-04 15:01*/public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
import org.springframework.stereotype.Component;/*** @author .29.* @create 2023-02-04 15:02*///简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}
※ 3.创建并配置切面类:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @author .29.* @create 2023-02-04 22:47*/
//切面类
//@Aspect代表这是一个切面类
//@Component代表将此类交给Spring处理,能够放入IOC容器中去
@Aspect
@Component
public class LogAspect {//设置切入点 和 通知类型//切入点表达式:execution(权限修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))//重用切入点表达式:使用@Pointcut注解,设置需要重复使用的切入点表达式@Pointcut(value = "execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")public void pointCut(){}//之后,在使用通知时,将切入点表达式用 方法名 替换,即pointCut()(同一个切面)//若在不同的切面,需要加上重用切入点表达式方法的全类名:com.haojin.spring.aop.annoaop.LogAspect.pointCut()(不同的切面)//通知类型:// @Before("切入点表达式配置切入点") 前置@Before(value = "pointCut()") //重用切入点表达式,使用方法名替换public void beforeMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();//获取增强方法的名字Object[] args = joinPoint.getArgs(); //获取增强方法的参数数组System.out.println("Logger-->前置通知,增强方法为:"+name+",参数:"+ Arrays.toString(args));}// @AfterReturning() 返回//返回通知 可以获取到返回值,在切入表达式中增加返回值属性:returning = ""@AfterReturning(value = "pointCut()",returning = "result")//增强方法中需要添加 返回值参数result,参数名与切入点表达式保持一致(result)public void afterReturningMethod(JoinPoint joinPoint,Object result){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->返回通知,增强方法为:"+name+",返回结果:"+result);}// @AfterThrowing() 异常//目标方法出现异常时,此通知会执行,在切入表达式中增加属性:@AfterThrowing(value = "pointCut()",throwing = "exception")//增加 Throwable类型参数,参数名必须与切入点表达式保持一致(exception)public void aAfterThrowingMethod(JoinPoint joinPoint,Throwable exception){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->异常通知,增强方法为:"+name+"异常信息:"+exception);}// @After() 后置@After(value = "execution(* com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")public void afterMethod(JoinPoint joinPoint){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->后置通知,增强方法为:"+name);}// @Around() 环绕通知,可以通过try-catch-finally,使得增强方法在所有阶段执行(增强方法可设置返回值)@Around("execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")//可设置 ProceedingJoinPoint属性参数,以此调用增强方法(JoinPoint的子接口,JoinPoint没有调用目标方法的功能)public Object aroundMethod(ProceedingJoinPoint joinPoint){String name = joinPoint.getSignature().getName();//通过ProceedingJoinPoint参数获取增强方法名Object[] args = joinPoint.getArgs(); //获取增强方法参数数组String argStr = Arrays.toString(args); //参数数组转字符串Object result = null;try{System.out.println("环绕通知 == 增强方法执行前执行");//通过ProceedingJoinPoint类型参数调用增强方法result = joinPoint.proceed();System.out.println("环绕通知 = 增强方法返回值之后执行");}catch(Throwable throwable){//捕捉Throwable类型异常throwable.printStackTrace();System.out.println("环绕通知 = 增强方法异常时执行");}finally {System.out.println("环绕方法 = 增强方法执行完毕执行");}return result;}
切面优先级:
切面的优先级控制切面的 内外嵌套 顺序
外层切面:优先级高
内层切面:优先级低…
可使用@Order()注解设置优先级
@Order(较小的数) 优先级较高
@Order(较大的数) 优先级较低
4.配置Spring配置文件(这里命名为 bean.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--基于注解的AOP的实现:1、将目标对象和切面交给IOC容器管理(注解+扫描)2、开启AspectJ的自动代理,为目标对象自动生成代理3、将切面类通过注解@Aspect标识--><context:component-scan base-package="com.haojin.spring.aop.annoaop"></context:component-scan><!--开启AspectJ的自动代理--><aop:aspectj-autoproxy /></beans>
5.测试:
import com.haojin.spring.aop.annoaop.Calculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author .29.* @create 2023-02-06 9:34*/
public class TestAop {//以实现类中的加法功能为例@Testpublic void testAdd(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");Calculator bean = context.getBean(Calculator.class);bean.add(3,13);}
}

相关文章:
【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ AOP - 面向切面编程一、简述AOP二、AOP底层原理…...
Java8 新特性 之 lambda 表达 和 函数式接口
—— lambda 表达式 概念 lambda 表达式是一个匿名函数,可以把 lambda 表达式理解为是一段可以传递的代码。更简洁、更灵活,使 Java 的语言表达能力得到了提升lambda 表达式是作为接口的实现类的对象(万事万物皆对象) 使用语法…...
Netty服务端和客户端开发实例
一、Netty服务端开发在开始使用 Netty 开发 TimeServer 之前,先回顾一下使用 NIO 进行服务端开发的步骤。(1)创建ServerSocketChannel,配置它为非阻塞模式;(2)绑定监听,配置TCP 参数,例如 backlog 大小;(3)创建一个独立的I/O线程&…...
linux基本指令和权限
目录 一.shell命令以及运行原理 二.Linux常用指令 1. ls 指令 2. pwd命令 3.cd指令 4. touch指令 5.mkdir指令(重要) 6.rmdir指令 && rm 指令(重要) 7.man指令(重要) 8.cp指令(重要&…...
滚蛋吧,正则表达式!
大家好,我是良许。 不知道大家有没有被正则表达式支配过的恐惧?看着一行火星文一样的表达式,虽然每一个字符都认识,但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」&…...
序列号和反序列化--java--Serializable接口--json序列化普通使用
序列化和反序列化序列化和反序列化作用为什么需要用途Serializable使用serialVersionUID不设置的后果什么时候修改Externalizable序列化的顺序json序列化序列化和反序列化 序列化:把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过…...
Java异步任务编排
多线程创建的五种方式: 继承Thread类实现runnable接口。实现Callable接口 FutureTask(可以拿到返回结果,阻塞式等待。)线程池创建。 ExcutorService service Excutors.newFixedThreadPool(10); service.excute(new Runnable01());另外一种创建线程池…...
Hive与HBase的区别及应用场景
当数据量达到一定量级的时候,存储和统计计算查询都会遇到问题,今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能&am…...
C++之单例模式
目录 1. 请设计一个类,只能在堆上创建对象 2. 请设计一个类,只能在栈上创建对象 3.请设计一个类,不能被拷贝 C98 C11 4. 请设计一个类,不能被继承 C98 C11 5. 请设计一个类,只能创建一个对象(单例模式) 设计…...
Redis十大类型——Set与Zset常见操作
Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解,相信大家很容易就明白Set,在Redis中也一样,Set的value值…...
车载雷达实战之Firmware内存优化
内存(Memory)是计算机中最重要的部件之一,计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器(RAM),只读存储器(ROM)以及高速缓存(Cache)。仅仅雷达的原…...
【剑指Offer】JZ14--剪绳子
剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路:因为是求乘积的最大值,所以如果截取剩下的是1,那还是它本身就没有意义。从此出发,考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...
raspberry pi播放音视频
文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器, 播放某段音频, 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…...
【电子学会】2022年12月图形化二级 -- 老鹰捉小鸡
老鹰捉小鸡 小鸡正在农场上玩耍,突然从远处飞来一只老鹰,小鸡要快速回到鸡舍中,躲避老鹰的抓捕。 1. 准备工作 (1)删除默认白色背景,添加背景Farm; (2)删除默认角色小…...
C++的双端队列
双端队列介绍1.双端队列知识需知2.大试牛刀1.双端队列知识需知 由于队列是一种先进先出(FIFO)的数据结构,因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素,可以考虑使用双端队列(deque)。…...
【独家】华为OD机试 - 拼接 URL(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...
为什么使用Junit单元测试?Junit的详解
Hi I’m Shendi 为什么使用Junit单元测试?Junit的详解 Junit简介 Junit是一个Java语言的单元测试框架。 单元测试是一个对单一实体(类或方法)的测试 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression test…...
怎么学好嵌入式Linux系统和驱动
嵌入式专业是一门实践性非常强的学科,只有多动手,多实践,多编程,多调试,多看书,多思考才能真正掌握好嵌入式开发技术。 现在很多同学也意识到了学校培养模式和社会需求脱节问题,有一部分同学也先…...
Spring Aware总结
概述 Spring中Aware到底是什么意思? 我们在看Spring源码的时候,经常可以看到xxxAwarexxx的身影,通常我会很疑惑,Aware到底是什么意思呢? 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…...
【RocketMQ】源码详解:Broker端消息刷盘流程
消息刷盘 同步入口:org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口:org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种,在实例化Commitlog的时候,会根据配置创建不同的服务 p…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

