A way to configure the Adapter to autowire via yml - java

I have a (spring) service sending emails via different client adapters, depending on the environment they run on. So when i am on dev i want to use ClientAdapterA and on live ClientAdapterB. The usage looks like the following:
#Service
public class EmailService {
#Autowired
private ClientAdapter clientAdapter;
public EmailResponse send(EmailRequest emailRequest) {
return clientAdapter.sendMail(emailRequest);
}
}
The configuration i want to happen via my application.yml:
mail:
adapter: ClientAdapterA
I tried to use #Qualifier but that only allows use of one hard-coded adapter:
#Qualifier("ClientAdapterB")
#AutoWired
private ClientAdapter clientAdapter;
I also tried creating a Config class which should provide the respective bean, but that didn't work either:
#Configuration
public class JavaBeansAdapterConfig {
#Value("${mail.adapter}")
private String adapterName;
#Bean
public ClientAdapter clientAdapterImplementation() throws ClassNotFoundException {
ApplicationContext ctx = new AnnotationConfigApplicationContext();
return (ClientAdapter)ctx.getAutowireCapableBeanFactory().createBean(Class.forName(adapterName));
}
}
What am i doing wrong, or is there even a way do do it like i want to? Any help is appreciated, thank you.

Define your Bean by using Class.forName(). But you need to set packageName as well. See below:
#Configuration
public class JavaBeansAdapterConfig {
#Value("${mail.adapter}")
private String adapterName;
private final String packageName = "com.foo.bar";
#Bean
public ClientAdapter clientAdapterImplementation() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return (ClientAdapter) Class.forName(packageName + "." + adapterName).newInstance();
}
}

I tried with the package name, but this only fixed half the problems. I still could not seem to autowire the sub-dependencies, so I did some more digging and stumbled upon this solution.
My adapter configuration now looks like this:
#Component
#Setter
#Getter
#ConfigurationProperties(prefix = "mail.adapter")
public class ClientAdapterConfig {
#Qualifier("mailClientA")
#Autowired
private ClientAdapter mailClientA;
#Qualifier("mailClientB")
#Autowired
private ClientAdapter mailClientB;
private String name;
#Bean
#Primary
public ClientAdapter getAdapter()
{
ClientAdapter adapter = null;
switch (name) {
case "mailClientA":
adapter = mailClientA;
break;
case "mailClientB":
adapter = mailClientB;
break;
default:
break;
}
return adapter;
}
}
With the #Qualifier annotation I make sure that it puts in the Adapter I want.
Making the Bean #Primary then ensures it is correctly autowired into the EmailService (which remains unchanged compared to the block in the question)
I am aware that this is not actually a Configuration in the Spring sense, but at this point this is good-enough of a replacement for me.

Related

Spring boot #Value NullPointerException

