Spring MVC: configure properties from before bean creation - java

I have a scenario where I want to programmatically inject properties into Spring before any beans are created/initialized:
The beans (not modifiable) are configured with ConditionalOnProperty, so properties need to be set before creation.
Properties need to be configured dynamically and programmatically, not via property file (we call an API and use the result to set the property value).
I see ApplicationContext has a way to get the current environment's property sources (via ConfigurableEnvironment), but I am not sure how to inject into the Spring lifecycle to configure the ApplicationContext before beans are initialized.
I'm aware of BeanFactoryPostProcessor as a hook which occurs before bean initialization, but I don't see a way to obtain an instance of ApplicationContext in it.
How can it be accomplished?
Note: the application itself is Spring Web/MVC, not Spring Boot. The third party library internally uses Spring Boot classes (ConditionalOnProperty).

Based on #m-deinum's comment, I was able to get it working with the following:
public class MyPropertyInitializer extends WebMvcConfigurerAdapter implements WebApplicationInitializer, ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
String initializerClasses = servletContext.getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
String initClassName = this.getClass().getName();
if (StringUtils.isNotBlank(initializerClasses)) {
initializerClasses += "," + initClassName;
} else {
initializerClasses = initClassName;
}
servletContext.setInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM, initializerClasses);
}
#Override
public void initialize(ConfigurableApplicationContext context) {
Properties props = getCustomProperties();
PropertySource<?> propertySource = new PropertiesPropertySource("my-custom-props", props);
context.getEnvironment().getPropertySources().addLast(propertySource);
}
protected Properties getCustomProperties() {
Properties props = new Properties();
// do logic to set desired values
return props;
}
}

Related

How to read spring boot tomcat properties another user defined bean before initialization?

I want to read spring boot ssl keystore password value from user defined spring bean and set to SSL property server.ssl.key-password.
#Component
public class KeyStorePasswordBean {
public String password() {
//implementation
}
}
I tried with EnvironmentPostProcessor but NullPointer exception thrown because KeyStorePasswordBean bean will be initialized after EnvironmentPostProcessor
public class PropertyEnvironmentPostProcessor implements EnvironmentPostProcessor {
#Autowired
KeyStorePasswordBean keyStorePasswordBean;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String keystorePassword = keyStorePasswordBean.password(); //NullPointerException
System.out.println("password: " + keystorePassword);
}
}
How to handle this in spring boot?
Thinking on how to load KeyStorePasswordBean bean to spring context before tomcat server initialization start.
I am using spring boot version 2.25 and spring version 5.2.4
you can read the following property via:
ConfigurableEnvironment environment
like that:
environment.getProperty("NAME_OF_YOUR_PROPERTIES")
then you can create and add your custom property source to the environment (with 'server.ssl.key-password' property)
Map<String, Object> properties = new HashMap<>();
properties.put("server.ssl.key-password", property); // property = environment.getProperty("NAME_OF_YOUR_PROPERTIES")
environment.getPropertySources().addLast(new MapPropertySource("muCustomPropertySource", properties));
also, read the following article - https://www.baeldung.com/spring-boot-environmentpostprocessor

Access properties in BeanFactoryPostProcessor

I am trying to create something which will auto-create beans based on configurable properties (from application.yml and the like).
Since I can't just access the properties component like I normally would in the BeanFactoryPostProcessor, I'm kind of stumped how I can access them.
How can I access application properties in BeanFactoryPostProcessor?
If you want to access properties in a type-safe manner in a BeanFactoryPostProcessor you'll need to bind them from the Environment yourself using the Binder API. This is essentially what Boot itself does to support #ConfigurationProperties beans.
Your BeanFactoryPostProcessor would look something like this:
#Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(
Environment environment) {
return new BeanFactoryPostProcessor() {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
BindResult<ExampleProperties> result = Binder.get(environment)
.bind("com.example.prefix", ExampleProperties.class);
ExampleProperties properties = result.get();
// Use the properties to post-process the bean factory as needed
}
};
}
I didn't want to use the solution above that used an #Bean producer method since it's contrary to the recommended approach of annotating a class with #Component and picking up via component scanning. Fortunately it's straightforward to do that by implementing EnvironmentAware:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ConditionalDependencyPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
/** Logger instance. */
private final Logger logger = LoggerFactory.getLogger(ConditionalDependencyPostProcessor.class);
/** Spring environment. */
private Environment environment;
#Override
public void setEnvironment(final Environment env) {
environment = env;
}
...
private boolean hasRequiredProfiles(final DependencyInfo info) {
final Set<String> activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));
for (String profile : info.requiredProfiles) {
if (!activeProfiles.contains(profile)) {
return false;
}
}
return true;
}
I should note what did NOT work: trying to autowire an Environment constructor argument. BeanFactoryPostProcessors require a no-argument constructor and don't support autowiring, which is itself a feature implemented by another post processor, AutowiredAnnotationBeanPostProcessor.

