首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

深入学习Java虚拟机——虚拟机字节码执行引擎

1. 运行时栈帧结构 1.1 认识栈帧 1. 栈帧:用于支持虚拟机方法调用和方法执行的数据结构,它是由虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回值地址等信息。每一个方法从调用开始到执行完成的过程都对应着一个栈帧的入栈到出栈。在代码编译完成时,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定,并且写入到方法表的Code属性中。对于执行引擎来说,在活动线程中,只有位于虚拟机栈顶的栈帧才是有效的,或者说执行引擎的所有字节码指令都只针对当前栈帧操作,最顶端的栈帧被称为当前栈帧,这个栈帧所对应的方法叫当前方法。栈帧结构的概念模型如下 1.2栈帧中的数据区域之一——局部变量表 1. 是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序源码编译为Class文件时,就在方法表中的Code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。 2. 局部变量表的容量的最小单位:变量槽,即Slot,一个Slot所占内存大小没有明确指定,但每个Slot都应该能够存储一个32位以内的数据类型,比如boolean、byte、short、char、int、float、reference(也有64位的)和returnAddress8种类型。对于reference,虚拟机应当能通过这个引用直接或间接地查找对象在Java堆中数据存放的起始地址索引,还可以通过此引用直接或间接的查找到对象所属的数据类型在方法区中的存储的类型信息。 而对于long和double(还有64位的reference类型的数据)这类64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间,而这种分割存储的方式也导致了在进行读写时也会分割为两次32位读写,但对于局部变量是线程私有的,不会出现数据安全问题,而且虚拟机也不允许任何方式单独的访问64位数据的两个Slot空间中的某一个,而之所以会出现在多线程中处理64位数据出现数据安全问题的原因在我的博客的多线程部分也会有解释。 对于实例方法(非static)的局部变量表,其中的第一个也就是第0位索引的Slot存储的是当前方法的类的实例对象的引用,在方法中可以通过关键字 this 来访问这个隐含参数。然后其余方法参数再按照参数表的顺序进入局部变量表,占用从索引1开始的Slot,参数表分配完毕后,再分配方法体内的其他局部变量。 3. Slot的复用:为了尽可能节省栈空间,局部变量表中的Slot可复用。方法体中定义的变量其作用域不一定会覆盖整个方法体,如果程序计数器(程序计数器,当前栈中执行字节码的行号指示器)的值超过了某个变量的作用域,那么该变量对应的Slot就可以交给其他变量使用。比如说以下代码 public void main(String[] args){ int[] arr=new int[10]; for(int i=0;i<10;i++){ arr[i]=i; } int m=1; System.out.println(arr); } 其中局部变量m就有可能占用变量i的Slot 4. Slot复用对垃圾回收工作的影响:以三段代码的比较为例 public void main(String[] args){ byte[] arr=new byte[1024*1024]; System.gc(); } 这段代码很简单,即向内存填充的1Mb的数据,然后调用gc进行垃圾回收,但是并不会回收arr所占的内存空间,因为gc执行时arr还在作用域内,或者说main方法还没有返回退出,所以虚拟机不能回收arr的内存。(观察GC过程可以添加运行参数“-verbose:gc”) public void main(String[] args){ { int[] arr=new int[1024*1024]; } System.gc(); } 这段代码中,arr的作用域被限制在花括号之中,从代码逻辑上看,执行gc时arr已经不可能被访问,gc应该可以对arr进行回收工作,但是实际上却没有,因为即使字节码执行已经超过了arr的作用域,但是在局部变量表的Slot中并没有进行新的Slot读写操作,也就是说arr这个引用仍然占用着原来的Slot空间,那么arr仍然引用着他的数组对象,所以此时gc判断对于arr引用所指向的数组对象仍然与arr存在关联,也就无法进行gc,而对于下一段代码 public void main(String[] args){ { int[] arr=new int[1024*1024]; } int m=1; System.gc(); } 在添加一行int m=1的代码之后,运行程序,可以发现arr可以被gc回收了。因为 int m=1 这行代码就对arr所占用的Slot空间进行了复用,或者说对arr所占据的Slot空间进行的读写操作,删除了arr引用在Slot空间中的数据,导致arr的数组对象失去了关联的引用,此时gc就可以进行回收了。所以,在日常应用中,如果遇到像arr这种前一部分代码定义了一些占据较大空间且后面不在使用的变量,而后面的代码又会有耗时较长的操作,在这种情况下推荐将arr这种类型的引用设置为null值。 1.3 栈帧中的数据区域之一——操作数栈 1. 一个先入后出的栈结构。操作数栈的最大深度在编译后便已经确定,并写入Code属性的max_stacks数据项中。操作数栈中的每一个元素可以是任意的Java数据类型,包括long,double。但是,对于32位长度的数据类型,占一个栈容量,64位的数据类型占2个。 2. 操作数栈的执行:方法刚开始执行时,操作数栈为空,在方法执行过程中会有各种字节码指令向栈中写入或读取内容,也就是出/入栈操作。做算数运算用操作数栈执行,或者调用其他方法时通过操作数栈来进行参数传递。比如执行整数相加的字节码指令iadd,会将操作数栈存放在最顶端的两个int类型数值进行相加并且将这两个值出栈,然后将相加的结果入栈,将结果赋予某变量时就会将该结果值出栈。 1.3 栈帧中的数据区域之一——动态连接 1. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用就是为了支持方法调用中的动态连接。字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数,这些符号有一部分会在类加载阶段或者第一次使用时就替换为直接引用,这种转化称为静态解析;另一部分将在运行期间转化为直接引用,这部分就叫动态连接。 1.4 栈帧中的数据区域之一——方法返回地址 1. 方法返回的方式:第一种是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的调用者,这种退出方式叫正常完成出口;另一种方式是方法执行过程中出现异常,且方法体内没有任何对这个异常的处理,就会导致方法退出,这种退出方式叫异常完成出口,异常完成不会给上层调用者任何返回值。 2. 方法返回地址:如论何种方式退出方法,都要返回到被调用的位置,程序才能继续执行,所以栈帧中会保存一些数据来恢复上层方法的执行状态,这一部分数据就是方法返回地址。一般来说,调用者的程序计数器的值可以作为返回地址,方法返回地址可能就会保存这个值,而方法异常退出时,栈帧一般不会保存这个信息。 3. 当前方法退出时可能执行的操作步骤有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有)压入调用者栈帧的操作数栈中,调整调用者程序计数器的值指向方法调用的下一条指令 2. 方法调用 方法调用不是方法执行,而是确定执行的是哪一个方法,或者说是哪一个版本的方法。 2.1 解析 所有方法在Class文件中都是常量池中的一个符号引用,在类加载过程的解析阶段中,会将其中一部分符号引用替换为直接引用,而实现这一步的前提是编译时就能确定所执行的方法版本(执行的是哪一个方法),并且这个方法的调用版本在运行期不可更改,这类方法的调用就叫解析。 满足这两种条件(编译器可知,运行期不可变)的方法主要是静态方法和私有方法两类,也就是说不可能通过继承或其他方式被重写的方法,都适合在类加载阶段解析。 1. 虚拟机中5中方法调用指令: (1)invokestatic:调用静态方法 (2)invokespecial:调用实例构造器方法、私有方法、父类方法 (3)invokevirtual:调用虚方法。 (4)invokeinterface:调用接口方法。 (5)invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。 invokestatic和invokespecial指令调用的方法都可以在解析阶段确定唯一的调用版本,比如静态方法、私有方法、实例构造器、父类方法4类,他们在类加载时就会将符号引用替换为直接引用,这些方法被称为非虚方法。其他方法(final方法除外)为虚方法。 fianl方法也是非虚方法的一种,虽然final方法由invokevirtual指令调用,但其符合非虚方法的特点,即无法覆盖,没有其他版本,多态选择的结果肯定是唯一的,所以final方法是非虚方法。 解析调用一定是一个静态的过程,在编译期就完全确定,类加载过程中将涉及的符号引用全部替换为确定的直接引用。而分派调用可能是静态也可能是动态,还可分为单分派和多分派,这两类分派方式组合就构成了静态单分派、静态多分派、动态单分派、动态多分派。 2.2 分派 Java具有面向对象的3个基本特征:继承、封装和多态,对于方法的重载与重写,分派是虚拟机正确定位目标方法的关键。 1. 静态分派——重载 public class StaticDispatch { static abstract class Human{ } static class Man extends Human{} static class Woman extends Human{} public void sayHello(Human guy){ System.out.println("hello guy"); } public void sayHello(Man man){ System.out.println("hello man"); } public void sayHello(Woman woman){ System.out.println("hello woman"); } public static void main(String[] args) { StaticDispatch s=new StaticDispatch(); Human m1=new Man(); Human m2=new Woman(); s.sayHello(m1); s.sayHello(m2); } } //输出结果 hello guy hello guy 在上面这段代码中,“Human”称为变量的静态类型,或者叫做外观类型,后面的“Man”则称为变量的实际类型,静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可以确定,编译程序在编译期并不知道一个对象的具体类型是什么。对于重载方法的调用,完全取决于参数数量和数据类型。编译期在重载时是通过参数的静态类型而不是实际类型作为判断依据的,并且静态类型是编译期可知的,因此,在编译阶段编译器就会根据静态类型决定用哪个重载版本,所以选择了sayHello(Human)作为调用目标,并把这个方法的符号引用写到main方法里的两条invokevirtual指令的参数中。 所有依赖静态类型(引用的类型)来定位具体执行方法版本的分派动作称为静态分派,静态分派的典型应用就是重载。静态分派发生在编译阶段,因此确定静态分派的动作是由编译器执行。另外,编译器能确定方法重载的版本,但重载版本有时并不是唯一的,往往只能选择一个更加合适的版本。比如sayHello(int)、sayHello(long)、sayHello(char),如果方法调用为sayHello(‘a’),那么首先会调用sayHello(char),如果没有sayHello(char)方法,就会调用sayHello(int),然后才是sayHello(long)。 2. 动态分派——重写 public class DynamicDispatch { static abstract class Human{ public abstract void sayHello(); } static class Man extends Human{ public void sayHello(){ System.out.println("man"); } } static class Woman extends Human{ public void sayHello(){ System.out.println("woman"); } } public static void main(String[] args) { Human man=new Man(); Human woman=new Woman(); man.sayHello(); woman.sayHello(); } } //执行结果 man woman (1)在这里自然不可能根据静态类型来决定方法的调用,而是通过对象的实际类型来找到相应的方法。 man和woman这两个对象是将要执行的sayHello方法的所有者,也成为接收者,而编译后的字节码文件中两行sayHello方法的调用指令invokevirtual执行的方法通过索引值(索引值指向常量池中的符号引用,该符号引用对应方法 Human.sayHello())来看是同一个方法,但最终执行的目标方法却不同。这就是因为invokevirtual指令在运行时解析方法的符号引用的过程大概如下 找到操作数栈顶的第一个元素所指向的对象的实际类型(因为调用方法首先会把引用从局部变量表压入操作数栈顶,然后通过引用找到对象),记为类型C。 如果在类型C中找到与索引值对应的常量池中的常量中描述符和简单名称都相符的方法,则进行权限校验,如果通过则返回这个方法的符号引用所对应的直接引用,查找过程结束;如果权限校验不通过,则抛出java.lang.IllegalAccessError异常。 否则按照继承关系从子类向上对C的父类进行第2步的查找和验证过程。 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。 invokevirtual指令的执行就是方法重写的本质,在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。 3. 单分派与多分派 方法的接收者和方法的参数统称为方法的宗量,单分派就是根据一个宗量对目标方法进行选择,多分派就是根据多个宗量来对目标方法进行选择。Java中,静态分派(比如重载)通过接收者的静态类型以及方法参数进行选择目标方法,所以Java的静态分派是多分派类型。而动态分派(重写)只依据接收者的实际类型来选择目标方法,也就是一个宗量,所以动态分派也是单分派类型。所以,Java语言是一门静态多分派,动态单分派的语言。 4. 动态分派的优化实现 动态分配的方法选择过程中需要运行时在类的方法元数据中搜索合适的目标方法,而且动态分派动作很频繁,所以为了优化虚拟机性能,会为类在虚拟机的方法区中建立一个虚方法表(专门存储虚方法索引的,调用该方法时会执行invokevirtual字节码指令的方法,而对应的,在invokeinterface执行时也会有接口方法表),使用虚方法表索引来代替元数据查找。 虚方法表中存放着各个方法的实际入口地址,如果某个方法在子类中没有被重写,那么子类的虚方法表里面的该方法地址入口和父类中的虚方法表里面的该方法是一样的,都指向父类的实现入口;如果过子类重写了该方法,那么子类方法表中的地址将会替换为指向子类实现版本的入口地址。 方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。 除了上面分派调用的优化手段之外,还有内联缓存和守护内联两种方法来获取更高性能。

