How can an ContextCustomizer access Springs environment properties? - java

I am implementing a custom Spring context customizer, as I have to perform some operations during startup of the application. The result of the operation is need to configure the datasource I need in my application.
My problem is now, that I need for those operations access to my configuration properties (from application.yaml), as they are the base for my operations.
My, simplified, implementation looks currently like this. Nothing special.
public class MyContextCustomizerFactory
implements ContextCustomizerFactory {
#Target(TYPE) #Retention(RUNTIME)
#Documented #Inherited
public #interface EnabledPostgresTestContainer {
}
#Override
public ContextCustomizer createContextCustomizer(Class<?> c,
List<ContextConfigurationAttributes> a) {
}
static class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext c,
MergedContextConfiguration mc) {
}
}
}
Of is there an alternatvie approach. Using Springs DynamicPropertySource is currently not an option.

Not sure ContextCustomizerFactory is what you are looking for, because you are talking about "application" but ContextCustomizerFactory is designed for running tests, anyway...
What exactly has confused you?
public class MyContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return new MyContextCustomizer();
}
}
public class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
ConfigurableEnvironment environment = context.getEnvironment();
// reading properties
String applicationName = environment.getProperty("application.name");
// enriching properties
Properties jdbcProperties = new Properties();
jdbcProperties.put("spring.datasource.url", "jdbc://....");
environment.getPropertySources().addFirst(
new PropertiesPropertySource("customizerProperties", jdbcProperties)
);
}
}
UPD.
If the goal is to modify env/properties after Spring has parsed #Configuration classes with #PropertySource we may use BeanFactoryPostProcessor, below are some examples from spring:
EmbeddedDataSourceBeanFactoryPostProcessor - very similar to what TC needs
PropertySourceOrderingPostProcessor - reorders property sources
PropertyOverrideConfigurer

Related

Spring create generic service multiple times using generic in constructor

I have a service that uses some object as a generic
#Component
#RequiredArgsConstructor
public class SomeGenericService<T extends Base> {
private final T base;
public void someWork(String info) {
base.someAction(info);
}
}
I also have 3 Base implementations marked with #Component(Base1, Base2, Base3)
I want spring itself to create a service with the generic it needs, for the following example
#Component
#RequiredArgsConstructor
public class Runner implements CommandLineRunner {
private final SomeGenericService<Base1> s1;
private final SomeGenericService<Base2> s2;
private final SomeGenericService<Base3> s3;
#Override
public void run(String... args) throws Exception {
String someString = "text";
s1.someWork(someString);
s2.someWork(someString);
s3.someWork(someString);
}
}
But after the launch, the spring does not understand what I want from it.
Parameter 0 of constructor in SomeGenericService required a single bean, but 3 were found:
- base1: defined in file [Base1.class]
- base2: defined in file [Base2.class]
- base3: defined in file [Base3.class]
Is it possible to set this to automatic, without manually configuring it via the #Bean annotation for each service?
You need to define how those beans should be injected. It's a good practice to have some #Configurations for this purpose. Something like:
#Configuration
#Import({
Base1.class,
Base2.class,
Base3.class
})
public class SomeConfig {
#Bean
SomeGenericService<Base1> someGenericService1() {
return new SomeGenericService(new Base1());
}
#Bean
SomeGenericService<Base2> someGenericService2() {
return new SomeGenericService(new Base2());
}
#Bean
SomeGenericService<Base3> someGenericService3() {
return new SomeGenericService(new Base3());
}
}

Improve explicitness of spring library for extendable config-objects

