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.
Related
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.
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
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.
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.
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.