优秀的个人博客,低调大师

docker学习系列13 实现 基于pxc 的mysql 多节点主主同步

背景 MySQL本身是开源的,有些公司或社区基于MySQL发布了新的分支,如有名的MariaDB。 在介绍 Percona 之前,首要要介绍的是XtraDB存储引擎,在MYSQL中接触比较多的是MyISAM 和 InnoDB这两个存储引擎。 MySQL 4 和 5 使用默认的 MyISAM 存储引擎安装每个表。从5.5开始,MySQL已将默认存储引擎从 MyISAM 更改为 InnoDB。MyISAM 没有提供事务支持,而 InnoDB 提供了事务支持。与 MyISAM 相比,InnoDB 提供了许多细微的性能改进,并且在处理潜在的数据丢失时提供了更高的可靠性和安全性。 Percona Server由领先的MySQL咨询公司Percona发布。Percona Server是一款独立的数据库产品,其可以完全与MySQL兼容,可以在不更改代码的情况了下将存储引擎更换成XtraDB 。 Percona XtraDB Cluster 完全兼容MySQL。 常见MySQL集群方案 image.png Percona XtraDB Cluster优缺点 优点: 1.当执行一个查询时,在本地节点上执行。因为所有数据都在本地,无需远程访问。 2.无需集中管理。可以在任何时间点失去任何节点,但是集群将照常工作。 3.良好的读负载扩展,任意节点都可以查询。 缺点: 1.加入新节点,开销大。需要复制完整的数据。 2.不能有效的解决写缩放问题,所有的写操作都将发生在所有节点上。 3.有多少个节点就有多少重复的数据。 基于Docker的实现流程 拉镜像 docker pull percona/percona-xtradb-cluster:5.7 镜像名字有点长,起个短点的docker tag percona/percona-xtradb-cluster:5.7 pxc 出于安全考虑,针对PXC集群实例创建内部网络 创建的时候通过参数指定IP段和子网掩码,Docker默认使用的IP 172.17.0.1,我们换个别的。docker network create --subnet=172.18.0.0/24 pxc-network 创建第一个节点docker run -d -p 33010:3306 -e MYSQL_ROOT_PASSWORD=root -e CLUSTER_NAME=pxc_cluster --name=pxc_node1 --net=pxc-network --172.18.0.2 pxc 执行 docker logs pxc_node1 查看执行状态,如果看到 mysqld: ready for connections. 就可以使用navicat等工具测试连接。 创建第二个数据库节点,并加入到第一个集群中,注意多了 CLUSTER_JOIN 参数docker run -d -p 33011:3306 -e MYSQL_ROOT_PASSWORD=root -e CLUSTER_NAME=pxc_cluster -e CLUSTER_JOIN=pxc_node1 --name=pxc_node2 --net=pxc-network --172.18.0.3 pxc 创建第三个数据库节点,并加入到第一个集群中,注意多了 CLUSTER_JOIN 参数docker run -d -p 33012:3306 -e MYSQL_ROOT_PASSWORD=root -e CLUSTER_NAME=pxc_cluster -e CLUSTER_JOIN=pxc_node1 --name=pxc_node3 --net=pxc-network --172.18.0.4 pxc 接下来可以创建第N个节点,注意参数如容器名称 --name 和映射的端口别冲突; 测试:本地连接这三个节点,在其中一个创建demo数据,其他节点都自动同步数据过去了 image.png 注意 启动第一个节点后记得使用docker logs查看启动状态,然后使用navicat等工具测试连接,等第一个mysql运行成功后再运行第二个容器。否则第二个起不来,需要重新启动容器。 如果停掉某一节点 docker stop pxc_node1 再启动时 docker start pxc_node1 可能会发现连接不上了。这时候可以删除容器,重新运行,命令类似 docker run -d -p 33010:3306 -e MYSQL_ROOT_PASSWORD=root -e CLUSTER_NAME=pxc_cluster -e CLUSTER_JOIN=pxc_node2 --name=pxc_node1 --net=pxc-network --172.18.0.2 pxc 参考 https://www.percona.com/doc/percona-xtradb-cluster/LATEST/install/docker.htmlhttps://www.percona.com/doc/percona-xtradb-cluster/5.7/index.html

