Gateway整合Springdoc,Springfox

简单记录一下,后续再看看有没有更好的方式 Springfox 定义Bean,没有该Bean将会出现,在通过Gateway可以访问swagger-ui,但调用时未携带router-path导致404(定义该Bean但配置中的不同,也可能出现这问题) Springdoc 这个需要在Gateway中进行配置,这种感觉有些麻烦了,不知道有没有别的好的方式,并且gateway也需要引入springdoc依赖 通过 gateway-url/webjars/swagger-ui/index.html访问,可通过definition选项切换

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 […]

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动态代理的过程。 […]

Springboot读取配置文件

指定配置文件 通常情况下我们将配置配置在application开头的主配置文件中,这样随着项目的增大配置项的增多会使文件变得非常臃肿,其实SpringBoot早已考虑到了该问题,SpringBoot提供了@PropertySource和@ImportResource两个注解用于加载外部配置文件使用。 @PropertySource通常用于属性加载配置文件,注意@PropertySource注解不支持加载yaml文件,支持properties文件。另外该注解只对加了的类生效,且application系列是全局属性,不需要单独指出 @ImportResource通常用于加载Spring的xml配置文件 @PropertySource使用 支持从classpath, file (本地), http 来获取配置 装配properties配置文件 在sources/config下创建一个yaml文件命名为user.properties内容与上方user的配置一样 Login类可如下写法 运行一下,同样能达到加载配置效果 当需要根据不同的env读不同的file时,也可以实现 但,这里spring.profiles.active不能配置多个(这个很好理解吧) 同时加载多个配置问题细心的你,会发现@PropertySource注解中属性value为一个数组,如果同时加载多个配置文件,并且不同配置文件中对同一个属性设置了不同的值,那么Spring会识别哪一个呢?带着疑问,我们可以通过控制变量法进行测试,具体过程再在赘述。 结论:Spring加载顺序为从左到右顺序加载,后加载的会覆盖先加载的属性值。 装配yaml配置文件 如果你有强迫症,一定想加载yaml配置文件,那么可以通过PropertySourcesPlaceholderConfigurer类来加载yaml文件,将原来的user.properties改成user.yaml,Bean配置类中加入如下代码,Login配置类和一开始的方式一致。 运行一下,仍然可以能达到加载配置效果的 @ImportResource使用 SpringBoot提出零xml的配置,因此SpringBoot默认情况下时不会识别项目中Spring的xml配置文件。为了能够加载xml的配置文件,SpringBoot提供了@ImportResource注解该注解可以加载Spring的xml配置文件,通常加于启动类上。 also see: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, […]

设置Feign的Header信息

