Create Spring beans with dynamic property values - java

I have the following immutable class (i.e. in separate library I can't change):
public class MyClass implements InitializingBean, BeanNameAware {
private BeanA prop1;
private BeanB prop2;
public MyClass() {}
#Override
public void afterPropertiesSet() {
// do smth with prop1 and prop2
}
}
I need to create multiple different MyClass beans but with property values (prop1, prop2) that are generated in runtime.
How to implement that?
I tried to implement FactoryBean<MyClass>, but the method FactoryBean#getObject doesn't have parameters.
BeanFactory#getBean with arguments can't be used, because MyClass doesn't have constructor arguments for those properties (it's separate library).

you could use javax.annotation.#PostContruct
#Component
public class MyClass {
private BeanA prop1;
private BeanB prop2;
#PostConstruct
public void doSomethingAfterInstanced() {
prop1 = null;
prop2 = new BeanB();
}
}

Related

Abstract properties in an abstract class [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I have an abstract class with some configuration properties value which are set using #Value. I want to reuse the abstract class but with another set of configuration properties. The issue is that, those properties value have already been set in the abstract class and all of the concrete classes have inherited it.
I have thought about:
creating another set of config values in the abstract class, but that seems to be creating duplicate, but this is not quite extensible when in future there is yet a third set of config values.
changing the accessibly of the config value in the abstract class from private to protected and have the concrete class to override it. But I'm not sure this kind of overriding is good as it seems create confusion as to what is the actual config value.
create another abstract class which is similar as "AbstractService" but injecting the different set of config value using #Value. This also seems to create duplication.
public abstract class AbstractService {
#Value("${config1}")
private String config1;
#Value("${config2}")
private String config2;
public void serviceMethod() {
//using config1 and config 2 values
}
}
public class concreteServiceA extends AbstractService {
public void serviceA() {
// using serviceMethod in the abstract class
}
}
public class concreteServiceB extends AbstractService {
public void serviceB() {
// using serviceMethod in the abstract class
}
}
Would it be a good way if using constructor to pass the required parameters in the abstract class, and let the concrete classes to use constructor injection and #Value to set those values ? though if there are long list of config values this may not scale well.
public abstract class AbstractService {
private String config1;
private String config2;
public AbstractService(String config1, String config2) {
this.config1 = config1;
this.config2 = config2;
}
public void serviceMethod() {
//using config1 and config2 values
}
}
public concreteServiceA extends AbstractService {
public concreteServiceA(#Value("${config1}") String config1,
#Value("${config2}") String config2) {
super(config1, config2);
}
public void serviceA() {
//using serviceMethod in the abstract class
}
}
public concreteServiceB extends AbstractService {
public concreteServiceB(#Value("${configB1}") String config1,
#Value("${configB2}") String config2) {
super(config1, config2);
}
public void serviceB() {
//using serviceMethod in the abstract class
}
}
You can go like following:
public abstract class AbstractService {
public void serviceMethod() {
String config1 = getConfig1();
String config2 = getConfig2();
//using config1 and config 2 values
}
public abstract String getConfig1();
public abstract String getConfig2();
}
public class concreteServiceA extends AbstractService {
#Value("${config1}") private String config1;
#Value("${config2}") private String config2;
public String getConfig1(){
return config1;
}
public String getConfig2(){
return config2;
}
public void serviceA() { // using serviceMethod in the abstract class }
}
public class concreteServiceB extends AbstractService {
#Value("${config1.1}") private String config1;
#Value("${config2.1}") private String config2;
public String getConfig1(){
return config1;
}
public String getConfig2(){
return config2;
}
public void serviceB() { // using serviceMethod in the abstract class }
}
You could either use setter injection or (probably more elegant) constructor injection like this:
public abstract class AbstractService {
protected AbstractService(String config1, String config2) {
this.config1 = config1;
this.config2 = config2;
}
private String config1;
private String config2;
public void serviceMethod() {
//using config1 and config 2 values
}
}
public class ConcreteServiceA extends AbstractService {
public ConcreteServiceA(#Value("${config1a}") String config1, #Value("${config2a}") String config2) {
super(config1, config2);
}
public void serviceA() {
// using serviceMethod in the abstract class
}
}
public class ConcreteServiceB extends AbstractService {
public ConcreteServiceB(#Value("${config1b}") String config1, #Value("${config2b}") String config2) {
super(config1, config2);
}
public void serviceB() {
// using serviceMethod in the abstract class
}
}
But if you have lots of values you can also use setter injection and override the setters in each subclass. Or you can still use constructor injection but pass a container class holding the config like this:
public class ServiceConfig {
private String config1;
private String config2;
// getters, setters and more properties
}
Then pass it like this
public abstract class AbstractService {
private ServiceConfig config;
protected AbstractService(ServiceConfig config) {
this.config = config;
}
}
public class ConcreteServiceA extends AbstractService {
public ConcreteServiceA(#Value("${configA}") ServiceConfig config) {
super(config);
}
}
You can externalize your properties to specific beans which will be autowired to the concrete classes.
Spring annotation #ConfigurationProperties allows you to initialise simple POJO properties based on properties prefix.
First create your POJO which we will inject in the concrete services :
public class ServiceProperties {
private String config1;
private String config2;
//getters and setters
}
Then create a configuration class in a package scanned by spring :
#Configuration
public class ServicePropertiesConfiguration {
#Bean
#ConfigurationProperties(prefix = "service-a")
public ServiceProperties serviceAProperties() {
return new ServiceProperties();
}
#Bean
#ConfigurationProperties(prefix = "service-b")
public ServiceProperties serviceBProperties() {
return new ServiceProperties();
}
}
As you can see, prefix tells to spring where he has to search the properties. Your application.properties will look like this :
service-a.config1=serviceAConfig1
service-a.config2=serviceAConfig2
service-b.config1=serviceBConfig1
service-b.config2=serviceBConfig2
At this stage, you will have two beans of type ServiceProperties with specific values inside
The abstract service looks like this :
public abstract class AbstractService {
private final ServiceProperties serviceProperties;
protected AbstractService(ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
public void serviceMethod() {
//using config1 and config 2 values
// serviceProperties.getConfig1();
// serviceProperties.getConfig2();
}
}
In the concrete service, you have to use #Qualifier annotation with name of created bean
#Service
public class ConcreteServiceA extends AbstractService{
public ConcreteServiceA(#Qualifier("serviceAProperties") ServiceProperties serviceProperties) {
super(serviceProperties);
}
}
#Service
public class ConcreteServiceB extends AbstractService{
protected ConcreteServiceB(#Qualifier("serviceBProperties") ServiceProperties serviceProperties) {
super(serviceProperties);
}
}

How to use Mockito to populate POJO

I have a POJO class:
#Data #Document
public class RoomPreferences{
private TypeEnum roomType;
private BigDecimal minLen;
private BigDecimal maxLen;
private List<BigDecimal> defaultPrices;
}
I want to populate a RoomPreferences object at test and I am using Mockito, but my RoomPreferences object's fields are always null.
public class TestingClass {
#Mock private RoomPreferences roomPreferences;
#InjectMocks public RoomServiceImpl roomService;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
when(roomPreferences.getMinLen()).thenReturn(BigDecimal.valueOf(10));
...
}
}
What I read out of this testing class:
public class TestingClass {
#Mock private RoomPreferences roomPreferences;
#InjectMocks public RoomServiceImpl roomService;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
when(roomPreferences.getMinLen()).thenReturn(BigDecimal.valueOf(10));
...
}
}
is that roomService of type RoomServiceImpl has an attribute of type RoomPreferences. And this attribute will be injected with the mocked object "roomPreferences".
The mocked object won't be populated with any value of the real class.
What you do is define what the mock should do when a method (as in the real class) is called on it:
when(roomPreferences.getMinLen()).thenReturn(BigDecimal.valueOf(10));
If you wan to assing a value to a property then don't mock the class and use the real class instead.
I am guessing that the problem you are facing is setting the RoomPreferences attribute in the roomService object. If that is the case you should add that class to the question.

How to mock all the classes that implement the same interface while using autowire for Set in spring?

Having the following classes :
public class A {
#Autowired
private Set<IClient> clients;
}
public class B implements IClient { }
public class C implements IClient { }
#RunWith(MockitoJUnitRunner.class)
public class Atest {
#InjectMocks
A a;
#Mock
IClient clients;
}
How I can use mocks for the Set of interfaces that will include both class B and C?
Use constructor injection insted of field injection, then create two mocks, put them in a set, and call your constructor with that set. For example:
public class Atest {
private A a;
#Mock
private IClient mockAClient;
#Mock
private IClient mockBClient;
#Before
public void prepare() {
a = new A(new HashSet<>(Arrays.asList(mockAClient, mockBClient));
}
}

Spring inject dependency for constructors

I'm planning to create my objects in my Spring MVC using the below setup but How can I inject values to my MyService ie; instantiate the object with default value...
public class MyController {
private MyService myService;
#Autowired
public void setMyService(MyService aService) { // autowired by Spring
this.myService = aService;
}
#RequestMapping("/blah")
public String someAction()
{
// do something here
myService.foo();
return "someView";
}
}
MyService
class Myservice(){
String servicename;
public Myservice(servicename){
this.servicename = servicename;
}
}
Without Spring
MyService first = new MyService("firstservice");
MyService second = new MyService("secondservice");
Just declare your constructor with #Autowired to mark it as the constructor to use and its parameter with #Value to indicate the value to use.
#Autowired
public Myservice(#Value("example") String servicename){
Or use a placeholder
#Autowired
public Myservice(#Value("${placeholder.key}") String servicename){
Firstly, your exam are wrong on using Spring DI. To inject Myservice type to another You should declare MyService as a interface instead:
interface Myservice(){
public void foo();
}
After that, declare an implementation of this interface (again, use Spring DI to inject String type):
class BarService() implements Myservice{
String servicename;
#Autowired
public Myservice(#Value("servicename") String servicename){
this.servicename = servicename;
}
public void foo(){
}
}

How To Configure MongoDb Collection Name For a Class in Spring Data

I have a collection called Products in my MongoDB database, which is represented by the interface IProductPrice in my Java code. The following repository declaration causes Spring Date to look to the collection db.collection: Intelliprice.iProductPrice.
I want it to configure it to look in db.collection: Intelliprice.Products using an external configuration rather than putting an #Collection(..) annotation on IProductPrice. Is this possible? How can I do this?
public interface ProductsRepository extends
MongoRepository<IProductPrice, String> {
}
The only way you can currently achieve this is by annotating your domain class with #Document using the collection property to define the name of the collection instances of this class shall be persisted to.
However, there's a JIRA issue open that suggests adding a pluggable naming strategy to configure the ways class, collection and property names are handled in a more global way. Feel free to comment your use case and vote it up.
using answer from Oliver Gierke above,
working on a project where I need to create multiple collections for one entity, I wanted to use the spring repositories and needed to specify the entity to use before using the repository.
I managed to modify the repository collection name on demand using this system, it using SPeL. You can only work on 1 collection at a time though.
Domain object
#Document(collection = "#{personRepository.getCollectionName()}")
public class Person{}
Default Spring Repository:
public interface PersonRepository
extends MongoRepository<Person, String>, PersonRepositoryCustom{
}
Custom Repository Interface:
public interface PersonRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
implementation:
public class PersonRepositoryImpl implements PersonRepositoryCustom {
private static String collectionName = "Person";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
To use it:
#Autowired
PersonRepository personRepository;
public void testRetrievePeopleFrom2SeparateCollectionsWithSpringRepo(){
List<Person> people = new ArrayList<>();
personRepository.setCollectionName("collectionA");
people.addAll(personRepository.findAll());
personDocumentRepository.setCollectionName("collectionB");
people.addAll(personRepository.findAll());
Assert.assertEquals(4, people.size());
}
Otherwise if you need to use configuration variables, you could maybe use something like this? source
#Value("#{systemProperties['pop3.port'] ?: 25}")
A little late,
but I've found you can set the mongo collection name dynamically in spring-boot accessing the application configuration directly.
#Document(collection = "#{#environment.getProperty('configuration.property.key')}")
public class DomainModel {...}
I suspect you can set any annotation attribute this way.
The only comment I can add is that you have to add # prefix to the bean name:
collection = "#{#beanName.method()}"
for the bean factory to inject the bean:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config {
}
I struggled to figure it out..
COMPLETE EXAMPLE:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config implements Serializable {
#Id
private String uuid;
private String profile;
private String domain;
private String label;
private Map<String, Object> data;
// get/set
}
public interface ConfigRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
#Component("configRepositoryCustom")
public class ConfigRepositoryCustomImpl implements ConfigRepositoryCustom {
private static String collectionName = "config";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
#Repository("configurations")
public interface ConfigurationRepository extends MongoRepository<Config, String>, ConfigRepositoryCustom {
public Optional<Config> findOneByUuid(String Uuid);
public Optional<Config> findOneByProfileAndDomain(String profile, String domain);
}
usage in serviceImpl:
#Service
public class ConfigrationServiceImpl implements ConfigrationService {
#Autowired
private ConfigRepositoryCustom configRepositoryCustom;
#Override
public Config create(Config configuration) {
configRepositoryCustom.setCollectionName( configuration.getDomain() ); // set the collection name that comes in my example in class member 'domain'
Config configDB = configurationRepository.save(configuration);
return configDB;
}
I use static class and method in SpEL;
public class CollectionNameHolder {
private static final ThreadLocal<String> collectionNameThreadLocal = new ThreadLocal<>();
public static String get(){
String collectionName = collectionNameThreadLocal.get();
if(collectionName == null){
collectionName = DataCenterApiConstant.APP_WECHAT_DOCTOR_PATIENT_COLLECTION_NAME;
collectionNameThreadLocal.set(collectionName);
}
return collectionName;
}
public static void set(String collectionName){
collectionNameThreadLocal.set(collectionName);
}
public static void reset(){
collectionNameThreadLocal.remove();
}
}
In Entity class ,#Document(collection = "#{T(com.test.data.CollectionNameHolder).get()}")
And then ,use
CollectionNameHolder.set("testx_"+pageNum)
in Service , and
CollectionNameHolder.reset();
Hope it helps you.

Categories