Add a filter to the spring boot executable jar package (no code intrusion)

Scenes

When we develop web projects based on the spring boot framework, in most cases, the project is packaged as an executable jar package and started using java -jar project.jar (or it is not too troublesome, through java -cp/- classpath), this article also specifically refers to the java -jar startup method.

If you want to add a web filter to the project, you can directly implement the javax.servlet.Filter class for development in the project. There is nothing to say.

However, I encountered a scenario where a filter needs to be added to a packaged executable jar package to perform permission processing. It is not allowed to develop directly on the source code and then repackage it.

The actual scenario is this: we used the open source nacos as the registry (version 1.4.0 at the time). But the permissions of this version of nacos are actually very simple and do not fit our scenario. We need to make some additional extensions. But the leader does not want me to modify the source code directly for packaging, because after the community releases a new version, if we want to upgrade simultaneously, we need to re-migrate the code and package again.

Therefore, I want to adopt a non-intrusive method. Of course, the first idea may be to adopt a code insertion scheme, and specify an agent jar package (-javaagent:agent.jar) when starting. But ah, if you encounter a situation like this, I really don't recommend this method. The reasons will be explained later.

In fact, I originally wanted to use bytebuddy to develop a proxy jar by modifying the bytecode. When I was ready to do it, I thought of another solution: using spring boot's SPI extension.

solution

The main idea is to implement the following class, and then let spring boot automatically configure and load related classes. For this class, you can directly check the relevant doc or search on the Internet. There should be a lot of articles about instructions or usage.

org.springframework.context.ApplicationContextInitializer

step

1. Implement the above class and register a filter example as follows, where DemoFilter is the business Filter class I want to add:

@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: classpath:/resources/META-INF adds a spring.factories, the content is as follows:

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

3. The project is marked as a jar package

Regarding the above steps, there is not much to say, they are all fixed paradigms. Moreover, it is not the focus of this article. If you are exposed to relevant knowledge for the first time, you can search for information on the Internet to learn more.

Where is this filter jar package?

Location of the filter jar package

The spring boot project directly enters all the dependencies into its lib directory and becomes an executable jar package.

If you search on the Internet, "java -jar start to specify external dependency packages", there should be many answers, in fact, there are only a few, but I don't recommend using them, for example, the general way to specify the extension path:

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

You can see that there is a point above, which is to specify the current directory, so just put the jar package of this extended filter in the current directory. But these solutions are not possible, no matter where you specify the jar package, or use other startup parameter configuration methods. The reason is very simple. You should all know the java class loading mechanism: the parent delegation model. The problem lies here.

The first thing to be clear is that some students may know it, while others may not. The structure of the jar package produced by the spring boot project is a bit different from our regular jar. All its dependencies are placed in a lib directory. Spring boot loads all dependencies and through a custom class loader. For related classes, if you don't understand, you can look at the source code for yourself.

Parent-delegated loading means that when the current class loader loads a class, first let its parent class loader load it. The parent-child inheritance relationship of the loader: start class loader-"extended class loader-"system (application) class loader- 》Custom class loader. This piece of knowledge is a lot, and I am interested in checking relevant information.

The problem now is that if the newly added class specifies the extension class path, it is loaded by the extension class loader. Normally, this is no problem. The problem is that the filter we wrote is not a demo in actual business. It also has a lot of code logic. It also needs to rely on many third-party libraries. Not only the built-in library of jdk is enough. . That's the problem. When I start, when loading the filter class, I need to load its related third-party classes. Where are the related third-party classes? In the lib directory of the spring boot jar package, it is The custom class loader is loaded. So it can't see it. When it can't find it, it will look up. The top is the startup class loader, and the startup class loader can't be found either.

What should I do? I may think of putting all the dependencies of the entire project under the extended classpath. Anyway, when the spring boot application starts, the parents will be assigned to find it first, and it will also be found under the extended classpath first. No need to look in the lib directory of spring boot, and the classes that this new filter relies on have also been found. Everyone has found it, so there is no problem.

This solution seems to be very good. I did it at the beginning. I unzipped the spring boot jar package made by nacos, and then put all the lib packages in it into the specified extension class path. The result is expected, but the operation is still not expected, because the operation does not look normal, maybe we know it is okay, but when you let students who don’t know the relevant knowledge operate, you will be very suspicious whether the operation will affect too much What's too big, wait, and it's hard to explain. And when these rely on my upload server, there are more than 80M, so I can't play like this.

Later, I didn’t think it was easy to deal with, so I just wanted to put this package directly in the lib directory. The specific operation is to use the jar command to decompress and unzip the spring boot executable jar package and put it in the lib directory. Archive again, if you don't have to bother under windows, open win rar and paste it directly, and then upload the jar package to the server.

The command is as follows:

Unzip:

jar -xf nacos-server.jar

Then, put the filter jar package and other jar packages in the BOOT-INF/lib directory that was decompressed. 

Archive, but do not compress the imported jar package (this version of the spring boot custom class loader does not support compression), it may be slightly larger than the original:

jar -cfM0 nacos-server.jar ./

Of course, the actual operation will be more complicated than this. I have provided two scripts, one for adding filters and one for restoring. So actually in the server environment, just execute the script directly (the above are just jar related commands, and the script is only provided according to my environment, so it will not be expanded). The recovery script is to back up the original package when the filter is added. When the recovery script is executed, the backed-up original jar is restored, and other data is cleaned up. It does not decompress the jar again, then take out the filter, and then compress it. This kind of.

Why is it not recommended to use a proxy

The proxy-based approach has a little technical threshold. Really, although there are already good bytecode modification frameworks, you don't need to understand bytecode deeply, but you need to understand these frameworks.

Later, when I had time, I also used byte buddy to develop this plugin for adding filters, and then I still encountered the problem of class loading during testing. Taking time to look up relevant information did not meet my expectations (that is, directly execute java -javaagent:demo.jar -jar procjet.jar, one step is in place). There are relatively few related problems, and the solution is not eager to use. There is no other way but to spend time. Go to see the source code of skywalking, because it also uses bytebuddy. If you use other bytecodes to modify the framework, let’s say that because different features have different support and writing methods, the code for solving problems may be different.

Guess you like

Origin blog.csdn.net/x763795151/article/details/114239142