Injecting dependency to a Spring bean - java

I would like to inject a singleton object dependency to a Spring bean. The catch is that I can't access and modify the class whose object I want to be injected. Let me describe on the example.
So I have my interface, and the implementation of this interface, like the following.
public interface MyServiceProxy {
String BEAN_NAME = "MyServiceProxy";
Data getData(String dataId);
}
public class MyServiceProxyImpl implements MyServiceProxy {
private final MyServiceClient client;
public MyServiceProxyImpl(MyServiceClient client) {
this.client = client;
}
#Override
public Data getData(String dataId) {//...}
Then in my Configuration class, I am creating a bean, but I need to pass it the MyServiceClient object in the constructor, and the catch is that I can't make MyServiceClient a bean because it's from external package and I can't modify it.
#Configuration
public class MyServiceProxyConfiguration {
#Bean(name = MyServiceProxy.BEAN_NAME)
public MyServiceProxy getMyServiceProxy(MyServiceClient client) { // could not autowire client
return new MyServiceProxyImpl(client);
}
}
So what I would like to do, is being able to pass/autowire an argument to getMyServiceProxy bean. Currently IntelliJ is giving me an error Could not autowire client. How can this be achieved?
UPDATE
Would something like the following work? Because IntelliJ is still reporting an "could not autowire" error. So if I created a bean method that returns the client I want injected, and then add #Inject annotation to the method where I want it injected.
public class MyServiceClientBuilder {
private final ClientBuilder builder;
public MyServiceClientBuilder(ClientBuilder builder) {
this.builder = builder;
}
#Bean
public MyServiceClient build() {
return builder.newClient();
}
#Configuration
public class MyServiceProxyConfiguration {
#Inject
#Bean(name = MyServiceProxy.BEAN_NAME)
public MyServiceProxy getMyServiceProxy(MyServiceClient client) { // could not autowire client
return new MyServiceProxyImpl(client);
}
}

You can define MyServiceClient as a separate bean in your configuration file like this:
#Configuration
public class MyServiceProxyConfiguration {
#Bean
public MyServiceClient getMyServiceClient () {
return MyServiceClient.getInstance(); //initiate MyServiceClient
}
#Bean(name = MyServiceProxy.BEAN_NAME)
public MyServiceProxy getMyServiceProxy(MyServiceClient client) {
return new MyServiceProxyImpl(client);
}
}
I have not tested this code, but it should work.

Related

Spring create generic service multiple times using generic in constructor

I have a service that uses some object as a generic
#Component
#RequiredArgsConstructor
public class SomeGenericService<T extends Base> {
private final T base;
public void someWork(String info) {
base.someAction(info);
}
}
I also have 3 Base implementations marked with #Component(Base1, Base2, Base3)
I want spring itself to create a service with the generic it needs, for the following example
#Component
#RequiredArgsConstructor
public class Runner implements CommandLineRunner {
private final SomeGenericService<Base1> s1;
private final SomeGenericService<Base2> s2;
private final SomeGenericService<Base3> s3;
#Override
public void run(String... args) throws Exception {
String someString = "text";
s1.someWork(someString);
s2.someWork(someString);
s3.someWork(someString);
}
}
But after the launch, the spring does not understand what I want from it.
Parameter 0 of constructor in SomeGenericService required a single bean, but 3 were found:
- base1: defined in file [Base1.class]
- base2: defined in file [Base2.class]
- base3: defined in file [Base3.class]
Is it possible to set this to automatic, without manually configuring it via the #Bean annotation for each service?
You need to define how those beans should be injected. It's a good practice to have some #Configurations for this purpose. Something like:
#Configuration
#Import({
Base1.class,
Base2.class,
Base3.class
})
public class SomeConfig {
#Bean
SomeGenericService<Base1> someGenericService1() {
return new SomeGenericService(new Base1());
}
#Bean
SomeGenericService<Base2> someGenericService2() {
return new SomeGenericService(new Base2());
}
#Bean
SomeGenericService<Base3> someGenericService3() {
return new SomeGenericService(new Base3());
}
}

Cannot inject two fields of same interface when using #Profile annotation on bean configuration method

I use Spring 5.1.4.RELEASE and have a trouble injecting two fields of same interface via constructor when using #Profile annotation on bean configuration methods. I have a simple Publisher component like follows:
#Component
public class Publisher {
private final MyClient prodClient;
private final MyClient testClient;
#java.beans.ConstructorProperties({"prodClient", "testClient"})
public Publisher(MyClient prodClient, MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}
}
When I mark whole configuration with #Profile annotation, then it works as expected:
#Profile(Profiles.MY_CLIENT)
#Configuration
public class ClientConfig {
#Bean
public MyClient prodClient() {
return new HttpClient("prod.client.channel");
}
#Bean
public MyClient testClient() {
return new HttpClient("test.client.channel");
}
}
The above configuration is OK, but the problem occurs when I want to have the #Profile annotation only on some methods inside a configuration class:
#Configuration
public class ClientConfig {
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient prodClient() {
return new HttpClient();
}
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient testClient() {
return new HttpClient();
}
// some other beans...
}
Then I get an error during startup:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.test.Publisher required a bean of type 'com.test.MyClient' that could not be found.
UPDATE:
It's solved. It was my mistake. I had two more bean methods annotated with different #Profile for integration tests, but they had the same name for production code (annotated with Profiles.MY_CLIENT profile):
#Configuration
public class ClientConfig {
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient prodClient() {
return new HttpClient();
}
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient testClient() {
return new HttpClient();
}
// ... other beans
#Profile(Profiles.MOCK_MY_CLIENT)
#Bean
public MyClient prodClient() {
return new MockClient();
}
#Profile(Profiles.MOCK_MY_CLIENT)
#Bean
public MyClient testClient() {
return new MockClient();
}
}
Mmm, If you try to inject a list of this components?
Something like
public Publisher(List<MyClient> clients) {
}
and in the client implementations you set a flag that could be useful to know when you should use it.
In this code here:
#java.beans.ConstructorProperties({"prodClient", "testClient"})
public Publisher(MyClient prodClient, MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}
Try using the #Autowired annotation on the parameters instead:
public Publisher(#Autowired MyClient prodClient, #Autowired MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}

SpringBoot application doesn't autowire field

I do have ServiceImpl which looks like this:
#Service
#RequiredArgsConstructor
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
And I would like to inject a field value to fieldA in an Application.java from application.yml like this:
#EnableSwagger2
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
But I receive the following error when running SpringBoot app:
Error creating bean with name 'serviceAImpl' defined in URLNo qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Do you have any solution for that?
You annotated your class with #Service and defined it manually as a bean with the #Bean annotation. I do think the second is the way you planned to use it.
The #Service annotation will make this class get picked up by Spring's component scan and additionally create an instance of it.
Of course it tries to resolve the parameters and fails when it tries to find a matching "bean" for the String field because there is no simple String bean (and should not :) ).
Remove the #Service annotation and everything should work as expected.
Try this
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowire
public ServiceAImpl(#Value("${fieldA}") String fieldA){
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
and this
#EnableSwagger2
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You should not use #Service and #Bean for the same class!
Spring is not so smart :)
You should annotate your bean like:
#RequiredArgsConstructor
public class ServiceAImpl {
#Value("${fieldA}")
private final String something;
...
But I'm not sure it will work with the #RequiredFieldsConstructor, it would be simpler for you write down the constructor annotated with #Autowired and using the #Value annotation for the String parameter:
#Autowired
public ServiceAImpl(#Value("${aProp}") String string) {
You're using two bean declaration mechanisms:
You're registering your bean using #Service
You're registering a bean using #Bean
This means that your service will be created twice. The one defined using #Bean works properly, since it uses the #Value annotation to inject the proper value in your service.
However, the service created due to #Service doesn't know about the #Value annotation and will try to find any bean of type String, which it can't find, and thus it will throw the exception you're seeing.
Now, the solution is to pick either one of these. If you want to keep the #Bean configuration, you should remove the #Service annotation from ServiceAImpl and that will do the trick.
Alternatively, if you want to keep the #Service annotation, you should remove the #Bean declaration, and you should write your own constructor rather than relying on Lombok because this allows you to use the #Value annotation within the constructor:
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
/**
* This constructor works as well
*/
public ServiceAImpl(#Value("${fieldA}") String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
If you want to declare ServiceAImpl as a Spring bean in your Java Configuration file, you should remove the #Service annotation from the class declaration. These annotations doesn't work well together.
ServiceAImpl.java
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowired
public ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
Application.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
}
Your application.properties
fieldA=value
The below implementation works well for me. You have two issues, first you have to choose between #Service and #Bean and the other issue I've seen in your code was the #Value annotation, you have to use only to inject a value from the properties.
#SpringBootApplication
public class TestedValueApplication {
#Autowired
void printServiceInstance(ServiceA service) {
System.out.println("Service instance: " + service);
System.out.println("value==value? " + service.isFieldA("value"));
}
public static void main(String[] args) {
SpringApplication.run(TestedValueApplication.class, args);
}
#Bean
public ServiceA serviceA(#Value("${fieldA}") String fieldA) {
return new ServiceAImpl(fieldA);
}
}
Service:
public class ServiceAImpl implements ServiceA {
private String fieldA;
ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
application.properties:
fieldA=value

Injecting a service in a springboot aws lambda

I'm building a lambda based on this code
The uppercaseService is "injected" like this:
#Component("uppercaseFunction")
public class UppercaseFunction implements Function<UppercaseRequest, UppercaseResponse> {
private final UppercaseService uppercaseService;
public UppercaseFunction(final UppercaseService uppercaseService) {
this.uppercaseService = uppercaseService;
}
This works fine until I try to inject another service inside UppercaseService.
#Service
public class UppercaseService {
#Autowired
MyService myService;
public String uppercase(final String input) {
myService.doSomething();
return input.toUpperCase(Locale.ENGLISH);
}
}
AWS console returns:
"errorMessage": "Error creating bean with name 'uppercaseService':
Unsatisfied dependency expressed through field 'myService'
This service works in a non lambda context. The class is present in the .jar built with maven package.
I tried the solution # https://www.profit4cloud.nl/blog/just-spring-enabled-aws-lambdas without success.
You have to initialize your MyService bean first. Since your MyService come from external service which very likely to have different package than your own package
Either directly:
#SpringBootApplication
public class UpperFunctionApplication {
#Bean
public MyService myService() {
return new MyService(); // You must provide code to construct new MyService bean
}
public static void main(String[] args) throws Exception {
SpringApplication.run(UpperFunctionApplication.class, args);
}
}
or via componentscan:
#SpringBootApplication(scanBasePackageClasses = {UpperFunctionApplication.class, MyService.class})
public class UpperFunctionApplication {
#Bean
public MyService myService() {
return new MyService(); // You must provide code to construct new MyService bean
}
public static void main(String[] args) throws Exception {
SpringApplication.run(UpperFunctionApplication.class, args);
}
}

#Autowire not preperly injected with Spring #Bean configuration

I am practising on spring-social and it seems that the userConnectionRepository is not properly autowired in the following code when I do a "Run as Junit Test" in Eclipse. I get a Null pointer exception on the usersConnectionRepository when creating a new FacebookOffLine although breakpoints put in the #Bean java creation code shows that they seem to be properly created. Thanks in advance,
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Here is the test code :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
org.springframework.social.quickstart.config.MainConfig.class,
org.springframework.social.quickstart.config.SocialConfig.class })
public class FacebookOfflineTest {
#Test
public void test1() {
FacebookOffline essai = new FacebookOffline(new User("yves"));
And the Spring configuration classes adapted from Keith Donald Quick Start Sample :
#Configuration
#ComponentScan(basePackages = "org.springframework.social.quickstart", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:org/springframework/social/quickstart/config/application.properties")
public class MainConfig {
#Bean
public DataSource datasource() {
DriverManagerDataSource toReturn = new DriverManagerDataSource("jdbc:mysql://localhost:3306/spring_social");
toReturn.setDriverClassName("com.mysql.jdbc.Driver");
toReturn.setUsername("spring");
toReturn.setPassword("spring");
return toReturn;
}
}
#Configuration
public class SocialConfig {
#Inject
private Environment environment;
#Inject
private DataSource dataSource;
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment
.getProperty("facebook.clientId"), environment
.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(
dataSource, connectionFactoryLocator(), Encryptors.noOpText());
return repository;
}
}
Actually there are 2 problems here.
Spring cannot autowire beans it doesn't control (i.e. created with new)
Dependencies aren't available in the constructor (an object instance is needed before it can be injected)
The first one can be mitigated by letting spring manage an instance of FacebookOffline (or if you need multiple instances make the bean request or session scoped).
The second is a bit harder but can probaly solved by using a method annotated with #PostConstruct (or by implementing InitializingBean from spring).
You did
FacebookOffline essai = new FacebookOffline(new User("yves"));
That means, Spring isn't managing this essai instance and thus spring can't autowire any variables in the essai.
You'll have to create bean of FacebookOffline in SocialConfig.
Then you can have
/* ... */
public class FacebookOfflineTest {
#Autowired
ApplicationContext context;
#Test
public void test1() {
FacebookOffline essai = context.getBean(FacebookOffline.class);
OR
/* ... */
public class FacebookOfflineTest {
#Autowired
FacebookOffline essai;
#Test
public void test1() {
// You can use essai now
Also, you'll need to update FacebookOffline as Dependencies ain't available in constructor.
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
}
#PostConstruct
void loadFacebook() {
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Spring can't autowire fields on an instance you create via new since it doesn't know about it. Declare a bean of type FacebookOffline instead.

Categories

Resources