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

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.

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.

How to access Configuration objects in Spring Boot

Spring Boot has a mechanism for accessing the contents of .properties (or YAML) files that one might want to include in an application.
I currently have a dbase.properties file (residing in src/main/resources) that contains the following information:
app.dbase.name=MyDbase
app.dbase.connect=jdbc:postgresql://localhost:5432
app.dbase.user=auser
app.dbase.password=mypassword
As described in various Spring Boot documents and examples, I have a configuration class that is defined below:
#Configuration
#PropertySource("dbase.properties")
#ConfigurationProperties(prefix = "app.dbase")
public class DbInfo
{
private String name;
private String connect;
private String user;
private String password;
// Getters and setters left out for brevity
}
Unfortunately, while the various documents and examples give good information on how to define a configuration
class, I have been unable to find any description on how to use it! Apparently, a Spring Boot web application
creates an instance of a configuration class upon startup (and it looks like it also initializes them with the
values from the properties file) but my attempts to guess how to access its contents when I need to have failed.
The method of doing so is probably simple, but no one seems to want to describe this method anywhere.
So: how does one access and use one of these configuration classes once they are instantiated?
Note that #ConfigurationProperties would require all of the properties in your file to be prefixed with 'app.dbase', as in 'app.dbase.username' and 'app.dbase.password'. If that's the case, the class you have now should work.
You would call it like this:
#Component
public class Component {
#Autowired DbInfo dbInfo;
public method() {
String username = dbInfo.username();
}
}
If you are having issues, you may be required to add this to a Configuration class:
#Configuration
public class Config {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
...
}
You may also need to add 'classpath:' inside your annotation, as in: #PropertySource("classpath:dbase.properties"), assuming your properties file is in your src/main/resources.

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

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.

How to configure a custom source to feed Spring Boot's #ConfigurationProperties

I'm new to Spring Boot and reading about how #ConfigurationProperties annotation enables auto-injection of field values without #Value annotation.
#Configuration
#ConfigurationProperties(locations = "classpath:some.properties", prefix = "something")
public class MyConfiguration { .. }
I'd like to use Groovy's ConfigSlurper to read my property configuration. Is there a way to associate #ConfigurationProperties with a custom property reader, may be a custom extension of Spring class that deals with ConfigSlurper? Or is there a way to simulate the same behavior with a different feature?
That's not what #ConfigurationProperties is meant to do. #ConfigurationProperties binds whatever is available from the Environment. The locations attribute is deprecated in 1.4 and will be removed in a future release.
The idea is that you specify a prefix and if they are keys matching that prefix in the environment, we inject the relevant properties in your POJO.If you want to use that infrastructure with this mechanism, please remove the locations attribute on the annotation and update the environment with your own property source. The other answer gives you a way to do that and you can use an EnvironmentPostProcessor to hook your implementation to the environment.
You can do so by implementing your own PropertySourceLoader:
public class ConfigSlurperPropertySourceLoader implements PropertySourceLoader {
#Override
public String[] getFileExtensions() {
return new String[] { "groovy" };
}
#Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
ConfigObject source = new ConfigSlurper(profile).parse(resource.getURL());
return new ConfigObjectPropertySource(name, source);
}
}
Extending PropertySource<T> to read values from ConfigObject (the ConfigObjectPropertySource above). Then you register it inside META-INF/spring.factories:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.example.ConfigSlurperPropertySourceLoader,\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
The spring-groovy-config already implements it and is available on github.

Spring Bean Extensions

I have a Spring MVC project with a generic application context xml file. This file defines the generic configuration of my applciation such as the base property file for i18n and data source to connect to the database and so on. While i define this context file i also want to define the session factory which will have base configurations such as the data source to use, the second level caching (eh-cache) and so on. But this will not contain the list of entity beans that my application would load. I want to keep the mapping of the entity beans only in separate file and load them based on need.
Is there a possibility to extend the session factory that i had defined in the base file and only add the additional entity beans? I will eventually have several spring configuration files which will load a separate set of entities. can this be achieved?
There are several posibilities.
You can use PropertyPlaceHolderConfigurer to externalize the entity list to a property file. (You can use SPEL in the property file).
You can use an abstract bean definition and use it as parent in other sessionFactory beans, then you can import thems based on a Enviroment PropertySource.
Note that Hibernate SessionFactory is inmutable after building it and SessionFactoryBean build SessionFactory in afterPropertiesSet method so the work of setting up the SessionFactoryBean that you want must be done by some BeanFactoryPostProcessor
EDIT
After reading your comment, I think that you could declare a EntityClassHolder bean and use the Autowire collections facility to get all entities in a EntityClassFactoryBean that you can inject in a single SessionFactoryBean. But i don't sure if that is that you want to do:
public class EntityClassHolder {
List<Class<?>> entityClasses;
public List<Class<?>> getEntityClasses() {
return entityClasses;
}
public void setEntityClasses(List<Class<?>> entityClasses) {
this.entityClasses = entityClasses;
}
}
public class EntityClassFactoryBean extends AbstractFactoryBean<List<Class<?>>> {
#Autowired
List<EntityClassHolder> list;
#Override
public Class<?> getObjectType() {
return List.class;
}
#Override
protected List<Class<?>> createInstance() throws Exception {
ArrayList<Class<?>> classList = new ArrayList<Class<?>>();
for (EntityClassHolder ech : list) {
classList.addAll(ech.getEntityClasses());
}
return classList;
}
}
Now, if you have several applicatonContext-xxx.xml for example, the SessionFactory will be configured with entity classes definied in EntityClassHolder beans when you load one of them.

Resources