How does one implement multiton pattern using Spring Framework's facilities?
https://en.wikipedia.org/wiki/Multiton_pattern
I want to write a factory which takes a pair of client and supplier as arguments. The factory should always return a bean of type T. For a given pair of client and supplier, the instance of T return should be a singleton, but for a different pair of client and supplier, it will be a different instance of T. Please suggest a way to implement this without implementing boilerplate code that Spring may already provide.
Interface ClientSdk {
sendRequestToClient();
}
class ClientASdk implements ClientSdk {
}
class ClientBSdk implements ClientSdk {
}
enum Client {
ClientA,
ClientB;
}
enum Supplier {
SupplierA,
SupplierB;
}
class ClientSupplier {
private Client client;
private Supplier supplier;
}
class SdkFactory {
public ClientSdk getClientSdk(ClientSupplier clientSupplier) {
//For a given ClientSupplier, always return the same
//ClientSupplier instance
}
}
#Service
class ClientRequestService {
public sendRequestToClient(ClientSupplier clientSupplier) {
ClientSdk clientSdk = SdkFactory.getSdk(clientSupplier);
clientSdk.sendRequestToClient();
}
}
Here's a solution to your problem. It does make SdkFactory a bean as #crizzis suggests, but it also creates bean instances for each ClientSdk instance so that each of them can be autowired or otherwise helped out by Spring. Note that I added an ident() method to the ClientSdk interface just to show that the MyClientSdk beans have in fact been autowired with the Spring Environment:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
interface ClientSdk {
void sendRequestToClient();
}
// This class is instantiated via a #Bean method inside SdkFactory. Since it is annotated as a Prototype bean,
// multiple instances of this class can be created as beans.
class MyClientSdk implements ClientSdk {
#Autowired
Environment environment;
private final String clientSupplier;
MyClientSdk(String clientSupplier) {
this.clientSupplier = clientSupplier;
System.out.printf("### Created MyClientSdk for: %s\n", clientSupplier);
}
public void sendRequestToClient() {
System.out.printf("Sending request for client: %s\n", clientSupplier);
System.out.printf("CS: %s Environment Prop: %s\n", clientSupplier, environment.getProperty("spring.application.name"));
}
}
#Component
class SdkFactory implements BeanFactoryAware {
private Map<String, ClientSdk> sdks = new HashMap<>();
private BeanFactory beanFactory;
// Here's the key logic to insure that we get just one instance of ClientSdk per clientSupplier value.
ClientSdk getClientSdk(String clientSupplier) {
if (!sdks.containsKey(clientSupplier))
sdks.put(clientSupplier, beanFactory.getBean(ClientSdk.class, clientSupplier));
return sdks.get(clientSupplier);
}
// This is probably the most important bit. This creates a Spring Bean unique to a particular 'clientSupplier'
// value, but only does so when requested so that the factory can control when these beans are created, creating
// only one per a particular `clientSupplier` value.
#Bean
#Scope("prototype")
ClientSdk createSdk(String clientSupplier) {
return new MyClientSdk(clientSupplier);
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
#Service
class ClientRequestService {
#Autowired
SdkFactory factory;
public void sendRequestToClient(String clientSupplier) {
ClientSdk clientSdk = factory.getClientSdk(clientSupplier);
clientSdk.sendRequestToClient();
}
}
#SpringBootApplication
public class HarmonyTestApp implements CommandLineRunner {
#Autowired
ClientRequestService service;
public static void main(String[] args) {
try {
ApplicationContext applicationContext = new SpringApplication(new Class<?>[]{HarmonyTestApp.class}).run(args);
} catch (Throwable e) {
e.printStackTrace();
}
}
#Override
public void run(String... args) {
service.sendRequestToClient("client1");
service.sendRequestToClient("client2");
service.sendRequestToClient("client1");
service.sendRequestToClient("client1");
service.sendRequestToClient("client2");
}
}
Result:
### Created MyClientSdk for: client1
Sending request for client: client1
CS: client1 Environment Prop: TestApp
### Created MyClientSdk for: client2
Sending request for client: client2
CS: client2 Environment Prop: TestApp
Sending request for client: client1
CS: client1 Environment Prop: TestApp
Sending request for client: client1
CS: client1 Environment Prop: TestApp
Sending request for client: client2
CS: client2 Environment Prop: TestApp
Note that per the output, each of client1's and client2's ClientSdk objects are only created once, even though they're used multiple times. Also notice that since the call to ident() in sendRequestToClient prints the value of a property obtained by an autowired Environment instance, autowiring of each ClientSdk object has worked.
I do realize that I used a String instead of a ClientSupplier object as the identifying key for each ClientSdk object. I did that just to keep the example as simple as I could. I expect you can expand the example to replace the clientSupplier String with an instance of ClientSupplier and somehow use that object as the key/identifier to insure that just one ClientSdk instance is created per ClientSupplier. That detail isn't really germain to the basic idea here.
Also, please note that the question itself changed while I was working on my implementation. Given that there are now exactly two subclasses of ClientSdk, you could simply make those regular #Component Spring beans. Having a small static number of those makes this problem less interesting. The technique I demonstrate here allows for an unlimited number of bean instances of ClientSdk class without having to define a unique class for each of them. This requires that Spring create arbitrary instances of them based on runtime information. This was what the original form of the question seemed to be asking for.
Related
I would like to write a unit test which is executed for every Spring bean of a given type. JUnit5's parameterized tests offer a lot of possibilities, but I don't know how to inject beans into a method source as it has to be a static method.
Is there a way to determine the parameters of a JUnit5 test based on Spring's application context?
For starters, a factory method configured via #MethodSource does not have to be static. The second sentence in the User Guide explains that.
Factory methods within the test class must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static.
Thus, if you use #TestInstance(PER_CLASS) semantics, your #MethodSource factory method can be non-static and can therefore access the ApplicationContext injected into the test instance.
Here's an example that demonstrates that for beans of type String, with an intentional failure for the bar bean.
import java.util.stream.Stream;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
ApplicationContext applicationContext;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return applicationContext.getBeansOfType(String.class).values().stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
If you don't want to work directly with the ApplicationContext, you can simplify the solution by having the collection of all such beans of a given type (String in this example) injected directly, as follows.
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
List<String> stringBeans;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return this.stringBeans.stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
The usage of the #TestFactory might help.
Actually I stumbled across a post that does a pretty similar (or the same) thing as you do on github.
Let your Test run with the SpringExtenion and use the injected Beans as parameters for our Test.
I am creating a cache client wrapper using spring framework. This is to provide cache layer to our application. Right now, we are using redis. I have found out that spring-data-redis library is very good for creating my wrapper.
My application will pass a configuration POJO to my wrapper and will then use the interface that I will provide.
spring-data-redis provides an easy way to access redis using two variables.
RedisConnectionFactory
RedisTemplate<String, Object>
Although, I will be providing a better interface to my application with my interface functions like:
public Object getValue( final String key ) throws ConfigInvalidException;
public void setValue( final String key, final Object value ) throws ConfigInvalidException;
public void setValueWithExpiry(final String key, final Object value, final int seconds, final TimeUnit timeUnit) throws ConfigInvalidException;
I still want to provide RedisConnectionFactory and RedisTemplate beans.
My question is how to initialize my wrapper application with this configuration POJO?
Currently my configuration looks like this:
import java.util.List;
public class ClusterConfigurationProperties {
List<String> nodes;
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
And my AppConfig.java looks like this:
import com.ajio.Exception.ConfigInvalidException;
import com.ajio.configuration.ClusterConfigurationProperties;
import com.ajio.validator.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
#Configuration
public class AppConfig {
#Autowired
private ClusterConfigurationProperties clusterConfigurationProperties;
#Autowired
private Validator validator;
#Bean
ClusterConfigurationProperties clusterConfigurationProperties() {
return null;
}
#Bean
Validator validator() {
return new Validator();
}
#Bean
RedisConnectionFactory connectionFactory() throws ConfigInvalidException {
if (clusterConfigurationProperties == null)
throw new ConfigInvalidException("Please provide a cluster configuration POJO in context");
validator.validate(clusterConfigurationProperties);
return new JedisConnectionFactory(new RedisClusterConfiguration(clusterConfigurationProperties.getNodes()));
}
#Bean
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws ConfigInvalidException {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory());
redisTemplate.setKeySerializer( new StringRedisSerializer() );
redisTemplate.setHashValueSerializer( new GenericToStringSerializer<>( Object.class ) );
redisTemplate.setValueSerializer( new GenericToStringSerializer<>( Object.class ) );
return redisTemplate;
}
}
Here I am expecting a ClusterConfigurationProperties POJO as a bean in application which will be using the interface of wrapper.
But to compile my wrapper, I have created a null bean itself. Then when application uses it, there will be two beans, one of application and one of wrapper.
How should I resolve this problem?
Actually what i wanted was to have cluster config as a bean in my client application. For that i dont need to declare #autowire clusterconfig in my wrapper application. Instead should take cluster config as a parameter in the method, so that the client will pass cluster config object when creating bean. And the bean which is created in client code should have code for creating redis connection factory.
But all this i was writing was to make my client unknown of redis. So, better solution is to have wrapper class which takes cluster config pojo and create redis connection factory etc. And client should create this wrapper as a bean.
Very poor concept of spring and design patterns lead me to this mistake.
My application uses one "main" redis instance for things like session storage and cache but needs to talk to a separate "external" instance for other reasons. I am trying to determine the "best" ("most idiomatic"? "simplest"?) way to configure this in my Spring Boot application.
Ideally I'd just like to use the default auto-configuration for the main instance but as soon as I register a connection factory for the external instance the #ConditionalOnMissngBean({RedisConnectionFactory.class}) condition in LettuceConnectionConfiguration becomes false and so the default instance isn't created. Looking at what else is going on in LettuceConnectionConfiguration etc. I feel like I'd rather not manually configure it if I don't need to.
I could just not expose the "external" connection factory as a bean and only use it internally to create the beans that depend on it but, while that would be ok in my specific case, I'd like to understand if there's a better solution where both factories can be exposed.
Is there some way I can expose the second RedisConnectionFactory without disabling the default one provided by auto configuration? Is there a clear "right way" to do this sort of thing?
you must implement the BeanDefinitionRegistryPostProcessor to adjust the RedisConnectionFactory order
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.stereotype.Component;
#Component
public class MultipleRedisConnectionFactoryRegistrar implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition bd1 = registry.getBeanDefinition("redisConnectionFactory");
if (bd1 != null) {
BeanDefinition bd = new RootBeanDefinition(ExternalRedisConnectionFactoryBean.class);
registry.registerBeanDefinition("externalRedisConnectionFactory", bd);
}
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
in ExternalRedisConnectionFactoryBean, you can create your own RedisConnectionFactory
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
public class ExternalRedisConnectionFactoryBean implements FactoryBean<RedisConnectionFactory> {
#Override
public RedisConnectionFactory getObject() throws Exception {
//you can mannually create your external redis connection factory here
return null;
}
#Override
public Class<?> getObjectType() {
return RedisConnectionFactory.class;
}
}
if you want to use the multiple RedisConnectionFactory, you #Qualifier is the right choice, for example
#Autowired
#Qualifier("redisConnectionFactory")
private RedisConnectionFactory defaultRedisConnectionFactory;
#Autowired
#Qualifier("externalRedisConnectionFactory")
private RedisConnectionFactory externalRedisConnectionFactory;
I've a domain class that I want to auto-populate from external config. Here is my domain class:
#Data
#Configuration
#PropertySource("classpath:application.properties")
public class StudioVo {
#Value("${studio.code}")
private code;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my context xml:
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
<property name="studioVo" ref="StudioVo" />
</bean>
<bean id="StudioConfigVo" class="com.sdm.studio.domain.StudioVo" />
</bean>
Here is the class where I want to use the vo:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
private StudioVo studioVo;
public List<Studio> read() throws Exception {
System.out.println("getCode: " + studioVo.getCode()); //code is null here
return null;
}
}
However when I run it via unit test by autowiring, it runs fine. Like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class StudioTest {
#Autowired
private StudioVo studioVo;
#Test
public void testAutoPopulationOfStudio(){
System.out.println("getCode: "+ studioVo.getCode()); // works!
// Assert.assertTrue(studioVo.getCode().equals("102"));
}
}
Not sure what's going on here - I'm working with an old Spring Batch application wrapped in Spring Boot (so there is a mix of XML based and Java based config - and may be that is the cause of this issue). What am I missing?
In your StudioTest, you are autowiring StudioReader where as you missed the #Autowired in your StudioReader code, so add it as shown below:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
#Autowired //add this so that studioVo can be injected
private StudioVo studioVo;
//add other code
}
Please be certain to note that using #Autowire requires a chain of Spring-managed beans below it from wherever you are using it including the class in which you are using #Autowire. That is because Spring needs the precedent references to match up the object-reference hierarchy. I.e., in business logic layer ClassA, you want to #Autowire a field. ClassA itself needs to be a managed bean. Further, if the field you want to #Autowire holds an object that has referential dependencies to other objects (and most do), these also must be Spring-managed.
For example, the following will work:
package com.example.demo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MessageRunner {
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage = (SetterMessage) (new AnnotationConfigApplicationContext(DemoConfiguration.class)).getBean("setterMessage");
setterMessage.setMessage("Finally it works.");
p(setterMessage.getMessage());
}
private static void p(String s) {
System.out.println(s);
}
}
DemoConfiguration.java looks like this:
package com.example.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.example.demo")
public class DemoConfiguration {
}
SetterMessage.java, this:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage {
private String message = null;
#Autowired
private SetterMessage2 setterMessage2;
public String getMessage(){
return message+setterMessage2.getSubMessage();
}
public void setMessage(String message) {
this.message = message;
setterMessage2.setSubMessage("("+message+")");
}
}
SetterMessage2.java:
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage2 {
private String subMsg = "";
public void setSubMessage(String msg) {
subMsg = msg;
}
public String getSubMessage() {
return subMsg;
}
}
Note that SetterMessage2.java is annotated as a Component (#Service) but no field in it is autowired. That is because it's the end of the object reference chain. But because it is a Component, it can be autowired into SetterMessage.java. However look at MessageRunner.java's main() method and field declarations. Note that the class field SetterMessage is NOT autowired. If it were annotated as #Autowired, main() would fail at runtime, throwing an NPE with the reference to setterMessage in main(). This is because MessageRunner.java is not marked as some kind of component. So we need to grab a valid instance of MessageSetter from the application context and use it.
To emphasize, the following version of MessageRunner.java's main() method WILL FAIL, throwing an NPE, if MessageRunner.java looked like this:
...
public class MessageRunner {
#Autowired // <-- This will not do the job for us
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage.setMessage("Finally it works."); // NPE here on ref to setterMessage
p(setterMessage.getMessage());
}
...
This is a real gotchya for people new to Spring. In fact, I'd place it among the Top Five Spring Newbie Discouragers and a really evil, pernicious detail that has caused new Spring programmers countless hours in aggravation and Google searches. So I do hope that noting this phenom here will save at least some newbies time and high blood pressure spikes.
Note: If you go to create the above classes in your IDE, bear in mind these were developed with Spring Boot enabled.
If I declare a class using #Bean and then component scan for the class, spring will instantiate the class by invoking it's constructor and injecting constructor args and injecting any fields marked with #Inject. For simplicity's sake, lets call this spring auto-building.
I dislike component scan and wish to avoid it completely (I don't wish to discuss my reasons for not liking it). I would like to use a #Configuration object instead but would still like to have the auto-building functionality available to me. Is it possible to invoke spring to auto-build my objects instead of explicitly having to pass all the constructor arguments in my #Configuration object?
Lets assume that I have a bean:
public class MyServiceImpl implements MyService {
public MyServiceImpl(Dependency1 d1, Dependency d2) { ... }
....
}
I could define a configuration object like this:
#Configuration
public class MyConfiguration {
// lets assume d1 and d2 are defined in another #Configuration
#Inject
Dependency1 d1;
#Inject
Dependency2 d2;
#Bean
public MyService myService() {
// I dislike how I have to explicitly call the constructor here
return new MyServiceImpl(d1, d2);
}
}
But now, I have explicitly had to call the MyServiceImpl constructor myself so I will have to keep updating this as my constructor changes over time.
I was hoping that I could declare an abstract method so that spring auto-building could take place:
#Configuration
public abstract class MyConfiguration {
#Bean
public abstract MyServiceImpl myService();
}
But this doesn't work. Is there a way that I can invoke spring auto-building without using a component scan?
In Google Guice, this can be done via the Binder:
https://google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/Binder.html
In Tapestry IOC, this can be done via the ServiceBinder:
http://tapestry.apache.org/ioc-cookbook-basic-services-and-injection.html#IoCCookbook-BasicServicesandInjection-SimpleServices
Update
Based on spod's answer, I was able to achieve what I was after (thanks!). Test case included for anyone that wants to do the same:
import java.util.Date;
import javax.inject.Inject;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class AutoBuildConfigurationTest {
#Configuration
public static class MyConfiguration {
#Inject
private AutowireCapableBeanFactory beanFactory;
#Bean
public Date date() {
return new Date(12345);
}
#Bean
public MyService myService() {
return autoBuild(MyService.class);
}
protected <T> T autoBuild(Class<T> type) {
return type.cast(beanFactory.createBean(type, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true));
}
}
public static class MyService {
private Date date;
public MyService(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
#Test
public void testAutoBuild() {
ApplicationContext appContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyService myService = appContext.getBean(MyService.class);
Assert.assertEquals(12345, myService.getDate().getTime());
}
}
The java based container configuration doesnt depend on doing a component scan in any way. Its merely a different approach for the XML based component configuration. With the XML configuration you'd just have to declare your bean with the MyServiceImpl class in case its already #inject annotated. Spring would recognize the annotations and take care of them. If you really want to instanciate MyServiceImpl from a #Configuration java class without calling the constructor yourself, then you'd have to make use of the bean factory (havent tested it, just give it a try):
#Configuration
public class MyConfiguration {
#Autowired AutowireCapableBeanFactory beanFactory;
#Bean public MyService myService() {
return beanFactory.createBean(MyServiceImpl.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
}
}