概述 在微服务间使用Feign进行远程调用时需要在 header 中添加信息,那么 springcloud open feign 如何设置 header 呢?有5种方式可以设置请求头信息: 在@RequestMapping注解里添加headers属性 在方法参数前面添加@RequestHeader注解 在方法或者类上添加@Headers的注解 在方法参数前面添加@HeaderMap注解 实现RequestInterceptor接口 示例说明 由于Feign是完全支持Spring MVC注解的, 所以推荐使用前两种Feign设置header的方式, 即: Spring MVC中使用注解设置header. 在@RequestMapping注解里添加headers属性 在application.yml中配置 app.secret: appSecretVal 编写feignClient @PostMapping(value = “/book/api”, headers = {“Content-Type=application/json;charset=UTF-8”,                                             […]

SpringBoot jar包启动的原理

转自 针对这个问题,之前一直不清楚,只知道jar可直接运行,war包要放到web容器中。 近期在做JPMS改造,当下在IDEA中已可以成功运行,但未找到jar部署的方式,以此为契机,正好把这个疑问解决一下。 感觉这篇文章还不错,就转了过来。因为当前站点主题对代码支持很不好,要结合源码来看。 Jar包结构 首先,先准备一个jar包,我这里准备了一个demo-0.0.1-SNAPSHOT.jar;先来看看jar包里面的目录结构: ├── BOOT-INF│   ├── classes│   │   ├── application.properties│   │   └── com│   │       └── sf│   │           └── demo│   │               └── DemoApplication.class│   └── lib│       ├── […]

@Controller、@Service、@Repository等放在接口还是实现类上

这里以@Service为例,其他也一样 @Service注解是标注在实现类上的 因为@Service是把spring容器中的bean进行实例化,也就是等同于new操作,只有实现类是可以进行new实例化的,而接口则不能,所以是加在实现类上的。 IOC的思想:A不直接调用B,而是通过调用生产B的工厂(工厂模式),由B工厂来创建B 有三种普通注入方式,分为构造函数的注入、属性注入、接口注入。 将注入方式交给第三方,通过bean的注解,想调用时直接通过注解注入。 所以通过注解注入bean,就是实例化依赖类的方式, 这也是为什么要将@Service和@Repository放到实现类上面而不是接口类上面, 接口只是一个规范,需要各种实现类去实现这个接口,我们要用的就是这些实用类的方法。 如果采用@Autowired来注解,则同样无需指定name属性,若是实现该接口有多个类,则需要通过@Qualifier来做区分(这里之前疑惑了一下,因为在上一层都是针对接口做的操作。另外有一点就是,因为@Service是标注在实现类上的,所以动态代理为cglib,这个debug一下就看到了) 但Mybatis和MapStruct都有个叫@Mapper的注解,这个是放在接口上的,这个属于其他情况,可忽略。

Spring循环依赖(转)

前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题。 其实笔者本人对这类框架源码题还是持一定的怀疑态度的。 如果笔者作为面试官,可能会问一些诸如“如果注入的属性为null,你会从哪几个方向去排查”这些场景题。 那么既然写了这篇文章,闲话少说,发车看看Spring是如何解决的循环依赖,以及带大家看清循环依赖的本质是什么。 细看的话,会发现两个Map就可以了,一个存最终的,一个存中间的(甚至极端点一个都行),至于为何用三个,看网上有说法称第二级的Map是在2.5.4版本才引入的(也就是之前只有第一和第三层,待确认),原因就是下面讲的 IOC只使用一级缓存有什么问题? 一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。 如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。 所以,就要加一个map,这个map,用来存放那种不完整的bean。 那为什么Sping不选择二级缓存方式,而是要额外加一层缓存? 因为当使用aop时,注入的bean是代理对象,所以需要使用singletonFactories对象; 当产生循环依赖时需要注入earlySingletonObjects中的对象,但不产生循环依赖的场景是不需要的,因为循环依赖只是部分场景,提前生成early对象没有必要(有个getxxx方法,用于生成early对象),所以引入了第三层。这部分讲述了earlySingletonObjects的用途,以及为啥不能不要。 下面这点,讲的是为啥不直接创建代理,这样不要singletonFactories的用途,以及为啥不能不要。 如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。 Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。 如果出现了循环依赖,那没有办法,只有给Bean先创建代理(earlySingletonObjects),但是没有出现循环依赖的情况下,是不需要创建early对象的,这样就造成了浪费,所以设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。(这段有点说的不明白) 正文 通常来说,如果问Spring内部如何解决循环依赖,一定是默认的单例Bean中,属性互相引用的场景。 比如几个Bean之间的互相引用: img 甚至自己“循环”依赖自己: img 先说明前提:原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。 原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A… 这就套娃了, 你猜是先StackOverflow还是OutOfMemory? Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException! 基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。 那么默认单例的属性注入场景,Spring是如何支持循环依赖的? Spring解决循环依赖 首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。 下面代码可以说是很清晰了 笔者翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。 在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map: singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。 singletonFactories 映射创建Bean的原始工厂 earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance. 后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。 所以笔者前文对“三级缓存”这个词有些迷惑,可能是因为注释都是以Cache of开头吧。 为什么成为后两个Map为垫脚石,假设最终放在singletonObjects的Bean是你想要的一杯“凉白开”。 那么Spring准备了两个杯子,即singletonFactories和earlySingletonObjects来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects中。 闲话不说,都浓缩在图里。 上面的是一张GIF,如果你没看到可能还没加载出来。三秒一帧,不是你电脑卡。 笔者画了17张图简化表述了Spring的主要步骤,GIF上方即是刚才提到的三级缓存,下方展示是主要的几个方法。 当然了,这个地步你肯定要结合Spring源码来看,要不肯定看不懂。 如果你只是想大概了解,或者面试,可以先记住笔者上文提到的“三级缓存”,以及下文即将要说的本质。 循环依赖的本质 上文了解完Spring如何处理循环依赖之后,让我们跳出“阅读源码”的思维,假设让你实现一个有以下特点的功能,你会怎么做? 将指定的一些类实例为单例 类中的字段也都实例为单例 […]

lWoHvYe 无悔,专一