对spring boot可执行jar包增加过滤器(无代码侵入)

场景

我们日常基于spring boot框架开发web项目的时候,大多数情况下将工程打包为一个可执行的jar包,使用java -jar project.jar的方式启动(或者不嫌麻烦了,通过java -cp/-classpath),本文也特指的是java -jar这种启动方式。

如果,想要在项目里增加一个web过滤器,直接在项目里实现javax.servlet.Filter类进行开发即可,这没什么好说的。

但是,我遇到了这样一个场景,需要对一个打包后的可执行jar包增加一个过滤器,进行权限处理,不允许直接在源码上开发,再重新打包。

实际场景是这样:我们使用了开源的nacos作为注册中心(当时还是1.4.0版本)。但是这一版本的nacos的权限其实很简单,也不太符合我们的场景,我们需要进行一些额外的扩展。但是leader不希望我直接修改源码进行打包,因为后续社区发布新版本后,我们如果要同步升级,还需要重新迁移代码,再次打包。

所以想采用一种无侵入的方式,当然,第一想法可能是采用插码的方案,启动的时候指定一个代理jar包的方式(-javaagent:agent.jar)。但是啊,如果大家遇到类似这种场景的话,我真的不建议采用这种方式,后面说明原因。

其实我最开始也是想用bytebuddy通过修改字节码的方式,开发一个代理jar来解决,当我准备动手的时候,想到了另一种方案:采用spring boot的SPI扩展。

解决方案

主要想法就是实现下面这个类,然后让spring boot自动配置加载相关类,关于这个类可以直接查看相关doc或者网上搜一下,说明或关于使用方式的文章应该很多。

org.springframework.context.ApplicationContextInitializer

步骤

1. 实现上面这个类,并注册一个过滤器示例如下,其中的DemoFilter是我要添加的业务Filter类:

@Configuration
public class DemoFilterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final Logger LOGGER = LoggerFactory.getLogger(DemoFilterInitializer.class);

    public void initialize(ConfigurableApplicationContext applicationContext) {
        LOGGER.info("...initialize");
    }

    @Bean
    public FilterRegistrationBean<DemoFilter> registerLoginAuthFilterBean() {
        FilterRegistrationBean<DemoFilter> bean = new FilterRegistrationBean<DemoFilter>();
        bean.setFilter(new DemoFilter());
        bean.setName("DemoFilter");
        bean.addUrlPatterns("/*");
        bean.setOrder(1);
        return bean;
    }
}

2.类路径:classpath:/resources/META-INF增加一个spring.factories,内容如下:

org.springframework.context.ApplicationContextInitializer=com.xuxd.DemoFilterInitializer

3. 该工程打成一个jar包

关于上面的步骤,没什么太多说的,都是固定范式。况且也不是本文的重点,如果第一次接触相关知识,可以在网上搜索资料,详细了解。

那个这个过滤器jar包放在哪里。

过滤器jar包放置位置

spring boot工程是直接将所有依赖都打进到它的lib目录,打成了一个可执行jar包。

如果你在网上搜索,“java -jar启动指定外部依赖包”,应该会有很多答案,其实就那几种,但是我都不建议使用,比如,一般会指定扩展路径的方式:

java -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:. -jar project.jar

你看上面有个点,就是指定当前目录下,所以只要把这个扩展过滤器的jar包放到当前目录下即可。但是这些方案都不可以,不论你把这个jar包指定到哪个位置,或者使用其它启动参数配置的方式。原因,很简单,应该都知道java的类加载机制:双亲委派模型吧,问题点在于这里。

首先要明确的一个事情,可能有的同学清楚,有的同学不了解。spring boot工程打出来的jar包,与我们常规jar的结构是有点不一样的,它的所有依赖都是放在了一个lib目录下,spring boot是通过自定义的一个类加载器加载所有依赖和相关类的,不了解的可以自己看下源码。

双亲委派加载,就是当前类加载器加载类的时候,先让它的父类加载器加载,加载器父子继承关系:启动类加载器-》扩展类加载器-》系统(应用)类加载器-》自定义类加载器。这块知识不少,有兴趣查相关资料。

现在的问题是,新添加的类如果是指定扩展类路径的方式,就是被扩展类加载器加载,正常来说,这没问题。问题就在于,我们写的这个过滤器,在实际业务中,它不是一个demo,它也有很多代码逻辑的,它也需要依赖很多第三方类库,不单单是jdk内置的类库就够用了。那问题就来了, 我启动的时候,加载这个过滤器的类的时候,要加载它相关的第三方类,相关的第三方类在哪,在spring boot的jar包里面的lib目录下,被自定义类加载器加载。所以它看不见,它找不到的时候往上找,往上面是启动类加载器,启动类加载器也找不到。

那要怎么办,可能会想到 ,把整个工程所有的依赖全都放在扩展类路径下,反正spring boot应用启动的时候,双亲委派先让上面地找,也会先在扩展类路径下找到,也不用在spring boot的lib目录下找了,而这个新增过滤器所依赖的类也都找到了,大家都找到了,不就没问题了。

这个方案看起来是极好的,我最初也是这样做的,把nacos打成的spring boot的jar包解压开,然后把里面的lib包全放进指定的扩展类路径下。结果是预期的,但是操作还是不太预期,因为操作看起来不太正常,可能我们知道没问题,但是你让不了解相关知识的同学操作的时候,就会很怀疑,这样操作是不是影响太大了啥的等等,解释也费劲。而且这些依赖我上传服务器的时候有80多M呢,不能这样玩。

后来我看都不太好处理,想着干脆就把这个包直接放到lib目录下得了,具体操作,就是使用jar命令先解压开这个spring boot的可执行jar包,放到lib目录下后,再归档,你要是在windows下也不用太费事,win rar打开直接粘贴进去,再将jar包上传服务器就行。

命令如下:

解压:

jar -xf nacos-server.jar

然后,把过滤器这个jar包和其它jar包一样也放到解压开的BOOT-INF/lib目录下。 

归档,但不压缩打进去的jar包(spring boot的这个版本自定义类加载器,不支持压缩),可能会比原来稍大一点点:

jar -cfM0 nacos-server.jar ./

当然实际操作会比这个复杂点,我提供了2个脚本,一个是增加过滤器,一个是恢复的。这样实际在服务器环境中,直接执行一下脚本就行了(上面只是jar相关命令,脚本也只是根据我的环境提供的,就不展开了)。恢复脚本就是把在增加过滤器的时候将原始包备份,执行恢复脚本的时候,把备份的原始jar恢复过来,其它数据清理掉,并不是把这个jar再解压开然后取出来过滤器,再压缩这种。

为什么不建议采用代理

基于代理的方式有一点点技术门槛,真的,虽然已经有很好的字节码修改框架,不用深入了解字节码,但是还要了解这些框架才行。

后来有时间的时候,我使用byte buddy也开发了这个增加过滤器的插件,然后在测试的时候,还是遇到类加载的问题。花时间查阅相关资料也没达到我的预期(就是直接执行java -javaagent:demo.jar -jar procjet.jar,一步到位),相关问题比较少,解决方案不太想使用,没办法只能花时间去看skywalking的源码了,因为它也是使用的bytebuddy。如果使用其它字节码修改框架的话,另说,因为不同的特性有不同的支持和写法,遇到问题的解决方式的代码可能不一样。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不识君的荒漠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值