Spring inject without autowire annotation - java

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.

Related

Injecting spring beans in non managed objects

the below bean of Class I want to inject in other non-managed bean, but it is not working as expected
#Component
#Setter
#Getter
public class AbstractLayoutProperties {
#Value("${spring.application.name}")
private String appName;
#Autowired
S3Service s3Service;
#Autowired
S3Client s3Client;
}
Below is the class which is not managed by the spring, but I am using #Configurable
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#EnableSpringConfigured
public class OverlayServiceImpl
implements GenericVehicleOverlayService<T, R> {
public findOnlyActive(){
appName = layoutProperties.getAppName(); // throwint NullPointerException beacuse the object not injected properly
}
#Autowired
AbstractLayoutProperties layoutProperties;
}
One more point, findOnlyActive method I am not calling directly, I am calling this from another service, lets say
#Service
public class OtherService{
public void findActive(){
OverlayServiceImpl impl=new OverlayServiceImpl();
impl.findOnlyActive();
}
#Autowired
OtherRepository otherRepo;
}
Problem statrement:
when impl.findOnlyActive(); is executed, it should inject all the required beans inside OverlayServiceImpl. In the same class I have two beans which are autowired, it seems none of them injected, this is the reaosn that every time I am encountering Nullpointer exception. so my question is how do I make it work, what are the steps and action I need to take so that spring will inject the dependencies in non managed object i,e OverlayServiceImpl.

#Autowired annotaded method vs #PostConstruct annotaded method

What is the difference between using #Autowired or #PostConstruct on a method since they offer the same result (according to what I have understood from different sources)
UPDATE:
Here is an example of my class in which I get the same result if I use #Autowired or #PostCosntruct to annotate the method configClient()
#Service
public class AwsSTSService {
#Autowired
private AwsConfiguration awsConfiguration;
public CustomCredentials getCredentials() {
......
return customCredentials;
}
#Autowired // or #PostConstruct
private void configClient() {
CustomCredentials customCredentials = getCredentials();
awsConfiguration.setAwsAccessKey(customCredentials.getAccessKeyId());
awsConfiguration.setAwsSecretKey(customCredentials.getSecretAccessKey());
awsConfiguration.setExpiration(customCredentials.getExpiration());
awsConfiguration.setSessionToken(customCredentials.getSessionToken());
}
}
Actually, they don't have anything in common. #Autowired could be used to inject any dependency in your beans (components), on the other hand, #PostConstruct can be used on methods of your beans, and spring boot will call that method after that bean was created (for purposes like populating a database or calculating some initial data).
You can see how this article used these two annotations in its example codes https://www.baeldung.com/spring-postconstruct-predestroy#postConstruct

Difference between #Autowired final setter and non-final setter in spring

