简体   繁体   中英

Spring Boot Component Scan Context

I have 10+ Spring Boot applications that can be built as war files and deployed within and application server, or run individually as standalone. Each application contains unique code, functionality, and RESTful services required for business operations.

My package structure is as follows:

WebServices (Gradle project)
|---A (Gradle project)
|---B (Gradle project)
|---C (Gradle project)

Services A, B, and C are all packaged and runnable as wars.

However, I also want to provide the option to start up one "large" server that contains all the services without having to start each application separately or bogging down the primary application server with the large Spring Boot wars.

Ideally this was going to be via another Spring Boot application that utilized the ComponentScan to include functionality from all other services. I was going to have Spring Boot application X that referenced functionality within services A , B , and C .

The issue with this is with context. Each of my services, when started via an application server, are automatically assigned a context based on the name of the war file. For example, if I have security functionality in application A since it contains sensitive information I log in with:

/ security /login

Where security is the is the name of the war file (security.war).

To compensate for this while running standalone I have an application property set for the context to match the war file name server.servlet.context-path: /security . This allows me to maintain the same endpoint weather deployed with either of my deployment methods.

Now, when I start server X that references projects A , B , and C with the component scan @ComponentScan(basePackages = {"com.package.a.*", "com.package.b.*", "com.package.c.*"}) I loose my context of security within application A and my endpoint now is accessed as:

/login

There is no differentiation between applications A , B , or C .

So, my question is how can I maintain individual contexts, or even route based on context, based on the components being scanned?

Since spring boot is not really designed for what you're asking, you'll have to be very creative. Here are some directions:

Option 1

As long as you have many wars deployed at the same server, the contextPaths will be different, you can play with them depending on the actual server but they have to you be different so that web server could differentiate between them somehow. With that in mind you can run what you're trying to do but then define a proxy (Zuul, ha-proxy or whatever) that would carefully map every possible url to the appropriate server.

Option 2

Pretty much the same as 1, but you can use docker-compose (or even kubernetes) and run spring boot applications as different containers. This arguably might be somewhat a better solution if you want to run in CI or development (docker compose will build everything for you given the locations of the created war files, no need to move to web server, deploy the server, etc). Just stating this as an option.

Option 3

If you really want them all in the same content path. Create a gradle plugin that would merge the wars / jars into one. Fat-Jar style. Note that spring boot applications might have a different layout (Its for sure in case of Jars), so the implementation should probably do something like this:

Step 1

The plugin assumes that the modules A,B,C are built. Now I don't have experience with Gradle but in Maven you could go to the target directory (I believe in gradle there is a build directory for the same purpose) and extract the content of the war/jar (classes, resources,dependencies) into another folder, say for project A, then do the same for project B, and so forth. The dependencies might clash (different versions, so you'll have to make sure that all the applications use the same versions of dependencies to eliminate the issues), the classes should not clash since they should reside in different packages ( com.package.a as opposed to com.package.b ).

Step 2

After step "a" you'll have to package everything into the artifact of your choice, that should be still a spring boot project. If you'll opt for JAR you'll have to understand how exactly its built. I can't comment on WAR, haven't worked with those in spring boot.

Option 4

Pretty much the same as option 3 but step 1 is done when one of the real projects it built (like project A, B, etc). The plugin would copy the produces classes, dependencies or whatever into some predefined folder. The plugin will be installed in each module so that when it runs it will "contribute" to the folder that would contain all the required resources at the end of the day. Step 2 will be the same as in Option 3.

Update

Based on your comment, consider Jars and option 1 / 2. Jars are the recommended way to work in spring boot applications, wars should be used for old-fashioned organizations that maintain web servers and don't want to do "Ops" changes.

Now, if port is an issue, If you use containers / kubernetes orchestration, there is also an option to assign a virtual host name to each service, so that will all be accessible on the same port but with virtual host name. The option of proxy will allow same port and host for all of them

I resolved this in another manner.

I placed a default RequestMapping("${serviceContext}") on each of my individual controllers. Where service changes for each controller. ie aContext , bContext , and cContext .

In my environments that are already bound by a context, within an application server and standalone via the application.yml, this property is not set and results in the already existing binding.

In my bundle application I was able to keep my component scan of @ComponentScan(basePackages = {"com.package.a.*", "com.package.b.*", "com.package.c.*"}) . The change then became adding additional properties during the startup of the application.

Properties used to bind context

Properties properties = new Properties(); properties.put("aContext", "/security"); properties.put("bContext", "/b"); properties.put("cContext", "/c");

I added this to Spring's setDefaultProperties before running the application. Then when I startup each as a bundle I get the expected context binding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM