How to invoke injected bean methods from instance initializer block - java

I am using Spring and SpringMvc and I want to invoke a method of a service in some controller, and both them
are managed by Spring. When I try to do this I got NullPointerException, but I find that the Service's constructor truly called before I invoke this method.
I think maybe this Service has been added in Spring, but the property here in this controller has not be set.
How can I get it from SpringContext?
My code like this:
#Controller
#RequestMapping("/test")
public class SomeController {
#Resource
private SomeService someService;
{
someService.serviceMethod();
//And something more
}
#RequestMapping("/someMethod")
private void controllerMethod(){
}
}

You are trying to call spring-injected service from an initializer block. The resource you annotated with #Resource has not been injected into the controller by spring yet! That's why you are getting the NullPointerException
Create a separate function annotated with #PostConstruct instead.
#Controller
#RequestMapping("/test")
public class SomeController {
#Resource
private SomeService someService;
#PostConstruct
public void postConstruct(){
someService.serviceMethod();
}
}
That "postConstruct" method will be invoked after the bean (i.e. the controller) has been created and all the dependencies (including SomeService) has been injected into the bean. Of course, it satisfies your requirement that it should only be called once.

Your specific problem has to do with the way Java handles Instance Initalizer Blocks:
The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.
If you want to stick with the initializer block you can solve the problem by adding a constructor to your controller and inject your service into it:
#Autowired
public SomeController(SomeService someService) {
this.someService = someService;
}
Alternatively, your could remove the initializer block and add a #PostConstruct annotated setup method.
#PostConstruct
public void setupSomeController(){
someService.serviceMethod();
}

Related

Why can't I use a JPA repository in more than one controller?

I am building a web application using Java Spring Boot. I'm trying to use a JPA repository in two different controllers. However, it only works inside one controller and not in the other one.
MyRepository.java
public interface MyRepository extends JpaRepository<Favorite, Long> { }
It works fine in OneController without any problem.
OneController.java
#RestController
#RequiredArgsConstructor
public class OneController {
#Autowired
private final MyRepository myRepository;
}
However, when I try to also use it in AnotherController, I get the following message: Variable 'myRepository' might not have been initialized.
AnotherController.java
#Controller
#RequiredArgsConstructor
public class AnotherController {
#Autowired
private MyService myService;
#Autowired
private final MyRepository myRepository;
private final DateUtil dateUtil;
public AnotherController(DateUtil dateUtil) {
this.dateUtil = dateUtil;
}
}
What's the problem here? One is RestController and the other is Controller, but that doesn't seem to be an issue.
EDIT:
Sorry for the confusion. I have omitted a constructor in AnotherController in my original question, so I have added it.
For some reason, after removing a constructor, I no longer see the message Variable 'myRepository' might not have been initialized. Without a constructor, everything works fine. Why is it that?
Also, #Autowired doesn't seem to be required for repositories, but the service needs it. Without the annotation, the whole page won't work. Why is it the case?
When you're using field autowiring, fields cannot be final. First the instance is created (with null values), then the autowired fields get updated. That can't be done if they're final.
Since you're using #RequiredArgsConstructor, you can just remove the #Autowired. This will make Spring use constructor autowiring; it will find the dependencies when calling the constructor.

SpringBoot - Mock stateful object created via "new" keyword in integration test

I have an SpringBoot application that consists of a Controller layer and a Service layer.
MyController has access to MyService via #Autowired, while MyService has a method that creates a new instance of MyClass, which is imported from an external dependency.
import externaldependency.MyClass;
#Service
public class MyService {
public void myMethod() {
MyClass c = new MyClass();
c.doStuff();
c.doOtherStuff();
c.doMoreStuff();
}
}
I use new to create the instance because MyClass holds state; it has several methods that change its state during the execution of myMethod until I get the desired result, therefore I shouldn't autowire it nor inject it in the constructor, since that would use a single instance of this class for every call to myMethod. I understand that "Prototype" beans exists, but as far as I know, even if I declare MyClass as a prototype bean and inject it to MyService via #Autowired, the service would still use the same instance of MyClass during execution, so ultimately I decided to just use new.
Recently I've been trying to do an integration test, calling my Controller layer, which in turn will call my Service layer, which in turn will create an instance of MyClass. The problem is that one of the many methods of MyClass internally calls an external service, which shouldn't be part of the test itself, so I would like to mock this class.
I understand that mocking is done via dependency injection, but in this case I can't do that. Is there an alternative way to mock MyClass, or is it simply not possible with this setup? If not, then how could I refactor my code to make mocking possible in this particular case?
Many thanks in advance.
I'll answer my own question.
Since MyClass holds state, it shouldn't be autowired to the service nor injected via its constructor, but rather new instances should be created as needed. However, whan can be autowired is a "factory" which creates these instances:
#Component
class MyClassFactory {
public MyClass getInstance() {
return new MyClass();
}
}
Therefore, the service becomes:
#Service
public class MyService {
#Autowired
private MyClassFactory myClassFactory;
public void myMethod() {
// MyClass c = new MyClass();
MyClass c = myClassFactory.getInstance();
c.doStuff();
c.doOtherStuff();
c.doMoreStuff();
}
}
In practice, using a factory is the same thing as just using new; I'm getting a new instance either way. The benefit comes during testing; now I can mock what the factory returns, since the factory is part of Spring's application context:
#SpringBootTest
public class MyTest {
#MockBean
private MyClass myClassMock;
#MockBean
private MyClassFactory myClassFactoryMock;
#Test
public void myTests() {
// Make a mock of MyClass, replacing the return
// values of its methods as needed.
given(
myClassMock.doStuff()
).willReturn(
"Something useful for testing"
);
// Then make a mock of the factory, so that it returns
// the mock of the class instead of a real instance.
given(
myClassFactoryMock.getInstance()
).willReturn(
myClassMock
);
// Do the tests as normal.
}
}
Probably not the most elegant solution, but at least solved my current problem.

passing properties field using #value annotation to mocked method is null

I am unable to pass the field by reading from application-test.properties file from test to the mocked method.
#RunWith(SpringRunner.class)
#TestPropertySource("classpath:application-test.properties")
public class ReportImplTest {
#Mock
private Dependencies dependencies;
#InjectMocks
private ReportImplTest underTest;
#Test
public void testgetReports() {
List<String> reps= underTest.getReports(anyString());
}
}
Here is the actual class of the mocked method
#Component
public class ReportImpl {
#Value("${REP_PROPNAME}")
String reppropname;
public List<String> getReports(String rep){
return staticUtilityclass.process(staticUtilityclass.transform(reppropname,"Reports"));
}
}
reppropname is coming as null in the getReports method. Test is executing in test context wheres the ReportImpl class will be in application context. Is there a way to get the value of the reppropname.
I tried used #ContextConfiguration (#ContextConfiguration(classes={ApplicaitonBootStarter.class)}
it is working , but it loads all the beans and dependencies.
Any other way to get the reppropname?
The reason why the value is not injected here is that you don't provide the configuration to your test class. Spring just doesn't know how to build your bean.
So, as you mentioned you have to annotate the test class with #ContextConfiguration. If you don't want to build the entire context with all the beans, you can provide create a test configuration and provide there only the needed beans.
#Configuration //can be as well annotated with #TestConfiguration
#ComponentScan("package.to.scan")
public class TestConfiguration {
}
And now provide this class to your test
#RunWith(SpringRunner.class)
#TestPropertySource("classpath:application-test.properties")
#ContextConfiguration(classes = TestConfiguration.class)
public class ReportImplTest {
........
}
But there is one more thing. Assuming that you have a #Before method that performs MockitAnnotations.initMocks(this);, you still have your object-under-test declared only with #InjectMocks. What does it mean? It means that if you don't initialize this object by yourself, mockito will take care of it and will initialize with using the available constructor, and in this case, spring won't inject the #Value annotated field. What you need to do, is to annotate you object-under-test with #Autowired so spring will initialize it before mockito will try to take care of it:
#InjectMocks
#Autowired
private ReportImplTest underTest;