Assuming:
abstract class CommonService {
protected VipMapper vipMapper;
#Autowired
public final void setVipMapper(VipMapper vipMapper) {
this.vipMapper = vipMapper;
}
}
#Service
public class BookService extends CommonService {
public int find() {
return vipMapper.findVip(); // return 100
}
}
#SpringBootTest
class BookServiceTest {
#Autowired
private BookService bookService;
#Test
void find() {
VipMapper v = new VipMapper() {
#Override
public int findVip() { // This method will not execute
return 10;
}
};
bookService.setVipMapper(v);
int find = bookService.find(); // find = 100 (not 10)
}
}
1. What is the reason I cannot inject VipMapper when setVipMapper method is final and I can inject when setVipMapper method is not final?
2. How can I inject VipMapper in runtime but still use #Autowired final setter?
Update
I'm using Spring + Mybatis
Source code:
https://bitbucket.org/nguyentanh/stackoverflow
Using the above code, when run that test for findVipCustomerTop3, I get an error connection. But when to remove final in CommonService.java (or #Transactional in BookService.java), the test is success
You issue is not with autowiring. VipMapper get autowired correctly and you are trying to replace the mapper manually via bookService.setVipMapper(v); in your test. It does not replace the vipMapper you passed. To Check this behaviour, define a getter in your service to get the vipMapper and it will return the original vipMapper which was autowired by spring.
Just remember you are not working with an instance of your original BookService class, you are working with a sub class of BookService which is run time generated .
Your original question missed an important piece of info which is #Transactional annotation in your service. As soon as #Transactional annotation is there, Spring actually need to create a proxy. Now spring will choose JDK dynamic proxy or CGLIB proxy to create the proxy for your book service. Since your Service does not have an interface, JDK dynamic proxy choice is not possible so spring is left with CGLIB proxy.
CGLIB proxy has its limitations.
With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses
Here is technique if you want to actually replace it. Add the following in your test class instead of the line bookService.setVipMapper(v);
((BookService)AopProxyUtils.getSingletonTarget(bookService))
.setVipMapper(v);
The above option is doing it hardcore way but good for understanding the concept. There is another option telling spring to create BookService in your test with a mock vipMapper autowired when it creates BookService.
#SpringBootTest
class BookServiceTest {
#Autowired
private BookService bookService;
#MockBean
private VipMapper vipMapper;
#Test
void find() {
when(vipMapper.findVip()).thenReturn(10);
bookService.setVipMapper(v);
int find = bookService.find();
}
}
Reference
https://docs.spring.io/spring/docs/5.2.8.RELEASE/spring-framework-reference/core.html#aop-proxying
From my understanding, you are using #autowired for setVipMapper() so it already injected VipMapper with default findVip() returing 100. Therefore, defining setVipMapper() as final won't change the value you pass through anymore

Spring bean not being picked up?

I have a java spring project with the below service:
#Slf4j
public class DialogFlowService {
private String projectId;
private String sessionId;
private String languageCode;
public DialogFlowService(DialogFlowConfig dialogFlowConfig) {
log.info("aaa" + dialogFlowConfig.languageCode);
this.projectId = dialogFlowConfig.projectId;
this.sessionId = dialogFlowConfig.sessionId;
this.languageCode = dialogFlowConfig.languageCode;
}
}
The constructor takes the below class as an argument:
#Configuration
#ConfigurationProperties(prefix = "dialog-flow")
public class DialogFlowConfig {
#NotNull
public String projectId;
#NotNull
public String sessionId;
#NotNull
public String languageCode;
}
In theory, this should be instantiated by the below bean:
#Bean
public DialogFlowService dialogFlowService() {
return new DialogFlowService(new DialogFlowConfig());
}
However in practice, when I try to log one of the constructor arguments, it comes up as null. Am I missing something?
I think changing your third code snippet like this would do the trick.
#Bean
public DialogFlowService dialogFlowService(DialogFlowConfig dialogFlowConfig) {
return new DialogFlowService(dialogFlowConfig);
}
The DialogFlowConfig class is already marked as #Configuration. Hence it is managed by the Spring Application context. So you dont have to explicitly make an object using the new keyword. You can just take it as a parameter
Try putting #EnableConfigurationProperties(DialogFlowConfig.class) into you Spring Application class.
This is similar to what's being described here: Why is my Spring #Autowired field null?
Essentially, by instantiating the DialogFlowConfig instance yourself and not handing it over to Spring, you're preventing Spring from post processing it and injecting ConfigurationProperties property values.
Instead create a #Bean method for DialogFlowConfig and use the corresponding Spring bean to create your DialogFlowService. For example
#Bean
public DialogFlowService dialogFlowService(DialogFlowConfig dialogFlowConfig) {
return new DialogFlowService(dialogFlowConfig);
}
#Bean
public DialogFlowConfig dialogFlowConfig() {
return new DialogFlowConfig();
}
Spring will use the #Bean annotated dialogFlowConfig() factory bean method to instantiate and process the corresponding instance (setting its fields). It'll then use it with the dialogFlowService() factory method.
Note: If you do it this way, you'll need to remove #Configuration annotation from DialogFlowConfig, assuming you were previously component scanning it. Alternatively, if you were correctly component scanning, you don't even need the additional #Bean annotated dialogFlowConfig() factory method I proposed. Just inject the DialogFlowConfig bean declared by its #Configuration annotation in the dialogFlowService method.
First of all you need to use either setter getter in DialogFlowConfig or #Value annotation on all properties. You also need to annotate your service class DialogFlowService with #Service stereotype

Autowire Bean with constructor parameters

I've got a bean with constructor parameters which I want to autowire into another bean using annotations. If I define the bean in the main config and pass in the constructor parameters there then it works fine. However, I have no main config but use #Component along with #ComponentScan to register the beans. I've tried using #Value property to define the parameters but then I get the exception No default constructor found;
#Component
public class Bean {
private String a;
private String b;
public Bean(#Value("a") String a, #Value("b") String b)
{
this.a = a;
this.b = b;
}
public void print()
{
System.out.println("printing");
}
}
#Component
public class SecondBean {
private Bean bean;
#Autowired
public SecondBean(Bean bean)
{
this.bean = bean;
}
public void callPrint()
{
bean.print();
}
}
The constructor for Bean needs to be annotated with #Autowired or #Inject, otherwise Spring will try to construct it using the default constructor and you don't have one of those.
The documentation for #Autowired says that it is used to mark a constructor, field, setter method or config method as to be autowired by Spring's dependency injection facilities. In this case you need to tell Spring that the appropriate constructor to use for autowiring the dependency is not the default constructor. In this case you're asking Spring to create SecondBean instance, and to do that it needs to create a Bean instance. In the absence of an annotated constructor, Spring will attempt to use a default constructor.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html

Categories