I am currently working on a spring-library that allows user-defined config-classes (has nothing to to with #Configuration) to be adjusted from another part of the application before they are used:
interface ConfigAdjuster<T extends Config<T>> {
void adjust(T t);
}
abstract class Config<T extends Config<T>> {
#Autowired
Optional<ConfigAdjuster<T>> adjuster;
#PostConstruct
private void init() {
//i know this cast is somewhat unsafe, just ignore it for this question
adjuster.ifPresent(a -> a.adjust((T)this));
}
}
This can be used as follows:
class MyConfig extends Config<MyConfig> {
//imagine many fields of more complex types
public String myData;
}
#Configuration
class MyConfigDefaults {
#Profile("dev")
#Bean
public MyConfig devDefaults() {
//imagine setting defaults values here
return new MyConfig();
}
}
Now a consumer of the library that uses MyConfig can do the following somewhere in his application:
#Bean
public ConfigAdjuster<MyConfig> adjustDefaults() {
return cfg -> {
cfg.myData = "something_other_than_default";
}
}
The biggest problem I see with this approach is that the whole "adjust the config"-part is somewhat hidden for the user. You can not easily tell you are able to change the default-configuration by using a ConfigAdjuster. In the worst case the user tries to autowire the config object and tries to modify it that way which results in undefined behaviour because other components could already have been initialized with the defaults.
Is there an easy way to make this approach more "telling" than what it is right now? The whole idea is to not copy&paste the whole default-config + adjustment parts across multiple projects.
One way to make all of this more explicit would be to require the adjuster in the constructor of Config, but this pollutes every constructor and usage of the inherting classes.
Any thoughts on this?
Edit: Do note that this is a simplified version of the library and I do know about the implications of a private #PostConstruct etc. If you have another way of achieving all of this without the #PostConstruct please do share :)
Edit2:
Let me outline the main goals of this library again:
Allow the definition of default config-objects for the library-user
Allow the enduser (consuming a depedency using this library) to overwrite certain parts of the default configuration before it is used
Save the library-user from boilerplate (e.g. define 2. on their own)
There is two solution for your problem:
1- define a generic Customizer something like:
public interface Customizer<T> {
T customize(T t);
boolean supports(Class target);
}
in your lib you have a config:
public class MyConfig {
private String property;
public MyConfig() {
}
public void setProperty(String property) {
this.property = property;
}
}
so your Default configuration should look something like this:
#Configuration
public class DefaultConfiguration {
#Autowired(required = false)
private List<Customizer> customizers;
#Bean
public MyConfig myConfig() {
MyConfig myConfig = new MyConfig();
myConfig.setProperty("default value");
if (customizers != null) {
for (Customizer c : customizers) {
if (c.supports(MyConfig.class)) {
return (MyConfig) c.customize(myConfig);
}
}
}
return myConfig;
}
}
this way, the only thing the user should do whenever he wants to customize you bean is to implement Customizer, and then declare it as a bean.
public class MyConfigCustomizer implements Customizer<MyConfig> {
#Override
public MyConfig customize(MyConfig myConfig) {
//customization here
return myConfig;
}
#Override
public boolean supports(Class<?> target) {
return MyConfig.class.isAssignableFrom(target);
}
}
and he should declare it:
#Bean
public Customizer<MyConfig> customizer(){
return new MyConfigCustomizer ();
}
I think this answers your question, but it's ugly (uncheched warnings and a List ...) not the best, as everything seems to the user customizable even it's not.
2- I suggest you expose interfaces for Beans that can be adjusted by the user, something like:
public interface MyConfigCustomizer{
MyConfig customize(MyConfig config);
}
your Default Configuration:
#Configuration
public class DefaultConfiguration {
#Autowired(required = false)
private MyConfigCustomizer customizer;
#Bean
public MyConfig myConfig() {
MyConfig myConfig = new MyConfig();
myConfig.setProperty("default value");
if (customizer != null) {
return customizer.customize(myconfig);
}
return myConfig;
}
}
this way the user knows that MyConfig can be adjusted (and not all the beans).

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.

Inject implementation based on command line argument

Is there a way to inject a particular interface implementation based on a command line argument in Spring Boot?
I have a data loading app and based on the command line argument I need to load a specific type of data.
Here is my main class and CommandLineRunner:
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
private Type1LoadProcess type1LoadProcess;
private Type2LoadProcess type2LoadProcess;
public DataLoadersApplication(Type1LoadProcess type1LoadProcess,
Type2LoadProcess type2LoadProcess) {
this.type1LoadProcess = type1LoadProcess;
this.type2LoadProcess = type2LoadProcess;
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
#Override
public void run(String... args) {
if (args[0].equalsIgnoreCase("load-type1")) {
type1LoadProcess.process();
} else if (args[0].equalsIgnoreCase("load-type2")) {
type2LoadProcess.process();
}
}
}
Is there a way where I create a DataLoadeProcess interface with two implementations Type1DataLoadProcess and Type2DataLoadProcess and inject the implementaion in main class based on the commandline arg?
You can use Spring profiles to achieve your goal:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
Create the interface DataLoadProcess
Then the classes:
#Component
#Profile("type1")
public class Type1LoadProcess implements DataLoadProcess {
}
#Component
#Profile("type2")
public class Type2LoadProcess implements DataLoadProcess {
}
Then you can inject the interface type like:
#Autowired
DataLoadProcess dataLoadProcessor;
And now you can start your application with one of the profiles for example with a system property set:
-Dspring.profiles.active=type1
A complete example for this is
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
public interface LoadProcess {
void doLoad();
}
#Component // default that exists unconditionally in any profile
static class Type1LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load1"); }
}
#Profile("type2") // this only exists in the type2 profile
#Primary // if it exists it gets picked over others
#Component
static class Type2LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load2"); }
}
// need a 3rd? #Profile("type3") #Primary #Component
#Autowired // need one of them here
private LoadProcess loadProcess;
#Override
public void run(String... args) {
loadProcess.doLoad();
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
}
This makes use of profiles and uses the primary bean mechanism to allow for a default implementation when no profile is specified.
You can then select which profile is used via any of the options listed at https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html e.g. by setting an environemnt variable
SPRING_PROFILES_ACTIVE=type2 java -jar myApp.jar
using a property
java -Dspring.profiles.active=type2 java -jar myApp.jar
or even a parameter
java -jar myApp.jar --spring.profiles.active=type2
when you want the type2 implementation. You can still put "type1" as active profile even though it is nowhere defined. It will still do the right thing and use the type1 code since that's the default.
I would use Spring profiles for this. Just turn your implementations into Spring Beans and then load the desired Bean based on an active profile.
When you then specify the active profile(s) as command line parameters when starting the app the respective Bean should get used.

Spring default wiring with Profile

I've got two beans. Both implement the mailing function. One is only working when it is deployed to an application server. The other one is used for testing.
We have profile for each developer and environment. I want to wire the testing bean only when actually testing. The other bean should be used when not testing. How can I archive this?
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Solution approaches:
Using "default" I read this somewhere, but there seems to be no fall-back to "default" for a profile like "dev":
#Component
#Profile("default")
public class OnlineMail implements Mailing {}
-> Exception for no bean for wiring found.
Leaving the profile out:
#Component
public class OnlineMail implements Mailing {}
-> Throws a unique exception when running the "localtest" profile.
Adding all profiles:
#Component
#Profile("prod")
#Profile("integration")
#Profile("test")
#Profile("dev1")
#Profile("dev2")
#Profile("dev3")
...
public class OnlineMail implements Mailing {}
This is actually working, however our devs aren't numbered they use "dev<WindowsLogin>" and adding the profiles, may work for one bean, but one will get into trouble when using it for several beans as this definitely gets ugly.
Using something like #Profile("!localtest") doesn't seem to work as well.
Does anyone know a nicer way to get a "wire by default if no specific bean is found"?
I finally found an easy solution.
The online mail is just wired by default.
#Component
public class OnlineMail implements Mailing {}
Using the #Primary annotation the offline mail takes precedence over the OnlineMail and avoids the Unique exception.
#Component
#Profile("localtest")
#Primary
public class OfflineMail implements Mailing {}
Try this:
#Component
#Profile("production")
public class OnlineMail implements Mailing {}
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Then run tests using #ActiveProfiles("localtest") and run production enviroment using "production" as DEFAULT profile.
Also I hope in next version of Spring ActiveProfilesResolver will be introduced SPR-10338 - it may be helpfull for you (to avoid "dev1", "dev2" and so on).
Spring supports inject the Bean by #Profile very well:
interface Talkative {
String talk();
}
#Component
#Profile("dev")
class Cat implements Talkative {
public String talk() {
return "Meow.";
}
}
#Component
#Profile("prod")
class Dog implements Talkative {
public String talk() {
return "Woof!";
}
}
Works in unit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContex-test.xml"})
#ActiveProfiles(value = "dev")
public class InjectByDevProfileTest
{
#Autowired
Talkative talkative;
#Test
public void TestTalkative() {
String result = talkative.talk();
Assert.assertEquals("Meow.", result);
}
}
Works in Main():
#Component
public class Main {
public static void main(String[] args) {
// Enable a "dev" profile
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main p = context.getBean(Main.class);
p.start(args);
}
#Autowired
private Talkative talkative;
private void start(String[] args) {
System.out.println(talkative.talk());
}
}
Check this for the Demo code: https://github.com/m2land/InjectByProfile

Categories

Resources