I'm writing a Spring Boot application and am trying to load some values from a properties file using the #Value annotation. However, the variables with this annotation remain null even though I believe they should get a value.
The files are located in src/main/resources/custom.propertes and src/main/java/MyClass.java.
(I have removed parts of the code that I believe are irrelevant from the snippets below)
MyClass.java
#Component
#PropertySource("classpath:custom.properties")
public class MyClass {
#Value("${my.property:default}")
private String myProperty;
public MyClass() {
System.out.println(myProperty); // throws NullPointerException
}
}
custom.properties
my.property=hello, world!
What should I do to ensure I can read the values from my property file?
Thanks!
#value will be invoked after the object is created. Since you are using the property inside the constructor hence it is not available.
You should be using constructor injection anyway. It makes testing your class easier.
public MyClass(#Value("${my.property:default}") String myProperty) {
System.out.println(myProperty); // doesn't throw NullPointerException
}
You are getting this error because you are initializing the class with new keyword. To solve this,
first you need to create the configuration class and under this class you need to create the bean of this class.
When you will call it by using bean then it will work..
My code:
#Component
#PropertySource("db.properties")
public class ConnectionFactory {
#Value("${jdbc.user}")
private String user;
#Value("${jdbc.password}")
private String password;
#Value("${jdbc.url}")
private String url;
Connection connection;
#Bean
public String init(){
return ("the value is: "+user);
}
My Config.class:
#Configuration
#ComponentScan
public class Config {
#Bean
public Testing testing() {
return new Testing();
}
#Bean
public ConnectionFactory connectionFactory() {
return new ConnectionFactory();
}
}
Calling it:
public static void main(String[] args) {
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(Config.class);
ConnectionFactory connectionFactory= context.getBean(ConnectionFactory.class);
System.out.println(connectionFactory.init());
}




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

Dirty test context since migrating from spring 4.0.x to 4.1.x

I'm trying to migrate an old applicaiton step by step, starting with migrating from spring 4.0 to 4.1.
While the documentation says that there are no breaking changes, it seems otherwise.
My integration test looks somewhat like this:
#ContextConfiguration(locations = {"/com/a/KernelTestContext.xml"})
public class KernelTest extends AbstractJUnit4SpringContextTests {
#Configuration
static class ContextConfiguration {
#Bean
...
}
}
#Resource(name = "CsvResourceProcessingChannel")
private MessageChannel csvResourceProcessingChannel;
#Test()
public void testIt() {
...
csvResourceProcessingChannel.send(MessageBuilder.withPayload(new ByteArrayResource(input.getBytes()))
.setHeader(Headers.IMPORT_SOURCE, importSource1).setReplyChannel(testChannel)
.setErrorChannel(testErrorChannel).build());
}
}
The csvResourceProcessingChannel Bean accesses posts into the splitter bean CsvResourceSplitter, which is defined like this:
public class CsvResourceSplitter extends AbstractMessageSplitter {
private final static Logger LOGGER = LoggerFactory.getLogger(CsvResourceSplitter.class);
#Autowired
private ApplicationContext applicationContext;
private Map<Partner, CsvMappingStrategy> csvMappingMap;
...
private void updateCsvMappings() {
final Map<String, CsvMappingStrategy> mappingBeanMap = applicationContext.getBeansOfType(CsvMappingStrategy.class,
false, true);
csvMappingMap = Maps.newEnumMap(Partner.class);
for (final CsvMappingStrategy csvMappingStrategy : mappingBeanMap.values()) {
final CsvMappingStrategy previous = csvMappingMap.put(csvMappingStrategy.getPartner(),
csvMappingStrategy);
Preconditions.checkArgument(previous == null, "More than one CsvMapping bean found for partner: '%s",
csvMappingStrategy.getPartner());
}
}
}
The problem becomes noticeable with the final Preconditions check in updateCsvMappings:
While the test only defines one CsvMappingStrategy class, several more including duplicates are found since the upgrade from spring 4.0 to 4.1.
Due to the contents of mappingBeanMap during runtime I'm pretty sure that the context used here contains context elements used in CsvResourceSplitterTest, which looks somewhat like this:
#ContextConfiguration
public class CsvResourceSplitterTest extends AbstractJUnit4SpringContextTests {
#Configuration
static class Config {
#Bean
public CsvResourceSplitter createCsvResourceSplitter() {
return new CsvResourceSplitter();
}
#Bean
public CsvMappingStrategy createTestMappingStrategy() {
return new AbstractCsvMappingStrategy() {
...
};
}
...
}
#Autowired
private CsvResourceSplitter splitter;
...
}
Any ideas on what is going wrong are appreciated.
A useful workaround for this was moving the CsvResourceSplitterTest context to a different profile:
#ContextConfiguration
#ActiveProfiles("anonymous_profile")
public class CsvResourceSplitterTest extends AbstractJUnit4SpringContextTests {
#Profile("anonymous_profile")
#Configuration
static class Config {
...
}

#Autowired create null object inspite #configuration

I have the following configuration class
#org.springframework.context.annotation.Configuration
public class TemplateConfiguration {
#Bean
public Configuration configuration() {
Configuration configuration = new Configuration(new Version(2, 3, 23));
configuration.setClassForTemplateLoading(TemplateConfiguration.class, "/templates/");
configuration.setDefaultEncoding("UTF-8");
configuration.setLocale(Locale.US);
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
return configuration;
}
}
and I use it at the following #service
#Service
public class FreeMarkerService {
#Autowired
private Configuration configuration;
private static final Logger logger = LoggerFactory.getLogger(FreeMarkerService.class);
public String process() {
try {
Template template = configuration.getTemplate("someName");
....
} catch (IOException | TemplateException e) {
logger.error("Error while processing FreeMarker template: " + e);
throw new RuntimeException(e);
}
}
}
but when I try to call process() like
FreeMarkerService f = new FreeMarkerService()
f.process()
I get a null exception cause the configuration Object is null
I want to create an instance using #Autowired and #Configuration annotations
what am I doing wrong?
You should use the Spring instantiated FreeMarkerService object avoiding use of new keyword for objects like Controllers or Services as possible.
For example,
#Service
public class SampleService {
#Autowired
private FreeMarkerService freeMarkerService;
public String callProcess() {
return freeMarkerService.process();
}
}
More details you can find in many posts like this.
This is a member injection:
#Autowired
private static Configuration configuration;
Which spring does after instantiating the bean from its constructor. So at the time you are making that static method call spring has not injected the value.
This is because you are trying to autowire a static field. This is not possible in Spring. Remove static from your Configuration property and it should work.
#Autowired
private Configuration configuration;
#Autowired
private static Configuration configuration;
Why autowired a static field? this is the reason. static member load as class definition load so it is not getting injected value and getting default value which is null.

Spring/TestNG integration: Auto-injection fails with two test classes

Hitting an odd issue with my spring based integration tests. Note that I'm not so much unit testing a spring IoC app as I'm using Spring to auto-inject properties into my test configuration. Also - we're not using any context configuration xml, everything is configured programatically.
I got the framework all set up and running with a single test before handing it off to the team tester, auto-injection was working fine. However, as soon as he added a second test class, auto-injection of my configuration field just stops working. I've cloned his branch and verified the behavior; Commenting out his class OR my original class causes the auto-injection to return to normal function.
Anybody ever seen this? Relevant code snippets below. I've tried moving around the #ContextConfiguration annotation but it didn't really help. It seems like Spring is having trouble deciding where the TestProperties object should come from. The field I've commented on below is coming up null when I run the tests, but only if there are two test classes enabled.
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public abstract class BaseIntegrationTest
extends AbstractTestNGSpringContextTests
{
#Autowired
protected MockServerClient mockServer;
// I get an error in IntelliJ Pro on this field.
// Could not autowire. There is more than one bean of 'TestProperties' type. Beans: getTestProperties, testProperties
// I've also tried #Qualifier("getTestProperties") and #Qualifier("testProperties")
// which both resolve the error in IntelliJ but the TestProperties field is still null when running the tests.
#Autowired
protected TestProperties properties;
//...
}
public class TestGetMuIT
extends BaseIntegrationTest
{
private ManagementUnitClient managementUnitClient;
#BeforeMethod(alwaysRun = true)
public void setup()
{
String endpoint = properties.getServiceBaseUri(); //NullPointerException thrown here during test run
}
}
Other relevant classes:
#Configuration
public class RootConfig
{
protected static final Logger LOGGER = LoggerFactory.getLogger(RootConfig.class);
private PropertyService propertyService;
private TestProperties testProperties;
#Bean
public PropertyService propertyService() throws IOException {
if (propertyService == null) {
propertyService = new DynamoDbPropertyService(config.getPropertiesConfig());
propertyService.initialize();
}
return propertyService;
}
#Bean
public TestProperties getTestProperties() throws IOException {
if (testProperties == null) {
testProperties = new TestProperties();
}
}
And the actual TestProperties class:
#Component
public class TestProperties
{
public static class Default {
public static final String BASE_URI = "http://localhost:8055";
}
#Autowired
private PropertyService propertyService;
public String getServiceBaseUri()
{
return propertyService.getPropertyRegistry().getString(TestConfigKey.LocalServiceBaseUri.key(), Default.BASE_URI);
}
}
And these are my spring config annotations:
#Configuration
//Note: TestProperties.class is a recent addition. I added that and commented out the #Bean getTestProperties() method in RootConfig as a troubleshooting step
#Import({RootConfig.class, TestProperties.class})
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.inin.wfm.it"},
excludeFilters = #ComponentScan.Filter(value = com.inin.wfm.it.config.ConfigPackageExcludeFilter.class, type = FilterType.CUSTOM))
public class TestConfig {\\... }
Again all of the above works just fine if there's only one test file (and it doesn't matter which of the two I comment out). Anybody know what I'm doing wrong in my configuration?

Categories

Resources