I've written an interceptor to generate service logs for a SpringBoot Java Rest API. I have the code below to define the custom WebMvcConfigurer:
package com.gem.common.interceptors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class InterceptorConfig implements WebMvcConfigurer {
#Autowired
LoggerInterceptor logInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
}
}
I'd like to use this InterceptorConfig across different modules. Can I package it and use it or do I need to define it in each module?
I suppose with "other modules" you are asking if you could make that code available to other spring boot applications too?
If that's the case - then: yes you can package it in a jar and add it as a dependency to all your other modules. I'll post the way to do this just below, however - just to warn you - if it's just for that simple class, the solution is not going to be worth it.
In short, what you'd need to do is to create your own external artifact (this usually is done via maven or gradle. You create a new maven project for your shared code. It will need to depend on some base libraries so that you have your #Configuration annotation available. Put your class as described in that project, and create a file src/main/resources/META-INF/spring.factories file. There you'll need to point to that class.
Then you build that project and upload the resulting jar to a package repository. Once that's done, you can add it as a dependency to your project. At startup, Spring boot will find the spring.factories file and automatically include the classes that are mentioned there in its initialization.
Please also note, that this is just a high level explanation and you will need more details. Spring has good documentation on this use case and they also have a demo project to show this extension mechanism.
Related
I am looking for best way to externalize my validation error messages out of my src code in spring and spring boot application, in order to avoid build/deployment on each time the error messages changes. Is there possibly any such ways to achieve it?
You can maintain all the validation error or success messages in a properties file. If you want to externalize, you can place the properties file outside the spring boot jar file. It is not necessary to put the configuration inside jar. I provide below the code snippet to achieve it.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySources({
#PropertySource("file:config/other-config.properties"),
#PropertySource("file:config/app-config.properties")
})
public class ExternalConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the above code, in the line #PropertySource("file:config/app-config.properties"), config is the name of the folder or directory which contains many properties files like "app-config.properties". For better understanding, I provide below the image, external config file and spring boot jar will look like this finally.
The default resource bundle framework assumes your resource bundles are property files in your resources folder. So they are packaged inside your jar file as part of your build process.
However you can implement your own ResourceBundle loading:
https://docs.oracle.com/javase/tutorial/i18n/resbundle/control.html
You can then opt to use other mechanisms, including using a database instead of property files (with a TTL to cache messages for a specific period of time). This way you don't even have to restart the application when a message changes.
I'd like to get help in setting up a multi-module Maven project using Spring Boot.
Correct me if I'm wrong but I've read that Spring-Boot reads the start main Application (Annotated with #SpringBootApplication and ran with SpringApplication.run) and finds the necessary classes through reflection. Which means that it first accesses the start class and then proceeds to find the controllers, models, repositories. If so, how do I set up the dependency in the pom.xml of each module if I had a project structure like this:
app
--src
--pom.xml
core
--pom.xml
--models
----/pom.xml
--controllers
----/pom.xml
--repositories
----/pom.xml
pom.xml
Please have a look complete guide how to create multi module project in spring boot.
https://spring.io/guides/gs/multi-module/
Spring boot will component scan from the package of the class annotated with #SpringBootApplication. Component scannign means that it is looking through the classes under that package recursively, analyzing annotations, and wiring up anything it recognizes. This can include controllers, simple variables with #Value annotations, members with #Autowired, and a host of other things.
You can actually jump into the source for the #SpringBootApplication annotation and see that it expands to numerous other annotations, #ComponentScan being one of them.
If all of your modules are in a sub-hierarchy package wise from there, then they will be scanned properly anyway. Often though, sub-modules will be in a slightly different package hierarchy. In this case, you can explicitly specify #ComponentScan() in your code and inside the () you can list the base packages to component scan from.
Whether or not its a sub-module doesn't matter much at this point; its just like scanning classes in any other library you're including.
General Advice
Also, just FYI - Multi module projects can get a little hard to manage (speaking from numerous separate experiences). They can be very good if used properly though. If you're a beginner to Maven though, it may be wiser to keep separte, well-defined projects with a proper release cycle and just import them as normal dependencies.
So, I'm not for or against them, but just make sure you understand them well going in :).
I have a GitHub project where I configured a multimodule maven project:
https://github.com/cristianprofile/spring-boot-mvc-complete-example
This is Example project maven module structure:
Spring mvc rest maven module ---> service maven module ---> repository maven module
The main module should be configured like this (Spring mvc rest layer):
#SpringBootConfiguration
#EnableAutoConfiguration
//spring mvc module auto scan only its package
#ComponentScan(basePackageClasses = HelloWorldController.class)
//It needs Service bean so it will import ConfigurationService.class from
// Service maven module
#Import({ConfigurationService.class})
Complete class:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/main/java/com/mylab/cromero/controller/Application.java
It will only scan its package :
HelloWorldController.class --> com.mylab.cromero.controller;
This Rest layer use a service maven module so it is necessary to add dependency:
<dependency>
<groupId>com.mylab.cromero.core</groupId>
<artifactId>mylab-core-service-impl</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
Complete pom file:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/pom.xml#L16
ConfigurationService.class from service maven module autoscan its packages and it will import ConfigurationRepository.class (Repository maven module)
#Configuration
//It needs repository's bean so it will import ConfigurationRepository.class from
// Repository maven module
#Import(ConfigurationRepository.class)
//service layer module auto scan only its package
#ComponentScan(basePackageClasses = ConfigurationService.class)
public class ConfigurationService {
}
Complete Service maven module code:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/mylab-core/mylab-core-service-impl/src/main/java/com/mylab/cromero/service/ConfigurationService.java#L12
Service maven module layer has a dependency with maven repository module:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/mylab-core/mylab-core-service-impl/pom.xml#L38
Repository module will auto configure jpa and domain classed:
#Configuration
#EnableJpaRepositories(basePackages = "com.mylab.cromero.repository")
#EntityScan(basePackageClasses=Base.class)
#ComponentScan(basePackageClasses = BaseRepository.class)
public class ConfigurationRepository {
}
Consider a very simple Spring Boot/Jersey application setup:
Generate a fresh Spring Boot application using Initializr and select the Jersey dependency (in my case, I prefer the Gradle setup).
Add simple Controller and Configuration classes:
JerseyConfig.java
package com.example.unload;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
#Configuration
public class JerseyConfig extends ResourceConfig {
JerseyConfig() { register(TestController.class); }
}
TestController.java
package com.example.unload;
import org.springframework.stereotype.Component;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Component #Path("test")
public class TestController {
#GET public String test() {
return "test";
}
}
Run the war task and deploy the WAR to Tomcat (8.5 in my case).
Whenever I want to undeploy (e.g. for re-deployment) the WAR, I get the following error message
Oct 23, 2017 8:13:11 AM org.apache.catalina.startup.ExpandWar deleteDir
FATAL: […\apache-tomcat-8.5.20\webapps\unload-0.0.1-SNAPSHOT\WEB-INF\lib] could not be completely deleted. The presence of the remaining files may cause problems
The culprit is the jersey-server-2.25.1.jar in the lib directory, to which apparently some classloader must have a reference. I also cannot manually delete the file, because Java holds a lock on it.
Interestingly, after manually executing a GC run (externally through jvisualvm), I am able to delete the jersey-server.jar.
So, I suspect that a stream is not properly closed (but does get closed during finalization).
Of course, manually performing a GC run is not an option in a production environment.
I tried to call System.gc() in the contextDestroyed callback of the ServletContextListener, but this gets called to early when there are still live references.
I am also aware of the antiResourceLocking option in the context.xml file, but this creates copies of the whole WAR content in a temp directory, without ever removing it (which might be the job of a cron job). However, this feels more like a work-around and not a productive solution.
A third possible option is to extract all Spring, Jersey and related dependencies and move it to the Tomcat's shared lib directory, but I prefer to bundle all necessary dependencies to ease deployment for customers.
What other options do I have, and what is the root cause?
I have not found any reference to a bug in neither Tomcat, Spring, nor Jersey.
I could reproduce this issue with Jersey 2.26 and jersey-hk2 2.26.
I have a library as a jar packaging Maven project which offers services.
The #Configuration class to instantiate service beans:
#Configuration
public class JpaService {
#Bean
public UserRoleServiceImpl userRoleService() {
return new UserRoleServiceImpl();
}
#Bean
public UserServiceImpl userService() {
return new UserServiceImpl();
}
}
I reckoned I needed to have the beans instantiation outside of the jar archive.
So I had a copy of this class in the test part of the project, and another copy in another war packaging Maven project using the library.
But what if I instantiated the services in the jar library itself. I would need to only do it once, be it for testing or for all client projects using it.
UPDATE: Two questions...
Should all component scanning only be done from the war ? Or should the jar service components be scanned from the jar ?
And what if two components (one in the jar and one in the war) have the same class name in the same package ?
I dont think i fully understand your question, but if you are aiming to add beans to your application context that is outside the jar then what you have to do is use the #ComponentScan annotation, and specify the package you want to scan, the package can be in a different jar, the only thing required is that you anotate the clases you want to include with #Service, #Componenet or even #Configuration
example:
#Configuration
#ComponentScan(basePackages={"com.somepackacge.controller",
...
you can include as much packages as you like.
By the way dont copy your clases from one place to the other, maintining that will be a headache in the futute, if you want to include your configuration in your tests you can always do :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyConfigClass.class)
Where MyConfigClass is the class u used before with the component scan
Hope it helps
I'm trying to use a spring getting started project, and I have a problem I cannot figure out: when I move the restController from the default "hello" package to another one (say com.mydomain.controllers) I get a 404 error page. Any ideas how to solve this?
PS: I'm using intellij + gradle
In this case, this controller is located in a package that's not scanned by Spring. In the Application class:
package hello;
#SpringBootApplication
public class Application {
//...
}
The #SpringApplication annotation is a convenience annotation - see the full explanation in the guide itself.
If you want to scan other locations and customize more your configuration, you can change your application class to this:
package hello;
#Configuration
#EnableAutoConfiguration
#ComponentScan({"hello", "com.mydomain.controllers"})
public class Application {
//...
}