Can spring inject sample bean twice with different parameterization? - java

Consider a code:
public class MyProcessor<T extends GenericData> implements ProcessorInterface<T> {
#Autowired
private List<SomeCriteria<T>> criterias;
#Override
public long calculate(T data) {
long result = 0;
for (SomeCriteria c : criterias) {
result += c.calculate(data);
}
return long;
}
}
So the difference only in SomeCriteria implementation and GenericData. E.g. for one GenericData there are several
SomeCriteria. So if there are 3 GenericData is it possible to write a code like that:
public DataService {
#Autowire
private MyProcessor<DataOne> processorOne;
#Autowire
private MyProcessor<DataTwo> processorTwo;
#Autowire
private MyProcessor<DataThree> processorThree;
}
Without writing implementation for processor each time?

Yes, it is possible. As of Spring 4.0 you can do thing such as
#Autowired
private Store<String> s1; // Injects the stringStore bean
#Autowired
private Store<Integer> s2; // Injects the integerStore bean
The example above was copied from the Spring Framework 4.0 and Java Generics blog post by Phil Webb on the Spring web site. Please read it for more details.

You can use #Qualifier annotation for create more than one bean of the same type. I hope this will helpfull to you.
public DataService {
#Qualifier
private MyProcessor<DataOne> processorOne;
#Qualifier
private MyProcessor<DataTwo> processorTwo;
}

Related

Using Polymorphism in Spring Boot services?

