AspectJ与LoadTimeWeaving动态代理

近期在看Spring Core的Doc,主要是IOC和AOP,其中有些已经了解了,有些是初次遇到,就比如这次的LTW 转自 前提介绍 当我们聊到Spring框架的项目实际开发中,用的强大的功能之一就是(面向切面编程)的这门AOP技术。如果使用得当,它的最大的作用就是侵入性比较少并且简化我们的工作任务(节省大量的重复性编码),最为重要的一点是,它可以让我们在不改变原有代码的情况下,织入我们的逻辑,尤其是在我们没有源代码的时候,而且当我们恢复之前的逻辑的时候,只需要去掉代理就可以了。 AOP的动态代理 Spring AOP的常规的实现方式为cglib和jdk动态代理。两者均可实现,只是性能上略有差异,此处不再详述。 当然,也是有变通的方案解决,比如将bean当做属性注入到自身,然后所有方法调用都通过这个属性来调用。或者通过AopContext.currentProxy的方式去获取代理对象。但是这些解决方案,在开发过程中开发者很容易因为疏忽导致出现问题。 所以,如果需要一种更加强大和易用的aop实现方案,那就是字节码编织技术aspectj。通过修改字节码,可以实现对所有方法进行切面,包括(final、private、static类型的方法),功能强大。并且spring支持aspectj方式的aop。 AOP技术类型 在介绍强大的Aspectj的技术之前,我们先进行对AOP技术实现基础进行分类,通过为目标类织入切面的方式,实现对目标类功能的增强。按切面被织如到目标类中的时间划分 这里我们介绍是Spring整合AspectJ的LTW机制。属于动态加载织入。 LTW可以解决的问题 LTW的原理 类加载期通过字节码编辑技术将切面织入目标类,这种方式叫做LTW(Load Time Weaving)。 使用JDK5 新增的 java.lang.instrument包,在类加载时对字节码进行转换,从而实现 AOP功能。 JDK的代理功能让代理器访问到JVM的底层组件,借此向JVM注册类文件转换器,在类加载时对类文件的字节码进行转换ClassFileTransformer接口。具体方向可以研究一下java agent技术即可。 Spring中实现LTW maven依赖和插件 spring-AOP 和 aspectJ spring的maven配置 springboot的配置 其中都会有maven aspectjWeaver包 spring全注解方式 springboot全注解方式 切面类 对象切点类 aop.xml配置 放到/src/main/resources/META-INF目录下 说明 @EnableAspectJAutoProxy 也会启动运行时代理植入方式。 启动 VM 参数 可以采用Maven方式进行打包进入执行 LTW 官方文档 EOF https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-aj-ltw-spring Tomcat, JBoss/WildFly, IBM WebSphere Application […]

数据库缓存的一致性

来源 | OSCHINA 社区 作者 | 腾讯云开发者社区 原文链接:https://my.oschina.net/qcloudcommunity/blog/5596831 导语 缓存合理使用确提升了系统的吞吐量和稳定性,然而这是有代价的。这个代价便是缓存和数据库的一致性带来了挑战,本文将针对最常见的 cache-aside 策略下如何维护缓存一致性彻底讲透。 但是客观上,我们的业务规模很可能要求着更高的 QPS,有些业务的规模本身就非常大,也有些业务会遇到一些流量高峰,比如电商会遇到大促的情况。 而这时候大部分的流量实际上都是读请求,而且大部分数据也是没有那么多变化的,如热门商品信息、微博的内容等常见数据就是如此。此时,缓存就是我们应对此类场景的利器。 关于一致性的文章网上有很多,这篇整体条理比较清晰,从基本解决方案(四种),到多步操作中某一步失败可能导致的一系列问题及解决。如同结尾讲的那样,每一种都有其使用场景,应结合具体场景进行选择。 缓存的意义 所谓缓存,实际上就是用空间换时间,准确地说是用更高速的空间来换时间,从而整体上提升读的性能。 何为更高速的空间呢? 更快的存储介质。通常情况下,如果说数据库的速度慢,就得用更快的存储组件去替代它,目前最常见的就是 Redis(内存存储)。Redis 单实例的读 QPS 可以高达 10w/s,90% 的场景下只需要正确使用 Redis 就能应对。 就近使用本地内存。就像 CPU 也有高速缓存一样,缓存也可以分为一级缓存、二级缓存。即便 Redis 本身性能已经足够高了,但访问一次 Redis 毕竟也需要一次网络 IO,而使用本地内存无疑有更快的速度。不过单机的内存是十分有限的,所以这种一级缓存只能存储非常少量的数据,通常是最热点的那些 key 对应的数据。这就相当于额外消耗宝贵的服务内存去换取高速的读取性能。 引入缓存后的一致性挑战 用空间换时间,意味着数据同时存在于多个空间。最常见的场景就是数据同时存在于 Redis 与 MySQL 上(为了问题的普适性,后面举例中若没有特别说明,缓存均指 Redis 缓存)。 实际上,最权威最全的数据还是在 MySQL 里的。而万一 Redis 数据没有得到及时的更新(例如数据库更新了没更新到 Redis),就出现了数据不一致。 大部分情况下,只要使用了缓存,就必然会有不一致的情况出现,只是说这个不一致的时间窗口是否能做到足够的小。有些不合理的设计可能会导致数据持续不一致,这是我们需要改善设计去避免的。 这里的一致性实际上对于本地缓存也是同理的,例如数据库更新后没有及时更新本地缓存,也是有一致性问题的,下文统一以 Redis […]

