spring体系中控制List注入时的顺序

在spring框架内部以及业务开发中,经常有注入List<T>的场景出现,一般情况下各个T的实现是相互不相干的,这样业务上也不会有问题,但如果实现上有重叠的部分,那么在某些场景中即会出现奇怪的问题。

如下的2个实现

class Impl1 implements Impl{
    public boolean canHandle(int i) {
        return i < 10;
    }
}

class Impl2 implements Impl {
    public boolean canHandle(int i) {
        return i < 100;
    }
}

上述实现中,程序中注入List<Impl>时 理想的顺序是 1 -> 2, 这样在逻辑上更满足业务的需要.但也有可能,实际注入后为 2 -> 1. 在这种情况下,如果 传入的 值,为 0,那么逻辑将导向 Impl2,在业务上就没有按预期进行。

这里不讨论 Impl本身的实现问题,这里仅为参考。在实际代码上,包括spring 相关框架本身,都存在着实现子类的逻辑互相重叠且相互影响的情况,这时候,注入正确的顺序非常重要.

本文简单描述解决问题的2种做法, 再描述一下spring框架自带的bean排序器.

继续阅读“spring体系中控制List注入时的顺序”

使用X-Forwarded-*正确处理浏览器端地址信息

在WEB开发中,有一定的场景中需要拿到基于浏览器视角的路径信息。比如在spring hateoas, spring security,以及基于302跳转的各类登陆,认证体系。在这种场景中,服务端拿到的地址信息可能并不是实际浏览器端访问的路径信息,这时候就要考虑地址转换的事务。特别是当前的业务系统必定前端有Nginx,以及https转http类的反向代理等场景。

本文描述了标准的 X-Forwarded-Host, X-Forwarded-Port 和 X-Forwarded-Proto的使用以及在部分框架中使用的 X-Forwarded-Prefix。同时,在业务框架(如Spring)中已经实际应用的示例代码。在具体场景下应当如何来进行正确地使用。

这些头信息Nginx以及浏览器不会自动地添加到请求头中,需要显示地进行配置。

X-Forwarded-Host
一般配置为 $http_host, 表示获取浏览器在请求时的 HOST 头

X-Forwarded-Port
表示浏览器在访问时的实际端口时,如nginx提供http服务,则为80,如果是https,则为 443。如果为标准服务,此项可以不用设置,仅用于非标准端口时才使用

X-Forwarded-Proto
表示浏览器在访问时的实际协议,如nginx提供https转http反向代理时,这里即要配置为 https,表示浏览器是使用https协议来进行访问,但后端可能用http提供服务。如果两边协议相同,则不用设置

X-Forwarded-Prefix
用于当nginx使用前缀代理后端请求时使用。如 浏览器请求的路径为 serviceName/a/b/c, Nginx在Location端通过serviceName匹配到后端服务时,但把前缀 serviceName去掉,实际访问后端为 /a/b/c时,这时即需要设置此值。

继续阅读“使用X-Forwarded-*正确处理浏览器端地址信息”

在nginx中通过map指令来正确添加请求头或修改头

在使用nginx进行请求转发时,由于各种需要,需要往后端在请求头中添加额外的请求头,或者在前端响应时添加后端没有返回的响应头信息。这些都可以在nginx中需要特定的指令来实现.同时,能够做到与业务无冲突.

本文涉及到的部分包括 添加请求头,添加响应头,以及达到ifAbsent的目的, 做到有则跳过,无则添加的目的.

本文参考的所有Nginx内置变量请参考 http://nginx.org/en/docs/varindex.html 此页.

继续阅读“在nginx中通过map指令来正确添加请求头或修改头”

Jackson中基于上下文拦截属性输出的两种实现方式

需求如下,在一个类中,有一些字段属性,其是否输出并不是由字段上的JsonIgnore来决定,而是根据从上下文(如request)中传递过来的某些参数决定。如下类:

class A {
    @ContextIgnored("field1")
    private String field1;

    @ContextIgnored("field2")
    private String field2;
}

当上下文值为 field1 时,则表示 A 的最终输出json 中没有字段 field1. 当上下文为 field2 时, 则最终输出没有字段 field2. 其它情况则输出所有字段。

本文讨论Jackson中的处理方式,如果使用 Fastjson,则有很多方式处理,这里不表.

本文描述通过利用 JsonIgnore 注解处理方式解决此问题 和 通过 PropertyFilter 两种方式来完成此需求的处理方式。

继续阅读“Jackson中基于上下文拦截属性输出的两种实现方式”

使用 MockFilterChain 来完成从入口层对业务服务的执行调用

之前进行filter层的cache操作时,并不能完成对后端调用的续命操作,即每次的缓存都是被动触发。当存在调用频率很高的请求时,如果能在缓存快过期时主动地触发后端的重新请求,那就能保证前端的请求始终均为命中缓存层,在性能上会有大大地提高。相应的策略如下:

  1. 首次访问时缓存数据,首次调用时间,过期时间
  2. 每次命中缓存时判断是否应该进行缓存续命
  3. 如果续命,则在返回原有缓存数据的同时,使用异步线程模拟正常访问,以强制使用业务的调用以更新缓存

这里面的第3步,并没有直接将旧有缓存进行重新设置ttl,而是再次请求。主要的考虑在于潜在的旧缓存可能不一致的问题,并且因为使用了异步调用,因此对实际上的前端访问是没有任何影响的。

本文的主要思路参考于(原文为nginx+lua,这里同样适用):
https://segmentfault.com/a/1190000003874328

本文的缓存工作点为Filter层,因此对后端的访问也同样起始于此点,可以理解为从此处继续后续的流程。但如果直接调用 FilterChain.doFilter,则会因为之前的请求已经结束了,此调用将直接报 类似 NPE这种错误。即一个 filterChain 不能即返回前端数据,同时又在新线程时继续原来的处理逻辑。

这里使用了spring-test 中的 mockFilterChain来完成相应的处理。整个思路来源于 AutoConfigureMockMvc 中创建起mockMvc的处理过程。
本文只考虑 GET 请求的处理.

继续阅读“使用 MockFilterChain 来完成从入口层对业务服务的执行调用”

spring cache 接口层缓存的演进过程

在spring 体系中,使用spring cache并结合redis来进行数据缓存是很常见的做法。不过,针对于具体的业务场景,可能会有不同的处理方法。

像以下的1个业务场景,即有不同的处理方式。

前端访问后端的指定请求路径(GET类请求), 针对特定的条件下(对应cache condition),希望这个结果能够被缓存.同时,支持当资源修改之后,让此缓存失效掉。

此场景的典型方案就是使用spring cache的 @Cacheable 注解 和 @CacheEvict 注解,并结合实际场景进行混合处理。

在这个实际的场景当中,经历了 service层缓存,Controller层缓存,和Filter层缓存三个阶段,最终达到业务的需求,并且在性能上更接近于实际的需要。
本文就里面碰到的一些实际问题以及解决方式进行了简单描述,从复杂的框架层修改到简单的拦截处理,在思考思路上进行一个分享。

前提
本文中使用的序列化为jackson,即使用jackson将对象序列化为 json 字符串,再getBytes为 字节数组.

继续阅读“spring cache 接口层缓存的演进过程”