In my Spring Boot app, I am thinking of using an approach as the following interface and service implementations:
PDFService:
public interface PDFService {
String createPdf(UUID uuid);
}
BrandPDFService:
#Service
#RequiredArgsConstructor
public class BrandPDFService implements PDFService {
private final BrandService brandService;
#Override
public String createPdf(UUID uuid) {
Brand brand = brandService.findByUuid(uuid);
// ... code omitted for brevity
return generateHtml(brand);
}
}
ProductPDFService:
#Service
#RequiredArgsConstructor
public class ProductPDFService implements PDFService {
private final ProductService productService;
#Override
public String createPdf(UUID uuid) {
Product product = productService.findByUuid(uuid);
// ... code omitted for brevity
return generateHtml(product);
}
}
For using these services:
// brand way
PDFService pdfService = new BrandService();
pdfService.createPdf(uuid);
// product way
PDFService pdfService = new ProductService();
pdfService.createPdf(uuid);
So, I think I need to use generic and pass it to PDFService and then their implementations, but I am not sure how to make it properly (using generic or passing via constructor). So, in order to use createPdf efficiently without repeating code (I know I can also use Template Pattern method, but I just wanted to know polymorphism side) how should I apply polymorphism to these Spring Boot Services properly?
Since BrandPDFService and ProductPDFService are Spring beans (because you annotated them with the #Service annotation), you should not be instantiating them yourself by using new. Instead, you should let Spring autowire them into the class where you are using them.
Because they are both implementations of interface PDFService, when you autowire them, you need to have something to let Spring distinguish them. Otherwise, if the field you are autowiring them in is of type PDFService, Spring won't know which implementation of the interface to autowire. You can give the beans names and use the #Qualifier annotation:
#Service("brandPDFService")
public class BrandPDFService implements PDFService { ... }
#Service("productPDFService")
public class ProductPDFService implements PDFService { ... }
// Example controller where you autowire them
#RestController
public class MyController {
#Autowired
#Qualifier("brandPDFService")
private PDFService brandPDFService;
#Autowired
#Qualifier("productPDFService")
private PDFService productPDFService;
// ...
}
So, I think I need to use generic and pass it to PDFService and then their implementations
I don't know why you think you need to use generics; this doesn't have anything to do with generics.

spring boot - integration test autowired interface no such bean found

I have a spring-boot app that now needs to support multiple Object stores and selectively use the desired store based on the environment. Essentially what i have done is create an interface that each store repository then implements.
I have simplified the code for the examples.
I have created 2 beans for each store type based on the spring profile determining the env:
#Profile("env1")
#Bean
public store1Sdk buildClientStore1() {
return new store1sdk();
}
#Profile("env2")
#Bean
public store2Sdk buildClientStore2() {
return new store2sdk();
}
in the service layer I have autowired the interface and then in the repositories i have used #Profile to specify which instance of the interface to use.
public interface ObjectStore {
String download(String fileObjectKey);
...
}
#Service
public class ObjectHandlerService {
#Autowired
private ObjectStore objectStore;
public String getObject(String fileObjectKey) {
return objectStore.download(fileObjectKey);
}
...
}
#Repository
#Profile("env1")
public class Store1Repository implements ObjectStore {
#Autowired
private Store1Sdk store1client;
public String download(String fileObjectKey) {
return store1client.getObject(storeName, fileObjectKey);
}
}
When I start the application with the configured "env" this actually runs as expected. however when running the test I get the "no qualifying bean of type ObjectStore. expected at least 1 bean which qualifies as autowire candidate."
#ExtendWith({ SpringExtension.class })
#SpringBootTest(classes = Application.class)
#ActiveProfiles("env1,test")
public class ComposerServiceTest {
#Autowired
private ObjectHandlerService service;
#Test
void download_success() {
String response = service.getObject("testKey");
...
}
}
As noted in the #ActiveProfile on the test class there are some other environments e.g. dev,test,prod. I have tried playing around with Component scan, having impl and interface in the same package, etc, to no success. I feel like I am missing something obvious with the test setup. But could be something with my overall application config? my main aim with the solution is to avoid having something a long the lines of
if (store1Sdk != null) {
store1Sdk.download(fileObjectKey);
}
if (store2Sdk != null) {
store2Sdk.download(fileObjectKey);
}
Try #ActiveProfiles({"env1", "test"}).
Activate multiple profiles using #ActiveProfiles and specify profiles as an array.
this probrom because Store1Repository use #Profile("env1"), when you use #test,this class not invoke. try delete #Profile("env1") of Store1Repository.
if you use #test, both of store1Sdk/store2Sdk don't instanse, try add default instanse.eg:
#Bean
public store2Sdk buildClientStoreDefault() {
return new store2sdk();
}

How to mock Optional bean in spring boot?

In my SpringBootApplication, I have a bean which injects another optional bean (like shown below)
#Service
public class A {
//B is another #Component from one of the dependencies
private Optional<B> b;
...
...
}
I am writing an integration test for class A where I need to #MockBean Optional<B> b. However since Optional is a final class, spring mockito raises following error
Cannot mock/spy class java.util.Optional
- final class
Is there a way around this? Any help is much appreciated.
You can use Optional.of(b).
If you use mockito with annotations, then you can't use #InjectMocks because your optional will not be known for mockito. You have to create your service A yourself. Something like this:
#RunWith(MockitoJUnitRunner.class)
public class ATest {
#Mock
private B b;
private A a;
#Before
public void setup() {
a = new A(Optional.of(b));
}
}
You should actually mock the actual bean using #MockBean or #MockBeans or TestConfig class and Autowire the Optional with mocked bean
#Autowired
private Optional<B> OptionalB;
#MockBean
private B b;
Although Lino's above answer works perfectly, I chose not to modify the production code to make the test work. I've instead modified my code like below:
#Service
public class A {
#Autowired(required = false)
private B b;
...
...
}

Unit test of Spring boot service with private fields and #PostConstruct

I have a Spring boot service defined like this
#Service
public class MyService {
private String field1;
private String field2;
#Autowired
private AnotherService anotherService
#PostConstruct
public void init() {
anotherService.initField1(field1);
anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
How should I write a unit test for foo. Well, it's more about how to deal with class fields and the PostConstruct methods.
Thanks!!
EDIT:
Added AnotherService as a field as well.
The following example shows a #Service Bean that uses constructor injection to obtain a required AnotherService bean:
#Service
public class MyService {
private String field1;
private String field2;
private final AnotherService anotherService;
public MyService(AnotherService anotherService) {
this.anotherService = anotherService;
this.anotherService.initField1(field1);
this.anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
Note you can omit the #Autowired becuase MyService has one constructor. See here for more info.
testing with Spring
Use the #RunWith(SpringRunner.class) and #SpringBootTest to inject MyService and start using it:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyServiceTest {
#Autowired
private MyService service;
#Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
testing without Spring
public class MyServiceTest2 {
private MyService service;
#Before
public void setUp() {
service = new MyService(new AnotherService.Fake());
}
#Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
Here Fake is a fake implementation of the AnotherService interface which allows you to have a pure unit test.
Writing good, testable code can be hard. There are some pitfalls waiting for everyone to fall into sooner or later.
As a rule of thumb, try to avoid field level injection, use constructor parameter injection instead:
#Service
public class MyService {
private AnotherService anotherService;
#Autowired
MyService (AnotherService anotherService) {
this.anotherService = anotherService;
}
}
This is the cleanest solution. You can call the constructor from your tests, spring will inject dependencies the same way at runtime. So there is no difference to deal with.
The same goes for any life cycle constructs like #PostConstruct. If you can avoid them, do it. Let the constructor handle it. If you absolutely have to keep them around, well, the only logical solution is to manually call them from your test code.
Now, how to setup services that at runtime would be autowired by the container?
For unit testing, you basically have three options (in no particular order):
If the required service is rather simple and can easily be constructed, create and pass it as the framework would do.
If the service has a limited interface that does not change too often, create a fake service.
Use a mocking lib like mockito (spring-boot-test provides it by default).

Spring inject without autowire annotation

I find some answer: https://stackoverflow.com/a/21218921/2754014 about Dependency Injection. There isn't any annotation like #Autowired, #Inject or #Resource. let's assume that there isn't any XML configuration for this example TwoInjectionStyles bean (except simple <context:component-scan base-package="com.example" />.
Is it correct to inject without specify annotation?
From Spring 4.3 annotations are not required for constructor injection.
public class MovieRecommender {
private CustomerPreferenceDao customerPreferenceDao;
private MovieCatalog movieCatalog;
//#Autowired - no longer necessary
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
#Autowired
public setMovieCatalog(MovieCatalog movieCatalog) {
this.movieCatalog = movieCatalog;
}
}
But you still need #Autowired for setter injection. I checked a moment ago with Spring Boot 1.5.7 (using Spring 4.3.11) and when I removed #Autowired then bean was not injected.
Yes, example is correct (starting from Spring 4.3 release). According to the documentation (this for ex), if a bean has single constructor, #Autowired annotation can be omitted.
But there are several nuances:
1. When single constructor is present and setter is marked with #Autowired annotation, than both constructor & setter injection will be performed one after another:
#Component
public class TwoInjectionStyles {
private Foo foo;
public TwoInjectionStyles(Foo f) {
this.foo = f; //Called firstly
}
#Autowired
public void setFoo(Foo f) {
this.foo = f; //Called secondly
}
}
2. At the other hand, if there is no #Autowire at all (as in your example), than f object will be injected once via constructor, and setter can be used in it's common way without any injections.

Categories