Spring boot register bean when application is started - spring

Before a Spring boot application starts, I need to make a request for some credentials. I'm storing them in an object.
Is there a way to register this object as a bean before all other beans so that I can inject them in a configuration class?
I've tried like below but it's throwing exceptions:
SpringBootApplication(exclude = {MongoAutoConfiguration.class,
SecurityAutoConfiguration.class, DataSourceAutoConfiguration.class})
public class BeanOnInitApplication {
public static void main(String[] args) {
System.out.println("Starting the app");
MongoCredentials creds = makeARequestToExternalServiceAndGetCredentials();
GenericApplicationContext appContext = (GenericApplicationContext) SpringApplication.run(BeanOnInitApplication.class, args);
appContext.registerBean("mongoCredentials", MongoCredentials.class, () -> creds, bdc -> bdc.setLazyInit(false));
}
}
And config class:
#Configuration
public class AppConfig {
#Autowired
MongoCredentials mongoCredentials;
#Bean(name = "mongoTemplate")
public MongoTemplate mt() {
String url = "mongodb://" + mongoCredentials.getUsername() + ":" + mongoCredentials.getPassword() + "#localhost:27017/admin";
MongoDatabaseFactory mdf = new SimpleMongoClientDatabaseFactory(url);
return new MongoTemplate(mdf);
}
}
If this is not a solution what are the alternatives? The scope is to register a critical bean before anything else.

Just make it an #Bean.
#Bean
public MongoCredentials mongoCredentials() {
return makeARequestToExternalServiceAndGetCredentials();
}
Don't handle exceptions in the makeARequestToExternalServiceAndGetCredentials and just let them bubble up. If the request then fails the application will simply fail to start. If you want to retry wrap the call with a Spring Retry RetryTemplate and you have everything you want.
Another option is, which would allow for using auto configuration for Mongo as you appear to be using Spring Boot. Is to create an EnvironmentPostProcessor which loads the properties and adds them to the environment.
public class MongoCredentialsPostProcessor implements EnvironmentPostProcessor {
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MongoCredentials credentials = makeARequestToExternalServiceAndGetCredentials();
String url = "mongodb://" + mongoCredentials.getUsername() + ":" + mongoCredentials.getPassword() + "#localhost:27017/admin";
Map<String, Object> props = Map.of("spring.data.mongodb.uri", url);
MapPropertySource mps = new MapPropertySource("mongo-credentials", props):
}
}
Finally you could also modify your current code to do the same as the EnvironmentPostProcessor.
SpringBootApplication(exclude = {
SecurityAutoConfiguration.class, DataSourceAutoConfiguration.class})
public class BeanOnInitApplication {
public static void main(String[] args) {
System.out.println("Starting the app");
MongoCredentials creds = makeARequestToExternalServiceAndGetCredentials();
String url = "mongodb://" + mongoCredentials.getUsername() + ":" + mongoCredentials.getPassword() + "#localhost:27017/admin";
SpringApplicationBuilder sab = new SpringApplicationBuilder(BeanOnInitApplication.class);
sab.properties(Map.of("spring.data.mongodb.uri", url)).run(args);
}
}
With both the latter and the EnvironmentPostProcessor you should be able to use the mongo autoconfiguration (which will provide the MongoTemplate for you.

Related

Is there a possibility to group properties using Spring Boot?

Can something like this be accomplished using Spring Boot?
The idea is to group properties and assign the same value to all of them, so instead of all of the properties ending with 'test*' i would like to change just one property 'my.flag'. I know that such functionality works in case of loggers, but can I define my own group?
I am not sure whether your problem has been solved or not, but I want to provide a solution to achieve what you want by using spring.factories and implementing ApplicationListener as following steps.
STEP 1
Create a class MyPropertiesListener which implements ApplicationListener and read the value of my.flag in application.properties first, then set it to all the properties whose key starts with my.flag..
public class MyPropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
String myFlag = env.getProperty("my.flag");
Properties props = new Properties();
MutablePropertySources propSrcs = env.getPropertySources();
StreamSupport.stream(propSrcs.spliterator(), false)
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> ((MapPropertySource) ps).getPropertyNames())
.flatMap(Arrays::<String>stream)
.forEach(propName -> {
if (propName.toString().startsWith("my.flag.")) {
props.put(propName, myFlag);
}
});
env.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
}
STEP 2
Create a file named spring.factories under src/main/resource/META-INF and configure MyPropertiesListener into it.
org.springframework.context.ApplicationListener=xxx.xxx.xxx.MyPropertiesListener
TEST
The value of my.flag.test3 is false in application.properties originally, but it is going to be overwritten as true while application starts.
#SpringBootApplication
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Value("${my.flag.test3}")
private String test3;
#Override
public void run(String... args) throws Exception {
System.out.println(test3); //true
}
}
See also
Creating a Custom Starter with Spring Boot