优秀的个人博客,低调大师

(9)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- JWT算法

一、 JWT 简介 内部 Restful 接口可以“我家大门常打开”,但是如果要给 app 等使用的接口,则需要做权限校验,不能谁都随便调用。 Restful 接口不是 web 网站,App 中很难直接处理 SessionId,而且 Cookie 有跨域访问的限制,所以一般不能直接用后端 Web 框架内置的 Session 机制。但是可以用类似 Session 的机制,用户登录之后返回一个类似 SessionId 的东西,服务器端把 SessionId 和用户的信息对应关系保存到 Redis 等地方,客户端把 SessionId 保存起来,以后每次请求的时候都带着这个SessionId。 用类似 Session 这种机制的坏处:需要集中的 Session 机制服务器;不可以在 nginx、CDN 等静态文件处理服务器上校验权限;每次都要根据 SessionId 去 Redis 服务器获取用户信息,效率低; JWT(Json Web Token)是现在流行的一种对 Restful 接口进行验证的机制的基础。 JWT 的特点:把用户信息放到一个 JWT 字符串中,用户信息部分是明文的,再加上一部分签名区域,签名部分是服务器对于“明文部分+秘钥”加密的,这个加密信息只有服务器端才能解析。用户端只是存储、转发这个 JWT 字符串。如果客户端篡改了明文部分,那么服务器端解密时候会报错。 JWT 由三块组成,可以把用户名、用户 Id 等保存到 Payload 部分 注意 Payload和 Header部分都是 Base64编码,可以轻松的 Base64解码回来。因此 Payload 部分约等于是明文的,因此不能在 Payload 中保存不能让别人看到的机密信息。虽然说 Payload 部分约等于是明文的,但是不用担心 Payload 被篡改,因为 Signature 部分是根据 header+payload+secretKey 进行加密算出来的,如果 Payload 被篡改,就可以根据 Signature 解密时候校验。 用 JWT 做权限验证的好处:无状态,更有利于分布式系统,不需要集中的 Session 机制服务器;可以在 nginx、CDN 等静态文件处理服务器上校验权限;获取用户信息直接从 JWT 中就可以读取,效率高; 二、.Net 中使用 JWT 算法 1) 加密 Nuget -> Install-Package JWT var payload = new Dictionary<string, object> { { "UserId", 123 }, { "UserName", "admin" } }; var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//不要泄露 IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); var token = encoder.Encode(payload, secret); Console.WriteLine(token); 2) 解密 var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qj w1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U"; var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); var json = decoder.Decode(token, secret, verify: true); Console.WriteLine(json); } catch (FormatException) { Console.WriteLine("Token format invalid"); } catch (TokenExpiredException) { Console.WriteLine("Token has expired"); } catch (SignatureVerificationException) { Console.WriteLine("Token has invalid signature"); } 试着篡改一下 Payload 部分。 3) 过期时间 在 payload 中增加一个名字为 exp 的值,值为过期时间和 1970/1/1 00:00:00 相差的秒数 double exp = (DateTime.UtcNow.AddSeconds(10) - new DateTime(1970, 1, 1)).TotalSeconds; 4) 不用秘钥解析数据payload 因为 payload 部分是明文的,所以在不知道秘钥的时候也可以用 Decode、DecodeToObject 等不需要秘钥的方法把payload部分解析出来。 var token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1 epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U"; try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); var json = decoder.Decode(token); Console.WriteLine(json); } catch (FormatException) { Console.WriteLine("Token format invalid"); } catch (TokenExpiredException) { Console.WriteLine("Token has expired"); } 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(6)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- AOP框架