How to control loading order when mixing auto-configure and component scan in Spring Boot

The user story is like this:
We have exposed some APIs through library jars which use the Spring Boot auto-configure mechanism. The APIs need some glue logic during initialization, so we put them under #PostConstruct block of some #Configurations and register them as auto-configured.
The user code is based on #SpringBootApplication, while they prefer to use #ComponentScan to define beans instead auto-configure.
So the issue comes, Spring always tries to load the beans defined by #ComponentScan first, then those auto-configured beans. So if any user bean relies on the API that not been initialized, it sure will fail.
It seems there's no way to define the bean order when mixing the auto-configured and component-scanned beans. The #Order, #AutoConfigureOrder, #AutoConfigureAfter, #AutoConfigureBefore annotation and Ordered interface only work among all auto configured beans.
Of course if user uses auto-configure for their beans too, it will work without problem. But from user's perspective, #ComponentScan looks a more natural and easier way, especially #SpringBootApplication implies the #ComponentScan of current java package.
Our current workaround is to eager load those API initialization configures at a very-early stage. For web context, it's ServletContextInitializer.onStartup(), and for other generic context, it's LoadTimeWeaverAware's #PostConstruct.
#Configuration
#ConditionalOnWebApplication
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class EagerInitWebContextInitializer implements ServletContextInitializer, PriorityOrdered
{
#Autowired
private ApplicationContext appContext;
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
String[] beanNames = appContext.getBeanNamesForAnnotation(EagerInitializer.class);
if (beanNames != null) {
// pre-load all eager initializers
for (String name : beanNames) {
appContext.getBean(name);
}
}
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
#Configuration
#ConditionalOnNotWebApplication
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class EagerInitGenericContextInitializer implements LoadTimeWeaverAware, PriorityOrdered
{
#Autowired
private ApplicationContext appContext;
#PostConstruct
protected void init() {
String[] beanNames = appContext.getBeanNamesForAnnotation(EagerInitializer.class);
if (beanNames != null) {
// pre-load all eager initializers
for (String name : beanNames) {
appContext.getBean(name);
}
}
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
// does nothing
}
}
This actually works well. But just wondering is there any better way to achieve this or whether future Spring version can provide a similar systematic way?

How to access spring boot properties from freemarker template

My problematic is really simple :
In my spring-boot web application, I have some env-related properties that the front/client-side needs to know about (let's say, a CORS remote url to call that is env dependant).
I have correctly defined my application-{ENV}.properties files and all the per-env-props mecanism is working fine.
The question I can't seem to find answer to is : how do you allow your freemarker context to know about your properties file to be able to inject them (specifically in a spring-boot app). This is probably very easy but I cant find any example...
Thanks,
Gonna answer myself :
Easiest way in spring-boot 1.3 is to overrides the FreeMarkerConfiguration class :
/**
* Overrides the default spring-boot configuration to allow adding shared variables to the freemarker context
*/
#Configuration
public class FreemarkerConfiguration extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
#Value("${myProp}")
private String myProp;
#Override
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = super.freeMarkerConfigurer();
Map<String, Object> sharedVariables = new HashMap<>();
sharedVariables.put("myProp", myProp);
configurer.setFreemarkerVariables(sharedVariables);
return configurer;
}
}
One option in spring boot 2:
#Configuration
public class CustomFreeMarkerConfig implements BeanPostProcessor {
#Value("${myProp}")
private String myProp;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
Map<String, Object> sharedVariables = new HashMap<>();
sharedVariables.put("myProp", myProp);
configurer.setFreemarkerVariables(sharedVariables);
}
return bean;
}
}
Spring Boot 2.x changed class structure so it's no longer possible to subclass and keep the auto configuration like was possible with Spring Boot 1.x.
import freemarker.template.Configuration;
#Component
public class FreemarkerConfiguration {
#Autowired
private Configuration freemarkerConfig;
#Value("${myProp}")
private String myProp;
Map<String, Object> model = new HashMap();
model.put("myProperty", myProp);
// set loading location to src/main/resources
freemarkerConfig.setClassForTemplateLoading(this.getClass(), "/");
Template template = freemarkerConfig.getTemplate("template.ftl");
String templateText = FreeMarkerTemplateUtils.
processTemplateIntoString(template, model);
}
step 2,
get property in freemarker template code.
<div>${myProperty}</td>

