Using Spring Boot & Spring Integration with database backed Configuration - java

For spring boot + integration application, I'm attempting to load configuration from database, allow it to be accessible to Spring's Environment & inject-able via #Value annotation and be override-able by externalized configurations as described in the spring boot reference documentation under the Externalized Configuration Section.
The problem I'm having is that my spring Integration XML contains ${input} property placeholders that can not be resolved, because I can't get the database backed configuration to be loaded before Spring attempts to load the XML configurations.
The entry point to the application:
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
How database configuration would be loaded:
#Configuration
public class DbPropertiesConfig {
#Autowired
private org.springframework.core.env.Environment env;
#PostConstruct
public void initializeDatabasePropertySourceUsage() {
MutablePropertySources propertySources = ((ConfigurableEnvironment) env).getPropertySources();
try {
// The below code will be replace w/ code to load config from DB
Map<String,Object> map = new HashMap<>();
map.put("input-dir","target/input");
map.put("output-dir","target/output");
DbPropertySource dbPropertySource = new DbPropertySource("si",map);
propertySources.addLast(dbPropertySource);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
How Spring Integration config is loaded:
#Profile("IN")
#Configuration
#ImportResource({"si-common-context.xml","si-input-context.xml"})
public class SiInputAppConfig {
}
The Spring Integration XML configurationsi-input-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file
http://www.springframework.org/schema/integration/file/spring-integration-file.xsd" default-lazy-init="true">
<int-file:inbound-channel-adapter channel="input2" directory="${input-dir}" filename-pattern="*">
<int:poller fixed-rate="500"/>
</int-file:inbound-channel-adapter>
<int:service-activator input-channel="input2" ref="sampleEndpoint" method="hello" output-channel="output2"/>
<int:channel id="output2"/>
<int-file:outbound-channel-adapter channel="output2" directory="${output-dir}"/>
</beans:beans>
The error I get:
2015-10-28 17:22:18.283 INFO 3816 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [si-common-context.xml]
2015-10-28 17:22:18.383 INFO 3816 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [si-mail-in-context.xml]
2015-10-28 17:22:18.466 INFO 3816 --- [ main] o.s.b.f.config.PropertiesFactoryBean : Loading properties file from URL [jar:file:/C:/Users/xxx/.m2/repository/org/springframework/integration/spring-integration-core/4.1.6.RELEASE/spring-integration-core-4.1.6.RELEASE.jar!/META-INF/spring.integration.default.properties]
2015-10-28 17:22:18.471 INFO 3816 --- [ main] o.s.i.config.IntegrationRegistrar : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2015-10-28 17:22:18.604 INFO 3816 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; ...
2015-10-28 17:22:18.930 WARN 3816 --- [ main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean#0.source' defined in null: Could not resolve placeholder 'si.in-input' in string value "${si.in-input}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'input-dir' in string value "${input-dir}" ...
Spring loads DbPropertiesConfig after XML configuration is attempted to be loaded.
How can I resolve this issue?
Thanks in advance
AP

Yes, I can confirm by my home tests that XML definitions are loaded before the annotation stuff. It's enough complicated to determine why it is, but I'm sure that #Import* resources are more important by premise than an internal #Configuration logic.
Therefore your #PostConstruct isn't good for mix with XML.
One solution is to move all Spring Integration configuration to the Annotation style and even consider to use Spring Integration Java DSL.
Another solution is to follow with Spring Boot's Externalized Configuration recommendation:
Default properties (specified using SpringApplication.setDefaultProperties).
That means you have to read properties from your DB before the starting Spring Application. Yes, if you were going to use DataSource on the matter from the same application, it won't be possible. From other side let's take a look to your goal one more time! You are going to load properties for the application from DB as an external configuration, so, what is the point to do that from the application itself? Of course, it would be more safe to load and feed properties before application start.
Hope I am clear.

Related

Serving static files with spaces in file names with Spring Boot

I've found a very strange behavior in Spring Boot when trying to serve static files with spaces (or any other special chars, like accents) in file names.
I'm using Spring Boot 2.6.1 with Spring Web MVC and the following customization:
#Configuration
public class MyConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/repo/**")
.addResourceLocations("file:///srv/intranet/repo/");
}
}
I have two files under /srv/intranet/repo, named foo.txt and foo bar.txt (note the space in the second file name).
When I start my application, I can access the first file at http://localhost:8080/repo/foo.txt. But I cannot access the second file (the one with the space in the file name) at http://localhost:8080/repo/foo%20bar.txt (I get a 404 error).
BUT if I put the file foo bar.txt under src/main/resources/static, then I can acces the file at http://localhost:8080/foo%20bar.txt.
I'm aware that Spring Boot configures several directories by default to serve static content (one of them being classpath:/static/), so I'm wondering: what is the difference between the preconfigured directories and the one I'm adding in my #Configuration class via addResourceHandler().addResourceLocations()? Am I missing some details when adding the new resourceHandler?
WORKAROUND
You can set the following property in your application.properties (or equivalent .yml) to get the old behavior in Spring Boot (pre-v2.6.0):
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
UPDATE
I believe this is probably a bug in PathPattern, which replaces AntPathMatcher, and was introduced in Spring Framework 5.3 and adopted in Spring Boot 2.6.0. I submitted a bug report.
UPDATE (2022-06-04)
The bug has been fixed. The fix will be included in Spring Framework 5.3.21.
I have found a workaround for this issue.
Just add the following in your Spring Boot configuration file application.properties:
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
The documentation for this property states that ant-path-matcher is the default value, but it is not. The source code shows that the default value is path-pattern-parser. I submitted an issue.
I have following configuration with Spring Boot 2.6.1 and it successfully loads file with a space in name.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.example.sw" })
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**").addResourceLocations("file:D:/img/");
}
}
I have an image with name "SO 1.png" (note the space). When hitting the application I get the image
You can probably troubleshoot by putting Spring web on TRACE level where it emits below information while serving the file.
logging.level.org.springframework.web.servlet=TRACE
Below are the logs
2021-12-07 14:24:24.544 TRACE 17200 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/files/SO%201.png", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2021-12-07 14:24:24.568 TRACE 17200 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler [URL [file:D:/img/]]] and 3 interceptors
2021-12-07 14:24:24.580 TRACE 17200 --- [nio-8080-exec-1] o.s.w.s.r.ResourceHttpRequestHandler : Applying default cacheSeconds=-1
2021-12-07 14:24:24.626 TRACE 17200 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned.
2021-12-07 14:24:24.626 DEBUG 17200 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK, headers={masked}
in Spring 5 a new PathPatternParser was introduced
try replacing
registry.addResourceHandler("/files/**")
with:
registry.addResourceHandler("/files/{*path}")

Autowired bean is null but it is valued in the Spring container [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 3 years ago.
[RESOLVED] In this case the problem was that I haven't included into the build path some useful libs such as "jersey-spring4-2.28.jar" and "spring-bridge-2.5.0.jar". These libraries allow integration between Jersey and Spring.
I'm working on a web-app on Tomcat (9.0.16), which exposes useful REST services for data communication (services made with the help of Jersey 2.28). In the class that manages these services I used Spring (v5.1.5) to inject the dependencies as follows:
package it.learning.rest;
#Component
#Path("/")
public class GameClientCommsRestService {
#Autowired
#Qualifier("CommunicationBusiness")
private GameClientCommunication comm;
#GET
#Path("/test/check/bean")
#Produces(MediaType.TEXT_PLAIN)
public Response testCheckCommsBean() {
StringBuilder sb = new StringBuilder();
if (comm == null) {
sb.append("GameClientCommunication instance is NULL!!");
} else {
sb.append("GameClientCommunication instance is NOT NULL ---> SUCCESSFULLY instantiated");
}
return getRestResponse(sb.toString());
}
}
The class for which the dependency is injected is defined as follows:
package it.learning.business.impl;
public class GameClientCommunicationBusiness implements GameClientCommunication {
#Override
public String processesMessage(String request, String remoteIpAddress) {
// Processing input...
}
}
The XML configuration file is the following:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="it.learning" />
<context:annotation-config />
<bean id="applicationContextProvider" class="it.learning.utils.spring.ApplicationContextProvider">
</bean>
<bean id="CommunicationBusiness" class="it.learning.business.impl.GameClientCommunicationBusiness">
</bean>
</beans>
On application start-up, neither Tomcat nor the log service records any problems, but if I call
testCheckCommsBean()
it returns
"GameClientCommunication instance is NULL!!"
If instead I use the ApplicationContext object to get the instance associated with "CommunicationBusiness", I get a properly functioning object.
The log shows that both the annotated beans and those declared in the XML file were successfully created and inserted into the Spring container:
2019-03-19 13:06:00,343 DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 7 bean definitions from ServletContext resource [/WEB-INF/spring/appContext.xml]
2019-03-19 13:06:00,362 DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from ServletContext resource [/WEB-INF/spring/appContextBeans.xml]
2019-03-19 13:06:00,438 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
2019-03-19 13:06:00,526 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
2019-03-19 13:06:00,530 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
2019-03-19 13:06:00,532 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
2019-03-19 13:06:00,533 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
2019-03-19 13:06:00,537 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalPersistenceAnnotationProcessor'
2019-03-19 13:06:00,549 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'gameClientCommsRestService'
2019-03-19 13:06:00,579 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'CommunicationBusiness'
2019-03-19 13:06:00,582 DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'applicationContextProvider'
Differently from what reported in
Why is my Spring #Autowired field null?
I never instantiated a "GameClientCommunication" object using the "new" keyword, therefore I really don't understand why the field annotated with #Autowired is null.
Thanks for your support!
You can try #component annotation on top of GameClientCommunicationBusiness class.
The problem is you didn't wire the implementation class.

How to annotate a custom Spring Boot custom repository?

I have an entity class called Screenshot and and a repository declared as this:
public interface ScreenshotRepository extends JpaRepository<Screenshot, UUID>, ScreenshotRepositoryCustom
The custom repository is defined like this:
interface ScreenshotRepositoryCustom
and
class ScreenshotRepositoryImpl implements ScreenshotRepositoryCustom {
private final ScreenshotRepository screenshotRepo;
#Autowired
public ScreenshotRepositoryImpl(ScreenshotRepository screenshotRepo) {
this.screenshotRepo = screenshotRepo;
}
This is following what's described in this other Stack Overflow question: How to add custom method to Spring Data JPA
Now, IntelliJ is giving me a warning:
Autowired members must be defined in a valid Spring bean
I tried adding these annotations to ScreenshotRepositoryImpl but none of the worked:
#Repository
#Component
#Service
but none work. Obviously some are wrong but I was experimenting. What's the correct annotation.
With #Repository, I get this error:
2017-11-23 12:30:04.064 WARN 20576 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'screenshotsController' defined in file [C:\Users\pupeno\Documents\Dashman\java\dashmanserver\out\production\classes\tech\dashman\server\controllers\ScreenshotsController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'screenshotRepositoryImpl' defined in file [C:\Users\pupeno\Documents\Dashman\java\dashmanserver\out\production\classes\tech\dashman\server\models\ScreenshotRepositoryImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'screenshotRepositoryImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
2017-11-23 12:30:04.064 INFO 20576 --- [ restartedMain] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2017-11-23 12:30:04.064 INFO 20576 --- [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2017-11-23 12:30:04.080 INFO 20576 --- [ restartedMain] utoConfigurationReportLoggingInitializer :
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-11-23 12:30:04.080 ERROR 20576 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
screenshotsController defined in file [C:\Users\pupeno\Documents\Dashman\java\dashmanserver\out\production\classes\tech\dashman\server\controllers\ScreenshotsController.class]
┌─────┐
| screenshotRepositoryImpl defined in file [C:\Users\pupeno\Documents\Dashman\java\dashmanserver\out\production\classes\tech\dashman\server\models\ScreenshotRepositoryImpl.class]
└─────┘
What is happening?
Your dependencies form a cycle: ScreenshotRepository extends ScreenshotRepositoryCustom, but the ScreenshotRepositoryCustom implementation depends on ScreenshotRepository. Because of this cycle, none of the beans can complete their instantiation.
Proposed solution
Spring Data does not support injection via constructor in these scenarios. Attempting to do so will result in a dependency cycle error. To be able to inject ScreenshotRepository into ScreenShotRepositoryImpl, you'd need to do injection via field:
#Repository
public class ScreenshotRepositoryImpl implements ScreenshotRepositoryCustom {
#Autowired
ScreenshotRepository screenshotRepository ;
...
}

Spring boot starter web - make a configuration bean that alters properties initialized first

I am using embedded tomcat in my spring boot application. My requirement is to read all configuration properties from db as well as property files.
I managed to read properties from db and append the properties to MutablePropertySources with a #Configuration bean as follows:
#Configuration
public class PropertiesConf {
#Autowired
private Environment env;
#Autowired
private ApplicationContext appContext;
#PostConstruct
public void init() {
MutablePropertySources propertySources = ((ConfigurableEnvironment) env).getPropertySources();
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
DataSource ds = (DataSource) appContext.getBean("confDBBeanName");
JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
//read config elements from db
//List<IntegraProperties> list = ..
list.forEach(entry -> map.put(entry.getKey(), entry.getValue()));
MapPropertySource source = new MapPropertySource("custom", map);
propertySources.addFirst(source);
}
}
The problem is that this config is initialized after servlets (cxf servlet for example) are registered. The folowing config is read from cxf.path=/api2 from my application.properties file:
2017-11-10 09:41:41.029 INFO 7880 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'CXFServlet' to [/api2/]*
As you can see, by the time I add the configuration properties, it is too late. Some initializations take place before I add my config .
How can I make sure my bean (PropertiesConf) initializes first during start up and alters properties so that they are system wide applicable to all beans?
Currently I am adding the following DependsOn annotation to all my beans which is very nasty...
#DependsOn("propertiesConf")
But still I have a problem with servlets and etc..
What is the correct spring way to do this
Probably you are looking for the EnvironmentPostProcessor.
It makes it possible to change Environment before the application context is started and I believe it's the most clearer way to do that.
Here is a tutorial to help you to get started: https://blog.frankel.ch/another-post-processor-for-spring-boot/#gsc.tab=0

how to configure 'dispatcherServlet' load on startup by spring boot?

I use spring-boot-starter-parent as parent and add spring-boot-starter-web as denpendency.
By add the #SpringBootApplication annotation, it works.
But DispatcherServlet need initialization
Initializing servlet 'dispatcherServlet'
FrameworkServlet 'dispatcherServlet': initialization started
Using MultipartResolver [org.springframework.web.multipart.support.StandardServletMultipartResolver#745f40ac]
Unable to locate LocaleResolver with name 'localeResolver': using default [org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver#219fc57d]
Unable to locate ThemeResolver with name 'themeResolver': using default [org.springframework.web.servlet.theme.FixedThemeResolver#7b4bd6bd]
Unable to locate RequestToViewNameTranslator with name 'viewNameTranslator': using default [org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#71ccfa36]
Unable to locate FlashMapManager with name 'flashMapManager': using default [org.springframework.web.servlet.support.SessionFlashMapManager#43f3e6a9]
Published WebApplicationContext of servlet 'dispatcherServlet' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet]
FrameworkServlet 'dispatcherServlet': initialization completed in 37 ms
I hope I can set it's loadonstartup by 1, and don't want to use this
annoying BeanNameUrlHandlerMapping, it rejected everything and I'm not going to use it.
o.s.w.s.h.BeanNameUrlHandlerMapping : Rejected bean name 'contextAttributes': no URL paths identified
I read the java-doc about BeanNameUrlHandlerMapping:
This is the default implementation used by the org.springframework.web.servlet.DispatcherServlet, along with org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping (on Java 5 and higher). Alternatively, SimpleUrlHandlerMapping allows for customizing a handler mapping declaratively.
That's all, I just want to change these two thing:
setLoadonStartup
don't use BeanNameUrlHandlerMapping
Beside that, other thing spring boot configure for me is very great, and I want to keep it.
Thank you for any help you can provide.
New reply to old post. Seems this is easier to do with more recent versions of Spring Boot. Just adding the property spring.mvc.servlet.load-on-startup=1 works for me.
I encountered the same problem with loadOnStartup. I solved it by using a custom BeanFactoryPostProcessor to modify the BeanDefinition of the ServletRegistrationBean that Spring Boot creates for registering the DispatcherServlet.
The following code will set loadOnStartup for the DispatcherServlet in a Spring Boot app, when used within an #Configuration class:
#Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return new BeanFactoryPostProcessor() {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition bean = beanFactory.getBeanDefinition(
DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
bean.getPropertyValues().add("loadOnStartup", 1);
}
};
}
BTW, the BeanNameUrlHandlerMapping is harmless here.
It is used to map a Spring Bean to a URL - for example it might be used to support Spring HttpInvoker remoting.
The rejection lines in the log output simply mean that it doesn't recognize any of the Spring beans as beans that require a URL mapping. Annoying messages but harmless. You could always set the logging level for this bean or its package to INFO or above to remove the message. In Spring Boot's application.properties put
logging.level.org.springframework.web.servlet.handler=INFO

Categories

Resources