How to make globally shared objects available to freemarker templates in Spring Boot 2 - spring-boot

What is the best way of making global shared objects available to freemarker templates when using Spring Boot 2.x, without losing Spring Boot's FreeMarker auto configuration?
The underlying mechanism for doing this is Spring Boot's FreeMakerConfigurer.setFreemarkerVariables, which in turn calls FreeMarker's Configuration.setAllSharedVariables
However, there is no obvious way (to me) to modify the FreeMarkerConfigurer that is setup by FreeMarkerServletWebConfiguration beyond the predefined freemarker properties that Spring Boot supports. (Search for "freemarker" here).
A common approach is to create a custom FreemarkerConfigurer bean, but I believe that then loses some of the auto configuration provided by spring boot, especially around the handling of various external properties.
One option that seems to work is to use a BeanPostProcessor like this:
public class CustomFreeMarkerConfig implements BeanPostProcessor {
Object sharedWithAllFreeMarkerTemplatesObj = new Object();
#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("obj", sharedWithAllFreeMarkerTemplatesObj);
configurer.setFreemarkerVariables(sharedVariables);
}
return bean;
}
}
It seems like there should be a cleaner way of doing it, perhaps by somehow extending or configuring FreeMarkerConfigurationFactory, but I haven't been able to find it.

I found a solution from spring git
Spring Boot 2.0 breaks the solution provided by #wo8335224, as FreeMarkerWebConfiguration is replaced by FreeMarkerServletWebConfiguration, which is unfortunately package-private and thus cannot be subclassed.
A currently working solution is to configure freemarker.template.Configuration bean:
#Configuration
public class FreemarkerConfig {
public FreemarkerConfig(freemarker.template.Configuration configuration) throws TemplateModelException {
configuration.setSharedVariable("name", "whatever type of value");
}
}
Internally FreeMarkerConfigurer#setFreemarkerVariables delegates its work to freemarker.template.Configuration#setAllSharedVariables.

Related

Programmatic RedissonClient in Spring boot project

I am trying to implement Hibernate second level caching in a Spring boot project using Redisson.
I have followed this blog as a reference
https://pavankjadda.medium.com/implement-hibernate-2nd-level-cache-with-redis-spring-boot-and-spring-data-jpa-7cdbf5632883
Also i am trying to initialize the RedissionClient programmatically and not through declaratively /through a config file
Created a spring bean to be initialized which should create the RedissonClient instance.
#Configuration
#Lazy(value = false)
public class RedissonConfig {
#Bean
public RedissonClient redissionClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
However this bean is never intialized and i get the following error while application startup.
Caused by: org.hibernate.cache.CacheException: Unable to locate Redisson configuration
at org.redisson.hibernate.RedissonRegionFactory.createRedissonClient(RedissonRegionFactory.java:107) ~[redisson-hibernate-53-3.12.1.jar:3.12.1]
at org.redisson.hibernate.RedissonRegionFactory.prepareForUse(RedissonRegionFactory.java:83) ~[redisson-hibernate-53-3.12.1.jar:3.12.1]
It seems Spring boot Hibernate still trying to load the Redisson config through a config file.
is it possible to load the Redission config in spring boot programmatically ?
Best Regards,
Saurav
I just did exactly this, here is how:
you need a custom RegionFactory that is similar to the JndiRedissonRegionFactory but gets its RedissonClient injected somehow.
an instance of this Class, fully configured, is put into the hibernate-properties map. Hibernates internal code is flexible: if the value of hibernate.cache.region.factory_class is a string it is treated as a FQDN. If it is an instance of Class<?>, it will be instantiated. If it is an Object, it will be used.
Spring offers a rather simple way to customize hibernate properties with a bean:
#AutoConfiguration(after = RedissonAutoConfiguration.class, before = JpaAutoConfiguration.class)
#ConditionalOnProperty("spring.jpa.properties.hibernate.cache.use_second_level_cache")
public class HibernateCacheAutoConfiguration {
#Bean
public HibernatePropertiesCustomizer setRegionFactory(RedissonClient redisson) {
return hibernateProperties -> hibernateProperties.put(AvailableSettings.CACHE_REGION_FACTORY, new SpringBootRedissonRegionFactory(redisson));
}
}
My RegionFactory is really simple:
#AllArgsConstructor
public class SpringBootRedissonRegionFactory extends RedissonRegionFactory {
private RedissonClient redissonClient;
#Override
protected RedissonClient createRedissonClient(Map properties) {
return redissonClient;
}
#Override
protected void releaseFromUse() {
}
}
I used the redisson-starter to get a RedissonClient, hence the reference to RedissonAutoConfiguration, but you could just create an instance by hand.
It is possible, but then you need to provide a custom implementation of RegionFactory to Hibernate, which can extends RedissonRegionFactory but uses your own client instance.

Spring Boot 2.1.4: #Autowired does not work in custom Jackson Serializers/Deserializers, how to enable it?

I am struggeling to get a Spring Component #Autowired into my custom Deserializer.
Example:
#JsonDeserialize (using = SomeClassJsonDeserializer.class)
SomeClass {
[...]
}
#JsonComponent
SomeClassJsonDeserializer extends JsonDeserializer<SomeClass> {
#Autowired
private SomeService service;
#Override
public SomeClass deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
[...]
// this.service is null
}
}
I found mainly two possible solutions which didn't work for me at all:
use SpringBeanAutowiringSupport in default constructor of Deserializer
use HandlerInstantiator (via config / custom implementation)
I am using only those Jackson annotations shown in the example above to 'configure' the Jackson parsing.
There is no additional custom configuration affecting Jackson in any way besides the default SpringBoot auto configuration. When using #EnableWebMvc (which breaks Spring-Boot auto configuration so I don't want to use it), the Component-wiring does work as expected.
Is there any official / recommended solution for plain Spring-Boot with default auto configuration ?
The problem was with how I used Spring's RestTemplate.
For a remote call, I created a new Instance of RestTemplate by contructor call (new RestTemplate()).
This way, Spring wasn't able to configure the RestTemplate - bean correctly (so that SpringBoot autoconfigure and Jackson autoconfigure 'connect' together, resulting in working Spring-DI in custom Jackson components).
I simply had to #Autowire the RestTemplateBuilder bean instance provided by Spring, and then call RestTemplateBuilder.build() to aqquire a RestTemplate bean instance created by Spring.

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