AOP 框架基础 要求懂的知识:AOP、Filter、反射(Attribute)。 如果直接使用 Polly,那么就会造成业务代码中混杂大量的业务无关代码。我们使用 AOP (如果不了解 AOP,请自行参考网上资料)的方式封装一个简单的框架,模仿 Spring cloud 中的 Hystrix。 需要先引入一个支持.Net Core 的 AOP,我们用.Net Core 下的 AOP 框架是AspectCore(国产,动态织入),其他要不就是不支持.Net Core,要不就是不支持对异步方法进行拦截 MVC Filter。 GitHub:https://github.com/dotnetcore/AspectCore-Framework Install-Package AspectCore.Core -Version 0.5.0 这里只介绍和我们相关的用法: 1、编写拦截器CustomInterceptorAttribute 一般继承自AbstractInterceptorAttribute public class CustomInterceptorAttribute:AbstractInterceptorAttribute { //每个被拦截的方法中执行 public async override Task Invoke(AspectContext context, AspectDelegate next) { try { Console.WriteLine("执行之前"); await next(context);//执行被拦截的方法 } catch (Exception) { Console.WriteLine("被拦截的方法出现异常"); throw; } finally { Console.WriteLine("执行之后"); } } } 2、编写需要被代理拦截的类 在要被拦截的方法上标注CustomInterceptorAttribute 。类需要是public类,方法如果需要拦截就是虚方法,支持异步方法,因为动态代理是动态生成被代理的类的动态子类实现的。 public class Person { [CustomInterceptor] public virtual void Say(string msg) { Console.WriteLine("service calling..."+msg); } } 3、通过AspectCore创建代理对象 ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder(); using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build()) { Person p = proxyGenerator.CreateClassProxy<Person>(); p.Say("rupeng.com"); } Console.ReadKey(); 注意p指向的对象是AspectCore生成的Person的动态子类的对象,直接new Person是无法被拦截的。 研究AOP细节 拦截器中Invoke方法下的参数AspectContext的属性的含义: Implementation 实际动态创建的Person子类的对象。 ImplementationMethod就是Person子类的Say方法 Parameters 方法的参数值。 Proxy==Implementation:当前场景下 ProxyMethod==ImplementationMethod:当前场景下 ReturnValue返回值 ServiceMethod是Person的Say方法 注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册