How to instantiate Spring managed beans at runtime?

I stuck with a simple refactoring from plain Java to Spring. Application has a "Container" object which instantiates its parts at runtime. Let me explain with the code:
public class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
public void load() {
// repeated several times depending on external data/environment
RuntimeBean beanRuntime = createRuntimeBean();
runtimeBeans.add(beanRuntime);
}
public RuntimeBean createRuntimeBean() {
// should create bean which internally can have some
// spring annotations or in other words
// should be managed by spring
}
}
Basically, during load container asks some external system to provide him information about number and configuration of each RuntimeBean and then it create beans according to given spec.
The problem is: usually when we do in Spring
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Container container = (Container) context.getBean("container");
our object is fully configured and have all dependencies injected. But in my case I have to instantiate some objects which also needs dependency injection after I execute load() method.
How can I achieve that?
I am using a Java-based config. I already tried making a factory for RuntimeBeans:
public class BeanRuntimeFactory {
#Bean
public RuntimeBean createRuntimeBean() {
return new RuntimeBean();
}
}
Expecting #Bean to work in so called 'lite' mode. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html Unfortunately, I found no difference with simply doing new RuntimeBean();
Here is a post with a similar issue: How to get beans created by FactoryBean spring managed?
There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html but it looks like a hammer in my case.
I also tried ApplicationContext.getBean("runtimeBean", args) where runtimeBean has a "Prototype" scope, but getBean is an awful solution.
Update 1
To be more concrete I am trying to refactor this class:
https://github.com/apache/lucene-solr/blob/trunk/solr/core/src/java/org/apache/solr/core/CoreContainer.java
#see #load() method and find "return create(cd, false);"
Update 2
I found quite interesting thing called "lookup method injection" in spring documentation:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method-injection
And also an interesting jira ticket https://jira.spring.io/browse/SPR-5192 where Phil Webb says https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-86051 that javax.inject.Provider should be used here (it reminds me Guice).
Update 3
There is also http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
Update 4
The issue with all these 'lookup' methods is they don't support passing any arguments.. I also need to pass arguments as I would do with applicationContext.getBean("runtimeBean", arg1, arg2). Looks like it was fixed at some point with https://jira.spring.io/browse/SPR-7431
Update 5
Google Guice have a neat feature for it called AssistedInject. https://github.com/google/guice/wiki/AssistedInject
Looks like I found a solution. As I am using java based configuration it is even simpler than you can imagine. Alternative way in xml would be lookup-method, however only from spring version 4.1.X as it supports passing arguments to the method.
Here is a complete working example:
public class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
private RuntimeBeanFactory runtimeBeanFactory;
public void load() {
// repeated several times depending on external data/environment
runtimeBeans.add(createRuntimeBean("Some external info1"));
runtimeBeans.add(createRuntimeBean("Some external info2"));
}
public RuntimeBean createRuntimeBean(String info) {
// should create bean which internally can have some
// spring annotations or in other words
// should be managed by spring
return runtimeBeanFactory.createRuntimeBean(info);
}
public void setRuntimeBeanFactory(RuntimeBeanFactory runtimeBeanFactory) {
this.runtimeBeanFactory = runtimeBeanFactory;
}
}
public interface RuntimeBeanFactory {
RuntimeBean createRuntimeBean(String info);
}
//and finally
#Configuration
public class ApplicationConfiguration {
#Bean
Container container() {
Container container = new Container(beanToInject());
container.setBeanRuntimeFactory(runtimeBeanFactory());
return container;
}
// LOOK HOW IT IS SIMPLE IN THE JAVA CONFIGURATION
#Bean
public BeanRuntimeFactory runtimeBeanFactory() {
return new BeanRuntimeFactory() {
public RuntimeBean createRuntimeBean(String beanName) {
return runtimeBean(beanName);
}
};
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RuntimeBean runtimeBean(String beanName) {
return new RuntimeBean(beanName);
}
}
class RuntimeBean {
#Autowired
Container container;
}
That's it.
Thanks everyone.
i think that your concept is wrong by using
RuntimeBean beanRuntime = createRuntimeBean();
you are bypassing Spring container and resorting to using regular java constructor therefore any annotations on factory method are ignored and this bean is never managed by Spring
here is the solution to create multiple prototype beans in one method, not pretty looking but should work, I autowired container in RuntimeBean as proof of autowiring shown in log also you can see in log that every bean is new instance of prototype when you run this .
'
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
Container container = (Container) context.getBean("container");
container.load();
}
}
#Component
class Container {
private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
#Autowired
ApplicationContext context;
#Autowired
private ObjectFactory<RuntimeBean> myBeanFactory;
public void load() {
// repeated several times depending on external data/environment
for (int i = 0; i < 10; i++) {
// **************************************
// COMENTED OUT THE WRONG STUFFF
// RuntimeBean beanRuntime = context.getBean(RuntimeBean.class);
// createRuntimeBean();
//
// **************************************
RuntimeBean beanRuntime = myBeanFactory.getObject();
runtimeBeans.add(beanRuntime);
System.out.println(beanRuntime + " " + beanRuntime.container);
}
}
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public RuntimeBean createRuntimeBean() {
return new RuntimeBean();
}
}
// #Component
class RuntimeBean {
#Autowired
Container container;
} '
A simple approach:
#Component
public class RuntimeBeanBuilder {
#Autowired
private ApplicationContext applicationContext;
public MyObject load(String beanName, MyObject myObject) {
ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) applicationContext;
SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();
if (beanRegistry.containsSingleton(beanName)) {
return beanRegistry.getSingleton(beanName);
} else {
beanRegistry.registerSingleton(beanName, myObject);
return beanRegistry.getSingleton(beanName);
}
}
}
#Service
public MyService{
//inject your builder and create or load beans
#Autowired
private RuntimeBeanBuilder builder;
//do something
}
Instead of using SingletonBeanRegistry you can use this:
BeanFactory beanFactory = configContext.getBeanFactory();
Anyway SingletonBeanBuilder extends HierarchicalBeanFactory and HierarchicalBeanFactory extends BeanFactory
You don't need the Container because all of the runtime objects should be created, held and managed by ApplicationContext. Think about a web application, they are much the same. Each request contains external data/environment info as you mentioned above. What you need is a prototype/request scoped bean like ExternalData or EnvironmentInfo which can read and hold runtime data through a static way, let's say a static factory method.
<bean id="externalData" class="ExternalData"
factory-method="read" scope="prototype"></bean>
<bean id="environmentInfo" class="EnvironmentInfo"
factory-method="read" scope="prototype/singleton"></bean>
<bean class="RuntimeBean" scope="prototype">
<property name="externalData" ref="externalData">
<property name="environmentInfo" ref="environmentInfo">
</bean>
If you do need a container to save the runtime objects, code should be
class Container {
List list;
ApplicationContext context;//injected by spring if Container is not a prototype bean
public void load() {// no loop inside, each time call load() will load a runtime object
RuntimeBean bean = context.getBean(RuntimeBean.class); // see official doc
list.add(bean);// do whatever
}
}
Official doc Singleton beans with prototype-bean dependencies.
It is possible to register beans dynamically by using BeanFactoryPostProcesor. Here you can do that while the application is booting (spring's application context has been initialized). You can not register beans latest, but on the other hand, you can make use of dependency injection for your beans, as they become "true" Spring beans.
public class DynamicBeansRegistar implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (! (beanFactory instanceof BeanDefinitionRegistry)) {
throw new RuntimeException("BeanFactory is not instance of BeanDefinitionRegistry");
}
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
// here you can fire your logic to get definition for your beans at runtime and
// then register all beans you need (possibly inside a loop)
BeanDefinition dynamicBean = BeanDefinitionBuilder.
.rootBeanDefinition(TheClassOfYourDynamicBean.class) // here you define the class
.setScope(BeanDefinition.SCOPE_SINGLETON)
.addDependsOn("someOtherBean") // make sure all other needed beans are initialized
// you can set factory method, constructor args using other methods of this builder
.getBeanDefinition();
registry.registerBeanDefinition("your.bean.name", dynamicBean);
}
#Component
class SomeOtherClass {
// NOTE: it is possible to autowire the bean
#Autowired
private TheClassOfYourDynamicBean myDynamicBean;
}
As presented above, you can still utilize Spring's Dependency Injection, because the post processor works on the actual Application Context.

Categories

Resources