Spring-boot application configuration - spring-boot

I understand how to configure spring-boot and it provides a mature and sensible overriding mechanism that is documented well but I'm converting an application that gets its configuration from other source that is not in line with the spring-boot mechanism.
Ultimately the application makes properties available to the code that can be bound in using #Value("$the.prop.key:default") or used in spring xml config. The way that these properties are retrieved and bound cannot be changed.
I am trying to configure the embedded tomcat server port but the only way I can do this is using application.properties. I can change this to a different file and even change the location but I cannot change the mechanism (it has to be a file).
Looking into the spring-boot code I see it uses the notion of EmbeddedServletContainerCustomizer implementations to set these properties. Fine, I will create an implementation and set the server properties using this. But unfortunately you get 2 implementations trying to do the same thing ServerProperties and my implementation. The code orders these but because ServerProperties has not ordering it is set to the lowest priority and a low priority gets executed last and so my implementation gets overwritten.
Instead I have implemented a BeanPostProcessor:
#Named
public class SpringBootCustomConfigurator implements BeanPostProcessor {
#Value("$the.prop.key:8080")
private int port;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ServerProperties) {
ServerProperties serverProperties = (ServerProperties) bean;
serverProperties.setPort(port);
}
return bean;
}
}
This does what I need to do but it isn't a satisfactory implementation. Any thoughts?

Given that it's about a new source of external properties, I think it would be more natural to write an ApplicationContextInitializer (or ApplicationListener to listen for one of the Spring Boot events on startup) that adds a new PropertySource to your Environment in the right place. You can register initalizers with the SpringApplication or using META-INF/spring.factories.

Related

Creating a custom FactoryBean in Sprint Boot 2.3/Spring 5

I've got a spring-boot web application that's mostly working; my DataSource is properly configured by an external application.properties file.
Now I want to add properties to that file to help me instantiate and configure two instances of a class in my app. I have a APNsFactory that I currently instantiate manually and configure using JNDI, but I want to get away from JNDI calls:
#Bean
public
APNsFactory
apnsFactory()
throws
javax.naming.NamingException
{
sLogger.info("Configuring APNsFactory");
InitialContext ctx = new InitialContext();
APNsFactory f = new APNsFactory();
f.setProductionKeystorePath((String) ctx.lookup("java:comp/env/apns/prod/keystorePath"));
f.setProductionKeystorePassword((String) ctx.lookup("java:comp/env/apns/prod/keystorePassword"));
f.setDevelopmentKeystorePath((String) ctx.lookup("java:comp/env/apns/dev/keystorePath"));
f.setDevelopmentKeystorePassword((String) ctx.lookup("java:comp/env/apns/dev/keystorePassword"));
return f;
}
When running before in a standalone webapp container, Spring properly called that method and the JNDI context from the container’s <env-entry> tags was available.
I'm trying to update my APNsFactory to be a proper Spring FactoryBean<>, and I’ve given it a couple of #Autowire String variables that I want to be set by Spring Boot from the application.properties file.
For bonus points, I want this to be usable both in Spring Boot and in a standalone container like Tomcat or Resin.
For the life of me, I can't figure out how to get Spring to do this. There are dozens of examples for DataSources and other Beans already implemented by Spring, but none for a completely custom one, using application.properties, in a Spring Boot web environment.
I've seen some examples that use an XML config file, but I'm not sure how to do that with Spring Boot.
I don't think you need a factory bean here.
You already have spring boot that can read application.properties out-of-the-box:
So try the following:
Create key/values in the application.properties file:
myapp.keystore.path=...
myapp.keystore.passwd=...
// the same for other properties
Create ConfigurationProperties class
#ConfigurationProperties(prefix="myapp.keystore")
public class MyAppKeyStoreConfigProperties {
private String path; // the names must match to those defined in the properties file
private String passwd;
... getters, setters
}
In the class marked with #Configuration (the one where you create #Bean public APNsFactory apnsFactory()) do the following:
#Configuration
// Note the following annotation:
#EnableConfigurationProperties(MyAppKeyStoreConfigProperties.class)
public class MyConfiguration {
// Note the injected configuration parameter
#Bean public APNsFactory apnsFactory(MyAppKeyStoreConfigProperties config) {
APNsFactory f = new APNsFactory();
f.setProductionKeystorePath(config.getKeyPath());
and so on
}
}
I've intentionally didn't show the separation between production/dev stuff.
In spring boot you have profiles so that the same artifact (WAR, JAR whatever) can be configured to run with different profile and depending on that the corresponding properties will be read.
Example:
If you're running with prod profile, then in addition to application.properties that will be loaded anyway, you can put these keystore related definitions to application-prod.properties (the suffix matches the profile name) - spring boot will load those automatically. The same goes for dev profile of course.
Now I haven't totally understand the "bonus points" task :) This mechanism is spring boot proprietary way of dealing with configuration. In "standalone" server it should still have a WAR with spring boot inside so it will use this mechanism anyway. Maybe you can clarify more, so that I / our colleagues could provide a better answer