#autowired on method in Spring

I am learning Spring, and as far as I understand, when we use #annotation on a method which has a generic name (not a setter method), then the method's arguments are autowired.
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
#Autowired
public void prepare(MovieCatalog mC,
CustomerPreferenceDao cPD) {
this.movieCatalog = mC;
this.customerPreferenceDao = cPD;
}
// ...
}
So, here, the fields movieCatalog and customerPreferenceDao are autowired with the values of mC and cPD. What I fail to understand is how is this different from the same method without the "#autowired".
I understand #autowired when applied to a Field name, but not able to understand when the values are explicitly being passed to the method (either a setter or any other method), then what does Spring do special?
quite late answer, but here it is:
any method annotated with #Autowired is a config method. It is called on bean instantiation after field injection is done. The arguments of the method are injected into the method on calling.
The #autowired method you have provided does not need to be invoked from within your own code. It will be invoked by the Spring framework. Imagine a setter method in a class that is declared as #autowired. The setters parameter is provided by Spring. So before using the class object instance you do not have to write code to call the setter method, just like you don't need to provide the parameters of an #autowired constructor. There are lots of things you can do with autowired. Check out these two examples:
Calling #autowired method
Other uses of #autowired
One of the advantages of #autowired is that you do not have to instantiate the object, as Spring Framework will do that for you. But not only that, an #autowired object is managed by the Spring Framework, so you don't have to worry about handling of object instances, cause Spring does it for you. So it saves a lot of coding and unnecessary code that is often used to keep track of and manage the objects. Hopefully this was of some help.
#autowired on a method is used for setter-injection. it is not different from field injection besides that the beans is not that dependent on the spring-container, you could instantiate and wire it yourself as well.
one reason to use it is if you have circular dependencies.
another use of setter injection is that it allow re-injection of (a possibly optional) dependency at a later time (JMX).
public class MovieRecommender {
#Autowired
private MovieCatalog movieCatalog;
#Autowired
private CustomerPreferenceDao customerPreferenceDao;
}
Now you have no need for a prepare method!

#Autowired does not create members in constructor

I have a bean that is created from context, after which a autowired member is created:
#Service
public class FileDownloadService extends WFWFileDownloadService {
#Autowired
ConfigurationManager configurationManager;
When I use in code manual constructor call:
FileDownloadService fileDownloadService = new FileDownloadService();
I see configurationManager is null, so I have to make manual wiring:
public FileDownloadService() {
configurationManager = new ConfigurationManagerImpl();
}
What am I doing wrong to make autowiring working with manual creating?
When you call the constructor directly, you're just creating an object and not a bean. The support of the #Autowired annotation is a feature of bean.
Ask the Spring context for the bean.
If you attempt to get the bean by using new operator, you will get all autowired beans inside that class as null.
Your service class is annotated with #Service, so to use it you should autowire this service class also.
Sample code to access service bean in other classes is :
#Controller or #Component
public class OtherClass {
#Autowired FileDownloadService fileService;
public void download() {
fileService.downloadFile();
}
}
In order this class to be able to autowire other beans, this class itself should be annotated with #Controller or #Component.
The Answer is simple:
If you manually create the Object, there is no wiring involved. How should Spring know, that you are in need for wiring? Instead of using new, you could use the getBean() method of the application context (Bean Factory). http://static.springsource.org/spring/docs/current/javadoc-api/index.html?org/springframework/beans/factory/BeanFactory.html
http://static.springsource.org/spring/docs/current/javadoc-api/index.html?org/springframework/beans/factory/BeanFactory.html

Categories