Is there a way in a spring boot 1 application to know the effective properties used by a Spring Boot application versus all properties setted up with YAML, system properties...
Ideally I would like to detect that in an ApplicationListener class and prevent the application to start if there are any obsolete properties that we maintain in our framework.
Thx by advance for your help,
Eric
What I did when I had the same requirement was creating my own PropertyPlaceholderConfigurer:
public class DisplayablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final Logger log = LoggerFactory.getLogger(DisplayablePropertyPlaceholderConfigurer.class);
private int processedNum;
private HashSet<String> propertyNames = new HashSet<>();
#Override
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
super.doProcessProperties(beanFactoryToProcess, valueResolver);
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
return super.resolvePlaceholder(placeholder, props, systemPropertiesMode);
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
propertyNames.add(placeholder);
return super.resolvePlaceholder(placeholder, props);
}
#Override
protected String resolveSystemProperty(String key) {
return super.resolveSystemProperty(key);
}
public HashSet<String> getPropertyNames() {
return propertyNames;
}
}
You could then register some CommandLineListener or ApplicationEvent listener to call getPropertyNames() on application startup.
After that you will get a list of used properties. It's a good point to start, isn't it? You can sort both lists and compare to filter out properties that are not used.
Related
I have an old code base that I need to refactor using Java 8, so I have an interface, which tells whether my current site supports the platform.
public interface PlatformSupportHandler {
public abstract boolean isPaltformSupported(String platform);
}
and I have multiple classes implementing it and each class supports a different platform.
A few of the implementing classes are:
#Component("bsafePlatformSupportHandler")
public class BsafePlatoformSupportHandler implements PlatformSupportHandler {
String[] supportedPlatform = {"iPad", "Android", "iPhone"};
Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform));
#Override
public boolean isPaltformSupported(String platform) {
return supportedPlatformSet.contains(platform);
}
}
Another implementation:
#Component("discountPlatformSupportHandler")
public class DiscountPlatoformSupportHandler implements PlatformSupportHandler{
String[] supportedPlatform = {"Android", "iPhone"};
Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform));
#Override
public boolean isPaltformSupported(String platform) {
return supportedPlatformSet.contains(platform);
}
}
At runtime in my filter, I get the required bean which I want:
platformSupportHandler = (PlatformSupportHandler) ApplicationContextUtil
.getBean(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);
and call isPlatformSupported to get whether my current site supports the following platform or not.
I am new to Java 8, so is there any way I can refactor this code without creating multiple classes? As the interface only contains one method, can I somehow use lambda to refactor it?
If you want to stick to the current design, you could do something like this:
public class MyGeneralPurposeSupportHandler implements PlatformSupportHandler {
private final Set<String> supportedPlatforms;
public MyGeneralPurposeSupportHandler(Set<String> supportedPlatforms) {
this.supportedPlatforms = supportedPlatforms;
}
public boolean isPlatformSupported(String platform) {
return supportedPlatforms.contains(platform);
}
}
// now in configuration:
#Configuration
class MySpringConfig {
#Bean
#Qualifier("discountPlatformSupportHandler")
public PlatformSupportHandler discountPlatformSupportHandler() {
return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone"})); // yeah its not a java syntax, but you get the idea
}
#Bean
#Qualifier("bsafePlatformSupportHandler")
public PlatformSupportHandler bsafePlatformSupportHandler() {
return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone", "iPad"}));
}
}
This method has an advantage of not creating class per type (discount, bsafe, etc), so this answers the question.
Going step further, what happens if there no type that was requested, currently it will fail because the bean does not exist in the application context - not a really good approach.
So you could create a map of type to the set of supported platforms, maintain the map in the configuration or something an let spring do its magic.
You'll end up with something like this:
public class SupportHandler {
private final Map<String, Set<String>> platformTypeToSuportedPlatforms;
public SupportHandler(Map<String, Set<String>> map) {
this.platformTypeToSupportedPlatforms = map;
}
public boolean isPaltformSupported(String type) {
Set<String> supportedPlatforms = platformTypeToSupportedPlatforms.get(type);
if(supportedPlatforms == null) {
return false; // or maybe throw an exception, the point is that you don't deal with spring here which is good since spring shouldn't interfere with your business code
}
return supportedPlatforms.contains(type);
}
}
#Configuration
public class MyConfiguration {
// Configuration conf is supposed to be your own way to read configurations in the project - so you'll have to implement it somehow
#Bean
public SupportHandler supportHandler(Configuration conf) {
return new SupportHandler(conf.getRequiredMap());
}
}
Now if you follow this approach, adding a new supported types becomes codeless at all, you only add a configuration, by far its the best method I can offer.
Both methods however lack the java 8 features though ;)
You can use the following in your config class where you can create beans:
#Configuration
public class AppConfiguration {
#Bean(name = "discountPlatformSupportHandler")
public PlatformSupportHandler discountPlatformSupportHandler() {
String[] supportedPlatforms = {"Android", "iPhone"};
return getPlatformSupportHandler(supportedPlatforms);
}
#Bean(name = "bsafePlatformSupportHandler")
public PlatformSupportHandler bsafePlatformSupportHandler() {
String[] supportedPlatforms = {"iPad", "Android", "iPhone"};
return getPlatformSupportHandler(supportedPlatforms);
}
private PlatformSupportHandler getPlatformSupportHandler(String[] supportedPlatforms) {
return platform -> Arrays.asList(supportedPlatforms).contains(platform);
}
}
Also, when you want to use the bean, it is again very easy:
#Component
class PlatformSupport {
// map of bean name vs bean, automatically created by Spring for you
private final Map<String, PlatformSupportHandler> platformSupportHandlers;
#Autowired // Constructor injection
public PlatformSupport(Map<String, PlatformSupportHandler> platformSupportHandlers) {
this.platformSupportHandlers = platformSupportHandlers;
}
public void method1(String subProductType) {
PlatformSupportHandler platformSupportHandler = platformSupportHandlers.get(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);
}
}
As it was written in Mark Bramnik's answer you can move this to configuration.
Suppose that it would be in yaml in that way:
platforms:
bsafePlatformSupportHandler: ["iPad", "Android", "iPhone"]
discountPlatformSupportHandler: ["Android", "iPhone"]
Then you can create config class to read this:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
public class Config {
private Map<String, List<String>> platforms = new HashMap<String, List<String>>();
// getters and setters
You can than create handler with checking code.
Or place it in your filter like below:
#Autowired
private Config config;
...
public boolean isPlatformSupported(String subProductType, String platform) {
String key = subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND;
return config.getPlatforms()
.getOrDefault(key, Collections.emptyList())
.contains(platform);
}
I am trying configuring multiple couchbase data source using springboot-data-couchbase.
This is a way I tried to attach two couchbase sources with 2 repositories.
#Configuration
#EnableCouchbaseRepositories("com.xyz.abc")
public class AbcDatasource extends AbstractCouchbaseConfiguration {
#Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("ip_address_of_couchbase");
}
//bucket_name
#Override
protected String getBucketName() {
return "bucket_name";
}
//password
#Override
protected String getBucketPassword() {
return "user_password";
}
#Override
#Bean(destroyMethod = "disconnect", name = "COUCHBASE_CLUSTER_2")
public Cluster couchbaseCluster() throws Exception {
return CouchbaseCluster.create(couchbaseEnvironment(), "ip_address_of_couchbase");
}
#Bean( name = "BUCKET2")
public Bucket bucket2() throws Exception {
return this.couchbaseCluster().openBucket("bucket2", "somepassword");
}
#Bean( name = "BUCKET2_TEMPLATE")
public CouchbaseTemplate newTemplateForBucket2() throws Exception {
CouchbaseTemplate template = new CouchbaseTemplate(
couchbaseClusterInfo(), //reuse the default bean
bucket2(), //the bucket is non-default
mappingCouchbaseConverter(), translationService()
);
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
#Override
public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) {
baseMapping
.mapEntity(SomeDAOUsedInSomeRepository.class, newTemplateForBucket2());
}
}
similarly:
#Configuration
#EnableCouchbaseRepositories("com.xyz.mln")
public class MlnDatasource extends AbstractCouchbaseConfiguration {...}
Now the problem is there is no straight forward way to specify namespace based datasource by attaching different beans to these configurations like in springdata-jpa as springdata-jpa support this feature do using entity-manager-factory-ref and transaction-manager-ref.
Due to which only one configuration is being picked whoever comes first.
Any suggestion is greatly appreciated.
Related question: Use Spring Data Couchbase to connect to different Couchbase clusters
#anshul you are almost there.
Make one of the Data Source as #primary which will be used as by default bucket.
Wherever you want to use the other bucket .Just use specific bean in your service class with the qualifier below is the example:
#Qualifier(value = "BUCKET1_TEMPLATE")
#Autowired
CouchbaseTemplate couchbaseTemplate;
Now you can use this template to perform all couch related operations on the desired bucket.
I know the default order of spring's properties sources: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
how can i add my own property source with a specific precedence?
#PropertySource is not enough as it adds new properties with very low priority
There are plenty of ways to do this; I'll just quote the official documentation:
A SpringApplication has ApplicationListeners and ApplicationContextInitializers that are used to apply customizations to the context or environment. Spring Boot loads a number of such customizations for use internally from META-INF/spring.factories. There is more than one way to register additional ones:
Programmatically per application by calling the addListeners and addInitializers methods on SpringApplication before you run it.
Declaratively per application by setting context.initializer.classes or context.listener.classes.
Declaratively for all applications by adding a META-INF/spring.factories and packaging a jar file that the applications all use as a library.
The SpringApplication sends some special ApplicationEvents to the listeners (even some before the context is created), and then registers the listeners for events published by the ApplicationContext as well. See Section 23.4, “Application events and listeners” in the ‘Spring Boot features’ section for a complete list.
It is also possible to customize the Environment before the application context is refreshed using EnvironmentPostProcessor. Each implementation should be registered in META-INF/spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor
My way was always to add an ApplicationEnvironmentPreparedEvent listener:
public class IntegrationTestBootstrapApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 4;
public static final String PROPERTY_SOURCE_NAME = "integrationTestProps";
private int order = DEFAULT_ORDER;
public void setOrder(int order) {
this.order = order;
}
#Override
public int getOrder() {
return this.order;
}
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
Map<String, Object> properties = ...; // generate the values
// use whatever precedence you want - first, last, before, after
environment.getPropertySources().addLast(
new MapPropertySource(PROPERTY_SOURCE_NAME, properties));
}
}
}
But you can just as easily use an initializer:
public class IntegrationTestBootstrapApplicationListener implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String PROPERTY_SOURCE_NAME = "integrationTestProps";
#Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> properties = ...; // generate the values
// use whatever precedence you want - first, last, before, after
environment.getPropertySources().addLast(
new MapPropertySource(PROPERTY_SOURCE_NAME, properties));
}
}
I am used to Spring on Tomcat/Jetty and I now work on an existing JAX-RS project running on WildFly (RESTEasy).
I would like to know where do the application/deployment property files go on WildFly, standalone/configuration/myapp.properties?
Then how does the application load them? I tried in our class extending javax.ws.rs.core.Application:
#javax.ws.rs.ApplicationPath("")
public class ApplicationConfig extends Application {
#Override
public Map<String, Object> getProperties() {
System.out.println(">>>>>>>>>>>>>>>> get properties");
// I added this method but nothing is printed...
}
#Override
public Set<Class<?>> getClasses() {
System.out.println(">>>>>>>>>>>>>>>> get classes");
// This is printed
...
// classes are loaded correctly
}
}
Then how would I access the properties in the controllers? By the way we don't use dependency injection.
Thanks!
Some Investigation...
Normally what should work
The getProperties() should be called on startup to load any required application properties.
You should be able to inject javax.ws.rs.core.Configuration into your resource classes (with #Context) and retrieve properties through that object. This is stated in the javadoc
This interface can be injected using the Context annotation.
Test
#ApplicationPath("/api")
public class RestApplication extends Application {
#Override
public Map<String, Object> getProperties() {
System.out.println(">>>>>>>>>>>>>>>> get properties");
Map<String, Object> props = new HashMap<>();
props.put("message", "Hello Configuration Properties!");
return props;
}
}
#Path("config")
public class ConfigResource {
#Context
private Configuration configuration;
#GET
public Response getProperty(#QueryParam("prop") String prop) {
String propValue = (String)configuration.getProperty(prop);
return Response.ok(propValue).build();
}
}
Discoveries
The above doesn't work from what I tested with Resteasy 3.0.9.Final. I get some error about no context for this type. I don't know why. Might be a bug, I don't know. Maybe something you can look into.
The above works fine with Jersey 2.16
What works with Resteasy
What I could get to work with Resteasy is to inject Application (as mentioned here into the resource (also with #Context) and get the properties that way.
#Path("config")
public class ConfigResource {
#Context
Application application;
#GET
public Response getProperty(#QueryParam("prop") String prop) {
String propValue = (String)application.getProperties().get(prop);
return Response.ok(propValue).build();
}
}
Working with Java Spring, how can I overwrite the default behavior of the property-placeholders to return 'foo' for any property?
The current path I'm going down is to extend PropertySource as follows:
public class FooPropertySource extends PropertySource<Object> {
private static final String DEFAULT_NAME = "foo";
public FooPropertySource() {
super(DEFAULT_NAME, null);
}
#Override
public Object getProperty(String name) {
return "foo";
}
}
At this point, I have two questions:
A) What do I do with my application Context XML file? As of now, I've defined this as a bean...That's about it.
B) Do I have to do anything in code to load other beans from my application context, such that they will use the FooPropertySource?
Thanks
You will have to register this PropertySource to be added to your application context. If you manually starting up your application context, you can do this:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocation("applicationContext.xml");
ctx.getEnvironment().getPropertySources().addLast(new FooPropertySource());
ctx.refresh();
If you are doing this in a web environment, you will have to register a custom ApplicationContextInitializer to intercept the application context before it is refreshed to inject in your PropertySource:
public class CustomInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
public void initialize(ConfigurableWebApplicationContext ctx) {
ctx.getEnvironment().getPropertySources().addLast(new FooPropertySource());
}
}
More details here