Spring Boot application runs fine via Maven but not via IDE Intellij IDEA

I have a spring boot application which runs fine via Maven's mvn spring-boot:run command. However, when I try to run it through the IDE, which is Intellij IDEA 2017.2.1 in my case, it fails because it could not #Autowire a data source.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myApp.Application required a bean of type 'javax.sql.DataSource' that could not be found.
Action:
Consider defining a bean of type 'javax.sql.DataSource' in your configuration.
The original authors of this code base have the main class, which starts the application, accepting constructor arguments for the data source, an approach I am unfamiliar with as I am used to just doing it through the application.properties file and letting Spring Boot wire up it's own DataSource.
#EnableTransactionManagement
#SpringBootApplication
#EnableCaching
public class Application extends JpaBaseConfiguration {
protected Application(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, properties, jtaTransactionManagerProvider, transactionManagerCustomizers);
}
In IDEA, I've noticed that the datasource and the properties arguments to this constructor are underlined in red. For datasource the IDE is complaining that two beans exist and it doesn't know which to autowire between XADataSourceAutoConfiguration.class and DataSourceConfiguration.class. As for the other argument to the construction which is underlined in red, properties, it can't find any beans, the IDE complains that no bean of type JpaProperties is found. Here are some other methods which are overridden in the main application starter class,
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> vendorProperties = new LinkedHashMap<>();
vendorProperties.putAll(getProperties().getHibernateProperties(getDataSource()));
return vendorProperties;
}
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
Unfortunately, because I am not familiar with this approach of using the constructor to configure/auto-configure the application in Spring Boot, I am unsure of a few things, but my exact question is why does the application run fine with Maven but not in Intellij IDEA? Moreover, since I don't have access to the original authors to this proprietary code base, I'd love to know why, if anyone can even give me a hint, they have configured the constructor as such as opposed to default autoconfiguration. I also have an integration test which I wrote that I am trying to run but this test, whether run through the IDE or via Maven's failsafe plugin also results in the same error with the DataSource not being #Autowired. So this is another question as to why this test won't run through Maven when the main application will. Here's my integration test,
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(value = TransactionController.class, secure = false)
public class TransactionControllerIT {
#Autowired
MockMvc mockMvc;
#Test
public void shouldInitiateTransfer() {
String transferTransaction =
"some json string I can't show here on stack overflow";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/begin-transfer")
.accept(MediaType.APPLICATION_JSON).content(transferTransaction)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = null;
try {
result = mockMvc.perform(requestBuilder).andReturn();
} catch (Exception e) {
fail("Exception in integration test!");
}
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
}
}
Thank you for reading my question.
You can easily run any Spring Boot app from IDEA doing the following:
In Maven panel, go to Plugins, unfold spring-boot and right-click on "spring-boot:run". Then click on "Create your-project..." as shown in the image.
This way you can just start the application in a comfortable way from IDEA from the main toolbar:
This way you are still using the maven way, but integrated in IDEA. I don't really know why are you having those problems. I also experience some problems when trying to execute the spring boot app directly.
Your test is failing because it's using a slice test, #WebMvcTest these tests (#DataJpaTest, JsonTest) only loads a small part of the overall application context rather than everything that the application does on startup or a (#SpringBootTest) would.
When using a slice test it will use any annotations, and require any beans, defined within the #SpringBootApplication class.
E.g. because you have autowired beans defined and and two additional annotations for any slice test caching and transaction management will be enabled and it will always require these dependencies passed.
I would not make your main application class extend a configuration class in this way, it's overly complex and smells like XY problem. You should externalize configurations (and Enable annotations) to their own #Configuration class and leave the #SpringBootApplication as vanilla a possible to avoid these sort of errors.

Camel context properties from DB

Right now, I bootstrap Camel using Spring. Using Spring, I can point my Camel contexts to their respective properties files which are then injected at boot time. My issues is that I now want to move my properties from a file to a database, yet still be able to use the property placeholders as I was before. What's the best way to go about doing this?
I've noticed that there's a PropertiesResolver interface that I could implement, but I wouldn't know how to tell Camel about my implementation. Camel's documentation is very lacking in this area.
I also wouldn't be opposed to having Spring get the properties from the database for me, although I don't see that happening.
The PropertiesResolver was designed to help Camel to locate the properties files from OSGi bundle or normal class path.
If you want to setup your owner PropertiesResolver, you can try to use org.apache.camel.spring.spi.BridgePropertyPlaceholderConfigurer.
You can inject your customer implementation of PropertiesResolver there.
I thought I'd update this with what I found to work. This probably isn't the best method way to do it, but it works and I didn't have to modify the Camel source code. Basically, I converted all of my classes from inheriting from RouteBuilder to inherit from GJKRouteBuilder (which inherits from RouteBuilder). Then in there, I did this:
public class GJKRouteBuilder extends RouteBuilder {
#Override
protected void checkInitialized() throws Exception {
//Get properties from CamelContext using getContext()
//Lookup properties from DB based on CamelContext
//Get the properties component from the context (or create one)
//call setOverrideProperties() on properties component
super.checkInitialized();
}
}
Again, probably not the best method, but it works. Now, any route that inherits from GJKRouteBuilder and has the proper values wired up through Spring will have the properties injected into the properties component as if they were coming right from a properties file.