Two different hash codes for an object created through Spring Boot in the same context

I have written a simple Spring Boot Application, which I would later extend to build a Spring REST client. I have a working code; I am playing around to understand the concepts better. The code is as follows.
#SpringBootApplication
public class RestClientApplication {
public static void main(String[] args) {
SpringApplication.run(RestClientApplication.class, args);
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
RestClientApplication.class)) {
System.out.println(" Getting RestTemplate : " + ctx.getBean("restTemplate"));
}
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
#Bean
public CommandLineRunner run(RestTemplate template) {
return args -> {
System.out.println("Rest Template instance from CLR is : " + template);
};
}
}
Observation
Rest Template instance from CLR is : org.springframework.web.client.RestTemplate#1e53135d
Getting RestTemplate : org.springframework.web.client.RestTemplate#5aa6202e
Question
I presumed the hashcodes to be the same. Is this the expected behaviour? Is yes, how?
You create two distinct Spring contexts :
// first context
SpringApplication.run(RestClientApplication.class, args);
// second context
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
RestClientApplication.class)) {
System.out.println(" Getting RestTemplate : " + ctx.getBean("restTemplate"));
}
So the result is expected.

Vertx instance variable is null in Spring context

I defined a Spring Boot App as a Verticle as follows:
#SpringBootApplication
public class SpringAppVerticle extends AbstractVerticle {
private Vertx myVertx;
#Override
public void start() {
SpringApplication.run(SpringAppVerticle.class);
System.out.println("SpringAppVerticle started!");
this.myVertx = vertx;
}
#RestController
#RequestMapping(value = "/api/hello")
public class RequestController {
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public void getEcho() {
JsonObject message = new JsonObject()
.put("text", "Hello world!");
myVertx.eventBus().send(EchoServiceVerticle.ADDRESS, message, reply -> {
JsonObject replyBody = (JsonObject) reply.result().body();
System.out.println(replyBody.encodePrettily());
});
}
}
}
I have a second non-Spring Verticle that is basically a echo service:
public class EchoServiceVerticle extends AbstractVerticle {
public static final String ADDRESS = "echo-service";
#Override
public void start() {
System.out.println("EchoServiceVerticle started!");
vertx.eventBus().consumer(EchoServiceVerticle.ADDRESS, message -> {
System.out.println("message received");
JsonObject messageBody = (JsonObject) message.body();
messageBody.put("passedThrough", "echo-service");
message.reply(messageBody);
});
}
}
The problem is that I get a nullpointer at line myVertx.eventbus().send in SpringAppVerticle class as the myVertx variable is null.
How do I properly instantiate a Vertx variable in a Spring context in order that I can exchange message between my both verticles?
My project can be found here: https://github.com/r-winkler/vertx-spring
The reason of the exception is the following:
SpringAppVerticle bean that is created during spring init is another object than starts the spring boot application. So you have two objects, one that has start() method invoked and another one that doesn't. Second one actually handles requests. So what you need is to register verticles as spring beans.
For samples of vertx/spring interoperability please refer to vertx examples repo.
P.S. I've created a pull request to your repo to make your example work.