ROM与RAM

转自 一文为你讲解清楚: RAM、SRAM、DRAM、SDRAM、DDR SDRAM ROM、PROM、EPROM、EEPROM、NAND flash、NOR flash ROM和RAM指的都是半导体存储器。ROM是Read OnlyMemory的缩写,RAM是Random Access Memory的缩写。ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存。 RAM RAM 有两大类。一种称为静态RAM(StaticRAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快。 DRAM分为很多种,常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,这里介绍其中的一种DDRRAM。 DDR RAM(Double-Date-Rate RAM)也称作DDR SDRAM,这种改进型的RAM,和SDRAM是基本一样的,不同之处在于它可以在一个时钟读写两次数据,这样就使得数据传输速度加倍了。这是目前电脑中用得最多的内存,而且它有着成本优势。在很多高端的显卡上,也配备了高速DDR RAM来提高带宽,这可以大幅度提高3D加速卡的像素渲染能力。 ROM ROM:只读存储器的总称。 PROM:可编程只读存储器,只能写一次,写错了就得报废,现在用得很少了。 EPROM:可擦除可编程存储器,这东西也比较古老了,是EEPROM的前身,在芯片的上面有个窗口,通过紫外线的照射来擦除数据。非常麻烦。 EEPROM:电可擦除可编程只读存储器,比之EPROM就先进点了,可以用电来擦除里面的数据,也是现在用得比较多的存储器,比如24CXX系列的EEPROM。(现在用的最多,小型存储器) Flsah FLASH 存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM 的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘)。 目前Flash主要有两种 ,NOR Flash (小、贵)和 NADN Flash (大,便宜)。NAND FLASH和NOR FLASH 都是现在用得比较多的非易失性闪存(ROM)。 NOR Flash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。 NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。 一般小容量的用NOR Flash,因为其读取速度快,多用来存储操作系统等重要信息,而大容量的用NAND FLASH,最常见的NAND FLASH应用是嵌入式系统采用的DOC(Disk On […]

Issue with Eureka Discovery

Consumer 端 Server Discovery的问题困扰了几个小时。主体为Register正常,但Discovery无法正常工作,无论是在Gateway还是OpenFeign,最终原因很简单,就是Register的服务没有配置 `spring.application.name`,Eureka不会将其放入Instance List中,从而在Consumer端无法通过serverId获取到对应的Instance 这个印象中,初次学的时候有讲过的,只是过了太久,忘了….

Spring 资源访问通配符和 classpath 和 classpath*区别

配置前缀 classpath: 从类路径中加载资源, classpath*:所有包含指定包名的路径 file: 使用URLResource从文件系统目录中装载资源, 请使用绝对路径 http:// 使用URLResource从web服务器中装载资源 ftp://使用URLResource从ftp服务器装载资源 无前缀 根据ApplicationContext的具体实现类采用对应类型的resource ,一般指定的是 classpath 下的路径 ( web项目中是 ServletContext 下的路径,基本一样 ) 匹配符 ?: 匹配文件名中的一个字符 *: 匹配文件名中的任意个字符 **:匹配多层路径 classpath:app-Beans.xml​说明:无通配符,必须完全匹配​classpath:App?-Beans.xml​说明:匹配一个字符,例如 App1-Beans.xml 、 App2-Beans.xml​classpath:user/*/Base-Beans.xml​说明:匹配零个或多个字符串(只针对名称,不匹配目录分隔符等),例如:user/a/Base-Beans.xml 、 user/b/Base-Beans.xml ,但是不匹配 user/Base-Beans.xml​classpath:user/**/Base-Beans.xml​说明:匹配路径中的零个或多个目录,例如:user/a/ab/abc/Base-Beans.xml,同时也能匹配 user/Base-Beans.xml​classpath:**/*-Beans.xml​说明:表示在所有的类路径中查找和加载文件名以“-Beans.xml”结尾的配置文件,但重复的文件名只加载其中一个,视加载顺序决定​classpath*:user/**/*-Beans.xml​classpath*:**/*-Beans.xml​说明:“classpath*:”表示加载多个资源文件( 依赖的jar包也会寻找 ),即使重名也会被加载 使用位置及示例 在 Spring 中任何需要加载资源的地方都可以使用 示例 : // 1ApplicationContext context = new ClassPathXmlApplicationContext(“classpath:spring-config.xml”);// 2Resource resource = context.getResource(“file:E:/Java_document/JavaWebTest/study-04/src/main/resources/hello.txt”);// 3Resource resource […]

Spring Cloud Client Read Multipart config file from config Server

from: https://stackoverflow.com/questions/43956072/how-to-read-multiple-config-file-from-spring-cloud-config-server 一个Client需要从Config Server读取多个配置的解决方案。 通常是 ,虽然也会有些问题(见第二个answer,但主体这样就能应对大多数场景了。但经过测试,很多common的property,是不需要加profile信息的,这样可能并不需要完全对name和profile做排列组合,但这也会使得所有的env用同一份配置),加载顺序为从前到后,后面的覆盖前面的 How to read multiple config file from Spring Cloud Config Server Spring cloud config server supports reading property files with name ${spring.application.name}.properties. However I have 2 properties files in my application. Can I get the config server to read both these properties files? 4 Answers Rename your properties files […]

RefreshScope的原理

通过Event Trigger相关logic, 来重新创建IOC Continer,从而实现热刷新 主意来自:https://blog.csdn.net/qq_41737716/article/details/106465806 前言 在SpringIOC中,我们熟知的BeanScope有单例(singleton)、原型(prototype), Bean的Scope影响了Bean的管理方式,例如创建Scope=singleton的Bean时,IOC会保存实例在一个Map中,保证这个Bean在一个IOC上下文有且仅有一个实例。SpringCloud新增了一个refresh范围的scope,同样用了一种独特的方式改变了Bean的管理方式,使得其可以通过外部化配置(.properties)的刷新,在应用不需要重启的情况下热加载新的外部化配置的值。 那么这个scope是如何做到热加载的呢?RefreshScope主要做了以下动作: 单独管理Bean生命周期创建Bean的时候如果是RefreshScope就缓存在一个专门管理的ScopeMap中,这样就可以管理Scope是Refresh的Bean的生命周期了重新创建Bean外部化配置刷新之后,会触发一个动作,这个动作将上面的ScopeMap中的Bean清空,这样,这些Bean就会重新被IOC容器创建一次,使用最新的外部化配置的值注入类中,达到热加载新值的效果下面我们深入源码,来验证我们上述的讲法。 管理RefreshBean的生命周期 首先,若想要一个Bean可以自动热加载配置值,这个Bean要被打上@RefreshScope注解,那么就看看这个注解做了什么: 可以发现RefreshScope有一个属性 proxyMode=ScopedProxyMode.TARGET_CLASS,这个是AOP动态代理用,之后会再来提这个 可以看出其是一个复合注解,被标注了 @Scope(“refresh”) ,其将Bean的Scope变为refresh这个类型,在SpringBoot中BootStrap类上打上@SpringBootApplication注解(里面是一个@ComponentScan),就会扫描包中的注解驱动Bean,扫描到打上RefreshScope注解的Bean的时候,就会将其的BeanDefinition的scope变为refresh,这有什么用呢? 创建一个Bean的时候,会去BeanFactory的doGetBean方法创建Bean,不同scope有不同的创建方式: 这里可以看到几件事情: 单例和原型scope的Bean是硬编码单独处理的 除了单例和原型Bean,其他Scope是由Scope对象处理的 具体创建Bean的过程都是由IOC做的,只不过Bean的获取是通过Scope对象 这里scope.get获取的Scope对象为RefreshScope,可以看到,创建Bean还是由IOC来做(createBean方法),但是获取Bean,都由RefreshScope对象的get方法去获取,其get方法在父类GenericScope中实现: 首先这里将Bean包装起来缓存下来 这里的ScopeCache对象其实就是一个HashMap: 这里就是将Bean包装成一个对象,缓存在一个Map中,下次如果再GetBean,还是那个旧的BeanWrapper。回到Scope的get方法,接下来就是调用BeanWrapper的getBean方法: 可以看出来,BeanWrapper中的bean变量即为实际Bean,如果第一次get肯定为空,就会调用BeanFactory的createBean方法创建Bean,创建出来之后就会一直保存下来。 由此可见,RefreshScope管理了Scope=Refresh的Bean的生命周期。 重新创建RefreshBean 当配置中心刷新配置之后,有两种方式可以动态刷新Bean的配置变量值,(SpringCloud-Bus还是Nacos差不多都是这么实现的): 向上下文发布一个RefreshEvent事件 Http访问/refresh这个EndPoint 不管是什么方式,最终都会调用ContextRefresher这个类的refresh方法,那么我们由此为入口来分析一下,热加载配置的原理: 我们一般是使用@Value、@ConfigurationProperties去获取配置变量值,其底层在IOC中则是通过上下文的Environment对象去获取property值,然后依赖注入利用反射Set到Bean对象中去的。 那么如果我们更新Environment里的Property值,然后重新创建一次RefreshBean,再进行一次上述的依赖注入,是不是就能完成配置热加载了呢?@Value的变量值就可以加载为最新的了。 这里说的刷新Environment对象并重新依赖注入则为上述两个方法做的事情: Set keys = refreshEnvironment(); this.scope.refreshAll(); 2.1 刷新Environment对象 下面简单介绍一下如何刷新Environment里的Property值 我们的重点在addConfigFilesToEnvironment方法,刷新Environment: 可以看到,这里归根结底就是SpringBoot启动上下文那种方法,新做了一个Spring上下文,因为Spring启动后会对上下文中的Environment进行初始化,获取最新配置,所以这里利用Spring的启动,达到了获取最新的Environment对象的目的。然后去替换旧的上下文中的Environment对象中的配置值即可。 2.2 重新创建RefreshBean 经过上述刷新Environment对象的动作,此时上下文中的配置值已经是最新的了。思路回到ContextRefresher的refresh方法,接下来会调用Scope对象的refreshAll方法: 还记得上面的管理RefreshBean生命周期那一节关于缓存的讨论吗,cache变量是一个Map保存着RefreshBean实例,这里直接就将Map清空了。 思路回到BeanFactory的doGetBean的流程中,从IOC容器中获取RefreshBean是交给RefreshScope的get方法做的: 可以看到,此时RefreshBean被IOC容器重新创建一个出来了,经过IOC的依赖注入功能,@Value的就是一个新的配置值了。到这里热加载功能实现基本结束。 根据以上分析,我们可以看出只要每次我们都从IOC容器中getBean,那么拿到的RefreshBean一定是带有最新配置值的Bean。 动态刷新的应用 在我们正常使用@RefreshScope的时候,也没有做一些getBean的操作,为什么也可以动态刷新呢?因为Spring利用AOP动态代理了原先的Bean,在调用Bean的方法前,会拦截并从IOC容器中getBean,然后针对返回的新Bean做方法调用,这样就达到了使用的配置值一直是最新的效果了。下面我们来分析分析这AOP动态代理的过程。 […]

API-Gateway 网关系统

来源:developer.aliyun.com/article/889271 本文准备围绕七个点来讲网关,分别是网关的基本概念、网关设计思路、网关设计重点、流量网关、业务网关、常见网关对比,对基础概念熟悉的朋友可以根据目录查看自己感兴趣的部分。 # 什么是网关 网关,很多地方将网关比如成门, 没什么问题, 但是需要区分网关与网桥的区别, 网桥工作在数据链路层,在不同或相同类型的LAN之间存储并转发数据帧,必要时进行链路层上的协议转换。可连接两个或多个网络,在其中传送信息包。 网关是一个大概念,不具体特指一类产品,只要连接两个不同的网络都可以叫网关,网桥一般只转发信息,而网关可能进行包装。 # 网关通俗理解 你看看,网关的作用是不是就是这三个, 最终目的就是减少你与集团的耦合,具体到计算机上就是减少客户端与服务端的耦合,如果没有网关意味着所有请求都会直接调用服务器上的资源,这样耦合太强了,服务器出了问题,客户端会直接报错, 例如老板换工作的地方了,如果没有网关你直接去原来的地方找, 肯定会被告知老板不在这儿。 # 为什么需要网关 当使用单体应用程序架构时,客户端(Web 或移动端)通过向后端应用程序发起一次 REST 调用来获取数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表,并将响应返回给客户端。微服务架构下,单体应用被切割成多个微服务,如果将所有的微服务直接对外暴露,势必会出现安全方面的各种问题,另外内外耦合严重。 客户端可以直接向每个微服务发送请求,其问题主要如下: 客户端需求和每个微服务暴露的细粒度 API 不匹配。 部分服务使用的协议不是Web友好协议。可能使用 Thrift 二进制 RPC,也可能使用 AMQP 消息传递协议。 微服务难以重构。如果合并两个服务,或者将一个服务拆分成两个或更多服务,这类重构就非常困难了。 服务端的各个服务直接暴露给客户端调用势必会引起各种问题。同时,服务端的各个服务可扩展和伸缩性很差。API 网关是微服务架构中的基础组件,位于接入层之下和业务服务层之上,如前所述的这些功能适合在 API 网关实现。 # 网关与服务器集群 回到我们服务器上,下面图介绍了网关(Gateway)作用,可知 Gateway 方式下的架构,可以细到为每一个服务的实例配置一个自己的 Gateway,也可以粗到为一组服务配置一个,甚至可以粗到为整个架构配置一个接入的 Gateway。于是,整个系统架构的复杂度就会变得简单可控起来。网关可以是多层的。 这张图展示了一个多层 Gateway 架构,其中有一个总的 Gateway 接入所有的流量(流量网关),并分发给不同的子系统,还有第二级 Gateway 用于做各个子系统的接入 Gateway(业务网关)。可以看到,网关所管理的服务粒度可粗可细。通过网关,我们可以把分布式架构组织成一个星型架构,由网络对服务的请求进行路由和分发。下面来聊聊好的网关应该具备哪些功能,也就是网关设计模式。 # 网关设计思路 […]

ForkJoin简解

以下文章来源于冰河技术 转自 虽然关于ForkJoin的看了不少,这个还是不错的,比较简单的讲解 stream原理 写在前面 在JDK中,提供了这样一种功能:它能够将复杂的逻辑拆分成一个个简单的逻辑来并行执行,待每个并行执行的逻辑执行完成后,再将各个结果进行汇总,得出最终的结果数据。有点像Hadoop中的MapReduce。 ForkJoin是由JDK1.7之后提供的多线程并发处理框架。ForkJoin框架的基本思想是分而治之。什么是分而治之?分而治之就是将一个复杂的计算,按照设定的阈值分解成多个计算,然后将各个计算结果进行汇总。相应的,ForkJoin将复杂的计算当做一个任务,而分解的多个计算则是当做一个个子任务来并行执行。 Java并发编程的发展 对于Java语言来说,生来就支持多线程并发编程,在并发编程领域也是在不断发展的。Java在其发展过程中对并发编程的支持越来越完善也正好印证了这一点。 Java 1 支持thread,synchronized。 Java 5 引入了 thread pools, blocking queues, concurrent collections,locks, condition queues。 Java 7 加入了fork-join库。 Java 8 加入了 parallel streams。 Java 19 加入了 virtual threads 并发与并行 并发和并行在本质上还是有所区别的。 并发 并发指的是在同一时刻,只有一个线程能够获取到CPU执行任务,而多个线程被快速的轮换执行,这就使得在宏观上具有多个线程同时执行的效果,并发不是真正的同时执行,并发可以使用下图表示。 并行 并行指的是无论何时,多个线程都是在多个CPU核心上同时执行的,是真正的同时执行。 分治法 基本思想 把一个规模大的问题划分为规模较小的子问题,然后分而治之,最后合并子问题的解得到原问题的解。 步骤 ①分割原问题; ②求解子问题; ③合并子问题的解为原问题的解。 我们可以使用如下伪代码来表示这个步骤。 在分治法中,子问题一般是相互独立的,因此,经常通过递归调用算法来求解子问题。 典型应用 二分搜索 大整数乘法 Strassen矩阵乘法 […]

lWoHvYe 无悔,专一