How can I instantiate an injected class (using Spring) before logback configuration

I am using spring to inject a class into my PropertyDefiner implementation which will be used to help set up some properties within the logback.xml file (through dynamic property loading).
I'd love to get this class loaded and instantiated before logback is configured. Any thoughts on how to do this?
If you're using annotations in Spring, it's convenient to do this by marking the class (i.e. the dependency) you'll be injecting as #Component and then using #Autowired in your PropertyDefiner implementation. This ensures that the first class will be instantiated first. http://static.springsource.org/spring/docs/3.0.0.M3/spring-framework-reference/html/ch04s12.html
Any other initialization you require could be achieved using instance initializer blocks http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
I do not know if this can be done elegantly at present time (2012-07). However, support for injection has been requested in LOGBACK-719.
If your bean factory implements AutowireCapableBeanFactory, given the Spring Applicaton context, you could invoke autowireBean(Object existingBean) to autowire the bean. Here is a tentative implementation:
class Your.PropertyDefiner implements PropertyDefiner, LifeCycle {
#Autowired
#Qualifier("myKey")
String myKey;
public void start() {
ApplicationContext appContext = ... somehow get the spring app context
AutowireCapableBeanFactory factory = appContext.getAutowireCapableBeanFactory();
factory.autowireBean(this); // declare victory
}
}
The start() method will be invoked only if your PropertyDefiner implements the LifeCycle interface. Moreover, you need logback version 1.0.7 or later. Earlier versions do not invoke start().
My solution resulted in not implementing a PropertyDefiner. The original question became an issue of not having the application context from spring to set the dynamic properties. I'm not sure why, but code in a later listener (after the Spring listeners) would get called (invoking the LoggerFactory call) before the application context was available. I tried a number of things, until I starting looking at a different approach.
Instead of using dynamic properties I created a listener (called on server startup) which then programmatically sets up my appender with the properties I want (through the createAdminNotifyAppender).
#Override
public void contextInitialized(ServletContextEvent arg0)
{
//Set up the property reader to pull the correct properties
ServletContext context = arg0.getServletContext();
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(context);
propReader = (AppConfigPropertiesReader)appContext.getBean("propertySourcesPlaceholder");
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
createAdminNotifyAppender(lc, propReader);
}
The createAdminNotify method simply sets up an appender and adds it to the logging context. (if you're really interested, you can see that method's implementation on this thread).
Now I have a separate and modular listener that I can add to other apps that are using logback, but possibly with different properties. The properties are pulled from a database and can also vary by environment.

Spring: how AnnotationConfigWebApplicationContext could not overwrite later beans?

I have a web application that use Sring IoC framework.
I use the Java configuration for Spring, and I only use #Configuration annoted module definition (no DI related tags elsewhere in the code).
The Spring registry is built on web application start-up thanks to (a bit modified version of) Spring context loader listener, and the contextConfigLocation param in web.xml configured to point to the #Configuration annotated class.
All that is good and I get a AnnotationConfigWebApplicationContext.
Now, I want to have plugins in my application, that will have their own #Configuration annotated configuration classes, and will use some of the main application services. BUT I don't want to have main application to be modified to load these new modules.
So, I thought that I could simply use the package searching of annotated class for that, but now, it seems that I can use two beans with the same type, even if they have different ids, and clearly AnnotationConfigWebApplicationContext doc states that:
Note: In case of multiple #Configuration classes, later #Bean definitions will override ones defined in earlier loaded files. This can be leveraged to deliberately override certain bean definitions via an extra Configuration class.
I don't want that, because modules should be able to contribute alternative version of services, not (alwways) override existing one - especcially if I want to have a "moduleDef" bean.
I tried to use differents approach on that, but the hierachy of Context and related services is just to big for me.
So, does anybody know how I could reach my goal ?
Thanks
You can have multiple beans of the same type, but You cannot have 2 or more beans with the same ID in a single Spring ApplicationContext - no matter if You use XML or JavaConfig.
The overriding mechanism matches the bean ID's, so all You need to do is to ensure unique ID, i.e.: coreModuleDef, someOtherModuleDef, anotherModuleDef. I don't think You need the ID of each module definition to be identical? What should be sufficient is the type to be the same, but not ID.
You can also turn off the overriding mechanism by setting allowBeanDefinitionOverriding to false on Your AnnotationConfigWebApplicationContext to get an exception if You accidentally override a bean:
public class MyDispatcherServlet extends DispatcherServlet {
#Override
protected void postProcessWebApplicationContext(
ConfigurableWebApplicationContext wac) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}
or:
public class MyContextLoaderListener extends ContextLoaderListener {
#Override
protected void customizeContext(
ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}

Resources