Multiple servlet mappings in Spring Boot

Is there any way to set via property 'context-path' many mappings for a same Spring Boot MVC application? My goal is to avoid creating many 'Dispatcherservlet' for the uri mapping.
For example:
servlet.context-path =/, /context1, context2
You can create #Bean annotated method which returns ServletRegistrationBean , and add multiple mappings there. This is more preferable way, as Spring Boot encourage Java configuration rather than config files:
#Bean
public ServletRegistrationBean myServletRegistration()
{
String urlMapping1 = "/mySuperApp/service1/*";
String urlMapping2 = "/mySuperApp/service2/*";
ServletRegistrationBean registration = new ServletRegistrationBean(new MyBeautifulServlet(), urlMapping1, urlMapping2);
//registration.set... other properties may be here
return registration;
}
On application startup you'll be able to see in logs:
INFO | localhost | org.springframework.boot.web.servlet.ServletRegistrationBean | Mapping servlet: 'MyBeautifulServlet' to [/mySuperApp/service1/*, /mySuperApp/service2/*]
You only need a single Dispatcherservlet with a root context path set to what you want (could be / or mySuperApp).
By declaring multiple #RequestMaping, you will be able to serve different URI with the same DispatcherServlet.
Here is an example. Setting the DispatcherServlet to /mySuperApp with #RequestMapping("/service1") and #RequestMapping("/service2") would exposed the following endpoints :
/mySuperApp/service1
/mySuperApp/service2
Having multiple context for a single servlet is not part of the Servlet specification. A single servlet cannot serve from multiple context.
What you can do is map multiple values to your requesting mappings.
#RequestMapping({"/context1/service1}", {"/context2/service1}")
I don't see any other way around it.
You can use 'server.contextPath' property placeholder to set context path for the entire spring boot application. (e.g. server.contextPath=/live/path1)
Also, you can set class level context path that will be applied to all the methods e.g.:
#RestController
#RequestMapping(value = "/testResource", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestResource{
#RequestMapping(method = RequestMethod.POST, value="/test", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TestDto> save(#RequestBody TestDto testDto) {
...
With this structure, you can use /live/path1/testResource/test to execute save method.
None of the answers to this sort of question seem to mention that you'd normally solve this problem by configuring a reverse proxy in front of the application (eg nginx/apache httpd) to rewrite the request.
However if you must do it in the application then this method works (with Spring Boot 2.6.2 at least) : https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot.
It describes creating a filter, putting it early in the filter chain and basically re-writing the URL (like a reverse proxy might) so that requests all go to the same place (ie the actual servlet.context-path).
I've found an alternative to using a filter described in https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot that requires less code.
This uses RewriteValve (https://tomcat.apache.org/tomcat-9.0-doc/rewrite.html) to rewrite urls outside of the context path e.g. if the real context path is "context1" then it will map /context2/* to /context1/*
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
factory.addEngineValves(rewrite);
}
}
If you need to use HTTP redirects instead then there is a little bit more required (to avoid a NullPointerException in sendRedirect):
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1 R=permanent", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
if (request.getContext() == null) {
String[] s = request.getRequestURI().split("/");
if (s.length > 1 && LEGACY_PATHS.contains(s[1])) {
request.getMappingData().context = new FailedContext();
}
}
super.invoke(request, response);
}
};
factory.addEngineValves(rewrite);
}
}
I use this approach:
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
#Configuration
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
rootContext.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/mapping1/*");
dispatcher.addMapping("/mapping2/*");
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}

Using Spring Dynamic Languages Support from Java Configuration

I'd like to use Dynamic Languages Support of Spring Framework.
In XML I'd just use the lang namespace, but I'd like to use Java configuration (i.e. #Configuration classes) only.
I can imagine that I can do it by initializing all the hell from org.springframework.scripting.config package, inc. all the BeanPostProcessors, Handlers, Parsers and FactoryBeans they create, but I really don't want to go there.
Is there some other way? If there's none, what will be the minimal configuration needed to create a reloadable bean out of a Groovy script?
Why don't you ask us directly by email? :-)
I see that XML Lang support is relly magic. There is enough stuff which is based on BeanDefinition and its attributes. In additional there are some hooks with ProxyFactory and CGLIB for the lang:property.
What I see for the JavaConfig is some Java class wrapper for the ScriptEvaluator and RefreshableResourceScriptSource from Spring Integration:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class RefreshableScriptJavaConfigTests {
#Autowired
private Calculator calculator;
#Test
public void testGroovyRefreshableCalculator() {
assertEquals(5, this.calculator.add(2, 3));
}
#Configuration
public static class ContextConfiguration {
#Value("classpath:org/springframework/integration/scripting/config/jsr223/Calculator.groovy")
private Resource groovyScriptResource;
#Bean
public ScriptEvaluator groovyScriptEvaluator() {
return new GroovyScriptEvaluator();
}
#Bean
public Calculator calculator() {
return new Calculator(new RefreshableResourceScriptSource(this.groovyScriptResource, 1000));
}
}
public static class Calculator {
private final ScriptSource scriptSource;
#Autowired
private ScriptEvaluator scriptEvaluator;
public Calculator(ScriptSource scriptSource) {
this.scriptSource = scriptSource;
}
public int add(int x, int y) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("x", x);
params.put("y", y);
return (int) this.scriptEvaluator.evaluate(this.scriptSource, params);
}
}
}
Where the Calculator.groovy is:
x + y
I understand that it isn't so flexible as it looks with interfaces and configuration from XML definition, but at least it will help you to see where we are.
Feel free to raise a JIRA issue on the matter and we'll see what we can do here. Something like #EnableScripting and #ScriptSource(refreshDelay = 1000) on the Resource #Bean method.
I think for now you can just #Import some XML snippets with lang definitions.
Cheers,
Artem
I'm going down the same path (work in progress), and have managed to initialise reloadable Groovy scripts by adding the bean definitions when the Spring Application is prepared. In my example I'm using spring-boot.
If you add the following AddBeanDefinitionsListener listener class and a ScriptFactoryPostProcessor bean, you can initialise Groovy scripts with very little effort...
AddBeanDefinitionsListener.groovy
public class AddBeanDefinitionsListener
implements ApplicationListener<ApplicationPreparedEvent> {
Map<String, BeanDefinition> beanDefs
AddBeanDefinitionsListener(Map<String, BeanDefinition> beanDefs) {
this.beanDefs = beanDefs
}
#Override
void onApplicationEvent(ApplicationPreparedEvent event) {
def registry = (BeanDefinitionRegistry) event.applicationContext
.autowireCapableBeanFactory
beanDefs.each { String beanName, BeanDefinition beanDef ->
registry.registerBeanDefinition(beanName, beanDef)
}
}
/* Static Utility methods */
static BeanDefinition groovyScriptFactory(String scriptLocation) {
def bd = BeanDefinitionBuilder.genericBeanDefinition(GroovyScriptFactory)
.addConstructorArgValue(scriptLocation)
.getBeanDefinition()
bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, 1000)
bd
}
}
Application.groovy
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application)
app.addListeners(new AddBeanDefinitionsListener([
'foobar0': groovyScriptFactory("file:/some/path/Foobar0Service.groovy"),
'foobar1': groovyScriptFactory("file:/some/path/Foobar1Service.groovy")
]))
app.run(args)
}
#Bean
ScriptFactoryPostProcessor scriptFactory() {
new ScriptFactoryPostProcessor()
}
}
(Might be nicer if implemented with Spring 4.2's events?)

Resources