How are components managed in Spring MVC and how to inject a customized component in Spring 3.2.5?

I’m considering to replace the DefaultSessionAttributeStore implementation of Spring MVC 3.2.5 with some class of my own, and I’ve known from the source code that in my 3.2.5 spring source, it’s SessionAttributesHandler which possesses a SessionAttributeStore interface reference and invokes the session store function. My question is how to replace that by DI? The SessionAttributesHandler holds a final private sessionAttributeStore reference and can only be set by the constructor:
public class SessionAttributesHandler {
...
private final SessionAttributeStore sessionAttributeStore;
...
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
this.sessionAttributeStore = sessionAttributeStore;
SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
if (annotation != null) {
this.attributeNames.addAll(Arrays.asList(annotation.value()));
this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
}
for (String attributeName : this.attributeNames) {
this.knownAttributeNames.put(attributeName, Boolean.TRUE);
}
}
...
}
Are all the components of spring mvc managed in the spring DI container? How to inject my own SessionAttributeStore implementation into SessionAttributesHandler? What does the "Class handlerType" argument mean in the constructor? From source, it seems like it's the "controller" class. Since SessionAttributesHandler is invoked and held by a ModelFactory, and in ModelFactory there is no code instantiating the SessionAttributesHandler, is there any "XML" bean configuration file for the Spring MVC inner components and how to overwrite them?
If you want to provide your own implementation of a SessionAttributeStore you need to manually configure the RequestMappingHandlerAdapter and set your custom implementation on there. That will take care of using it through-out the rest of the infrastructure.
Assuming that you use java config you can do the following
#Configuration
public class MyConfiguration extend WebMvcConfigurationSupport {
#Bean
public SessionAttributeStore sessionAttributeStore() {
return new MyCustomSessionAttributeStore();
}
#Override
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter rmha = super.requestMappingHandlerAdapter();
rmha.setSessionAttributeStore(sessionAttributeStore());
return rmha;
}
}
If you want to do this in XML you either have to write a BeanPostProcessor which sets it on the default RequestMappingHandlerAdapter instance created by <mvc:annotation-driven /> or configure it manually and drop the namespace support.

How to use properties in a Spring project to configure log4j.xml

I have multiple properties files in my Spring project. The spring context loads these properties and handles property overriding in a convenient manner. Is there a way to take the properties that are available to my Spring configuration XML files (ie. ${myprop}) and use them in a similar fashion in my log4j.xml file? I know that I can pass system properties to log4j using -Dprop=value on startup, but I would prefer having all of the configuration in the properties files in my project. Is this possible?
My app runs in Tomcat.
Try to use this class, after integrating your multiple properties files to one Properties.
public class DOMConfiguratorWithProperties extends DOMConfigurator {
private Properties propertiesField = null;
public synchronized Properties getProperties() {
return propertiesField;
}
public synchronized void setProperties(final Properties properties) {
propertiesField = properties;
}
#Override
protected String subst(final String value) {
return super.subst(value, getProperties());
}
public static void configure(final String filename) {
new DOMConfiguratorWithProperties().doConfigure(
filename,
LogManager.getLoggerRepository());
}
public static void configure(
final String filename,
final Properties properties) {
DOMConfiguratorWithProperties configurator = new DOMConfiguratorWithProperties();
configurator.setProperties(properties);
configurator.doConfigure(
filename,
LogManager.getLoggerRepository());
}
}
I think the only way you can interact with Log4J is through the Nested diagnostic context.
So if you are very desperate, you could write a Spring AOP aspect that sets the diagnostic context for your Spring Bean logs (and you could use the properties there). However, that would require that the Log be available to the Spring Bean, so you would need to add a getLog() method to your service interfaces (this gets a lot easier if you use static AspectJ compilation and is described in AspectJ in Action).
But short of using AOP, I can't think of a sensible way to let Spring and Log4J interact.

Resources