How to access spring boot properties from freemarker template - java

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>

Related

Spring MVC: configure properties from before bean creation

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;
}
}

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.

Returning Singleton beans depending on input in Spring

Let's say I have the following:
#Component
#NoArgsConstructor
public class ToolFactory {
public Tool getTool(String type) {
return StaticToolProvider.getTool(type);
}
}
This class will be injected elsewhere and called like this:
Tool screwdriver = ToolFactory.getTool("screwdriver")
If the tools can be screwdriver, hammer, or wrench, I want Spring to create Singleton beans for each, and return them when getTool() is called. I believe #Provides #Singleton would do this in Guice, but how could I do it here?
I would create map with all needed beans. Map will be singleton by default and unmodifiable to prevent anyone changing it by mistake. If you need hammer to be spring bean just autowire it, if no you can just use regular object creation using new
#Configuration
class Config {
#Bean
Map<String, Tool> tools(Hammer hammer) {
Map<String, Tool> map = new HashMap<>();
map.put("hammer", hammer);
//map.put("hammer",new Hammer())
return Collections.unmodifiableMap(map);
}
}
#Component
public class Container {
private Map<String, Tool> tools;
#Autowired
public Container(Map<String, Tool> tools) {
this.tools = tools;
}
Tool getTool(String tool) {
return tools.get(tool);
}
}

Loading Beans based on hostname

I am writing services in Spring boot that get their configurations from Spring cloud. These services are multi-tenant and the tenant is based on the host name.
what I have now is
public class MyController {
#Autowired
public MyController(MyServiceFactory factory) {
...
}
#RequestMapping("some/path/{id}")
ResponseEntity<SomeEntity> getSomeEntity(#RequestHeader header, #PathVariable id) {
return factory.getMyService(header).handle(id);
}
}
where MyServiceFactory looks something like...
public class MyServiceFactory {
private final HashMap<String, MyService> serviceRegistry = new HashMap<>();
public MyService getMyService(String key) {
return serviceRegistry.get(key);
}
MyServiceFactory withService(String key, MyService service) {
this.serviceRegistry.put(key, service);
return this;
}
}
then in a configuration file
#Configuration
public ServiceFactoryConfiguration {
#Bean
public MyServiceFactory getMyServiceFactory() {
return new MyServiceFactory()
.withService("client1", new MyService1())
.withService("client2", new MyService2());
}
}
While what I have now works, I don't like that I need to create a factory for every dependency my controller may have. I'd like to have my code look something like this...
public class MyController {
#Autowired
public MyController(MyService service) {
...
}
#RequestMapping("some/path/{id}")
ResponseEntity<SomeEntity> getSomeEntity(#PathVariable id) {
return service.handle(id);
}
}
with a configuration file like
#Configuration
public class MyServiceConfiguration() {
#Bean
#Qualifier("Client1")
public MyService getMyService1() {
return new MyService1();
}
#Bean
#Qualifier("Client2")
public MyService getMyService2() {
return new MyService2();
}
}
I can get the code that I want to write if I use a profile at application start up. But I want to have lots of different DNS records pointing to the same (pool of) instance(s) and have an instance be able to handle requests for different clients. I want to be able to swap out profiles on a per request basis.
Is this possible to do?
Spring profiles would not help here, you would need one application context per client, and that seems not what you want.
Instead you could use scoped beans.
Create your client dependent beans with scope 'client' :
#Bean
#Scope(value="client",proxyMode = ScopedProxyMode.INTERFACES)
#Primary
MyService myService(){
//does not really matter, which instance you create here
//the scope will create the real instance
//may be you can even return null, did not try that.
return new MyServiceDummy();
}
There will be at least 3 beans of type MyService : the scoped one, and one for each client. The annotation #Primary tells spring to always use the scoped bean for injection.
Create a scope :
public class ClientScope implements Scope {
#Autowired
BeanFactory beanFactory;
Object get(String name, ObjectFactory<?> objectFactory){
//we do not use the objectFactory here, instead the beanFactory
//you somehow have to know which client is the current
//from the config, current request, session, or ThreadLocal..
String client=findCurrentClient(..);
//client now is something like 'Client1'
//check if your cache (HashMap) contains an instance with
//BeanName = name for the client, if true, return that
..
//if not, create a new instance of the bean with the given name
//for the current client. Easiest way using a naming convention
String clientBeanName=client+'.'+name;
Object clientBean=BeanFactory.getBean(clientBeanName);
//put in cache ...
return clientBean;
};
}
And your client specific beans are configured like this :
#Bean('Client1.myService')
public MyService getMyService1() {
return new MyService1();
}
#Bean('Client2.myService')
public MyService getMyService2() {
return new MyService2();
}
Did not test it but used it in my projects. Should work.
tutorial spring custom scope

IOC containers: de-duplicating the configuration code

I am using spring framework for 2 different applications. Let's say both of the applications talk to one single MongoDB database. Following is how I configure MongoDB in both the applications:
#Configuration
#PropertySource("file:/etc/x/y/mongodb.properties")
public class MongoConfiguration {
#Autowired
private Environment env;
#Bean
public UserCredentials mongoCredentials() {
String mongoUserName = env.getProperty("mongodb.username");
String mongoPassword = env.getProperty("mongodb.password");
UserCredentials credentials = new UserCredentials(mongoUserName, mongoPassword);
return credentials;
}
#Bean
public MongoClient mongoClient() throws Exception {
String mongoUrl = env.getProperty("mongodb.url");
String mongoPort = env.getProperty("mongodb.port");
MongoClient mongo = new MongoClient(mongoUrl, Integer.valueOf(mongoPort));
return mongo;
}
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate() throws Exception {
String mongoDatabaseName = env.getProperty("mongodb.databasename");
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), mongoDatabaseName, mongoCredentials());
return mongoTemplate;
}
Now, this piece of code is duplicated in two different application configurations. How do I avoid doing this configuration at two different places?
Treat it the same as a util class that you don't want to duplicate: move you config file to a separate project and make both your applications include that projects.
If you need to add additional project-specific configuration, Spring provides the #Import annotation that allows you to import configuration from separate classes, so you can create two project specific configuration classes that both import the generic configuration from the shared lib and supply their own individual beans and property sources, e.g.:
#Configuration
#PropertySource("classpath:/com/appspecific/app.properties")
#Import(com.genericlib.BaseConfig.class)
public class AppConfig {
#Inject BaseConfig baseConfig;
#Bean
public MyBean myBean() {
// reference the base config context
return new MyBean(baseConfig.getSomething());
}
}
Use Spring Boot, and optionally include the #PropertySource to add to the environment. It will collect all the MongoDB information and configure a client and template for you.

Categories

Resources