I have a bean for an webservice client in my project which requires some configuration settings to be injected. We are using Spring 3.1. Currently the best idea that came up was using the #Value annotation like this:
#Service
public class MyWebServiceClient {
private String endpointUrl;
#Required
#Value("${mywebserviceClient.endpointUrl}")
public void setEndpointUrl(String endpointUrl) {
this.endpointUrl = endpointUrl;
}
}
However I don't really like hardcoding the property name into the class. It also has the problem that there is no way to have more than one client with different settings in the same context (as there is only one property and this is hardcoded). Is there a more elegant way of doing this with autowiring or should I resort to plain old xml configuration for doing this?
I would use JavaConfig to do this.
More specifically, I would use JavaConfig to create multiple instances of MyWebServiceClient, and have the config be #Value'd with the proper endpoint property keys.
Something like this:
#Configuration
public class MyWebServiceConfig {
#Required
#Value("${myWebserviceClient1.endpointUrl")
private String webservice1Url;
#Required
#Value("${myWebserviceClient2.endpointUrl")
private String webservice2Url;
#Required
#Value("${myWebserviceClient3.endpointUrl")
private String webservice3Url;
#Bean
public MyWebServiceClient webserviceClient1() {
MyWebServiceClient client = createWebServiceClient();
client.setEndpointUrl(webservice1Url);
return client;
}
#Bean
public MyWebServiceClient webserviceClient2() {
MyWebServiceClient client = createWebServiceClient();
client.setEndpointUrl(webservice2Url);
return client;
}
#Bean
public MyWebServiceClient webserviceClient3() {
MyWebServiceClient client = createWebServiceClient();
client.setEndpointUrl(webservice3Url);
return client;
}
}
With this, you should have 3 instances of MyWebServiceClient in your ApplicationContext available via the names of the methods annotated with #Bean.
Here is some more documentation to JavaConfig for your convenience.
Related
I'm trying to create a flexible framework for creating components with as much configuration externalized as possible. I'd like to be able to use the YML configuration to define a collection of beans and use them in another collection of beans.
I'm starting with an application.yml like this:
component:
services:
- name: google
url: google.com
- name: yahoo
url: yahoo.com
capabilities:
- name: searchTehWebsForDragons
service: google
inputString: "dragons"
- name: searchTehWebsForPuppies
service: google
inputString: "puppies"
I've been able to use the #ConfigurationProperties annotation to parse that into POJOs
#ConfigurationProperties(prefix = "component")
public class ConfigureComponents{
public List<Service> services;
public List<Capability> capabilities;
//getters and setters
}
The Service and Capability classes look like this:
public class Service {
public String name;
public String url;
public String callIt(String inputString){
//servicelogic
}
//getters and setters
}
public class Capabilities {
public String name;
public Service service;
public String inputString;
public String doIt(){
service.callIt(inputString);
}
//getters and setters
}
So, by default, the YML configuration can create POJOs of the Service objects, but not beans. I can turn those into beans with something like this:
#Autowired
private ConfigurableBeanFactory beanFactory;
#PostConstruct
public void init() {
for (Service service: services) (
beanFactory.registerSingleton(service.name, service);
)
}
But if I'm trying to create the Capability POJOs with the yml, they need to be able to wire in those Service beans. I've tried getBeans from the context, but they don't seem to exist when I need them.
There have been many questions like this, but I haven't found a streamlined way to do this without reverting to XML configuration.
I know Spring keeps adding functionality, so if there's anything new I've missed, clue me in!
I'd love to put some annotations in the Configuration class to create the List of beans by default, but I haven't figured out how. I've messed with #Bean annotations, but I can't figure out how that works with a List of Objects.
Any help would be appreciated! Let me know if you need more information.
If I understand this question correctly, you want to turn your List<Service> into a bean that can be injected into other Components. This can be accomplished by marking your ConfigureComponents as a #Configuration, and creating a method to return the List<Service> as a Bean.
#Configuration
#ConfigurationProperties(prefix = "component")
public class ConfigureComponents{
public List<Service> services;
public List<Capability> capabilities;
#Bean
public List<Service> getServices() {
return this.services;
}
public void setServices(List<Service> services) {
this.services = services;
}
}
This logic will create a Bean in your Spring context that is a List of Service that you can then autowire into your other components.
You can also achieve this by marking your ConfigureComponents class as a Component and just injecting it into your other Beans, and when you want your Services, calling the getter, like this:
#Component
#ConfigurationProperties(prefix = "component")
public class ConfigureComponents{
public List<Service> services;
public List<Capability> capabilities;
public List<Service> getServices() {
return this.services;
}
public void setServices(List<Service> services) {
this.services = services;
}
}
Now, you can inject ConfigureComponents into your collaborators and call configureComponents.getServices() to get back your Services.
An existing utility exists that I am importing to my project. It is written similarly as these two classes
#Service
public class ServiceAccessorImpl implements ServiceAccessor {
#Autowired private ServiceConfiguration serviceConfiguration;
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
#Configuration
#Data //automatically adds getters and setters
#ConfigurationProperties(prefix="config")//pulls serviceEndPoint value from external config
//Assume external config has config.serviceEndPoint = "www.endpoint1.com"
public class ServiceConfiguration {
private String serviceEndPoint;
}
In a separate project below I am importing the above into my project. I would like to have two instances of the same service with two unique and respective configuration classes. so that service1 is linked to config1 and service2 is linked to config2. My reasoning is I want one instance that only pulls the endpoint from the external configuration and another instance that I can use to set the endpoint. I have tried using things like #Qualifier but I cant figure out how to link the correct config with the correct service. I have a feeling that this may not be possible because ServiceConfiguration is privately scoped within ServiceAccessorImpl and I have no access through setters or constructors.
Controller Endpoint. The below is psuedo code of how I would like to implement my design. Autowiring in a single instance and using either endpoint works for that endpoint but not for both as shown below.
#ComponentScan(basePackageClass = ServiceAccessorImpl.class)
public class ServiceAccessorController {
#Autowired private ServiceAccessor serviceAccessor1;
#Autowired private ServiceConfiguration serviceConfiguration1;
#Autowired private ServiceAccessor serviceAccessor2;
#Autowired private ServiceConfiguration serviceConfiguration2;
Response CallEndpoint1(){
//www.endpoint1.com is already set here from external config
return serviceAccessor1.executeCall();
}
Response CallEndpoint1(){
serviceConfiguration2.setServiceEndPoint("www.endpoint2.com")
return serviceAccessor2.executeCall();
}
}
Thank you in advance
If you need multiple instances of the same implementation, it's easier to not annotate it as a bean, and have a #Configuration provide the beans instead.
public class ServiceAccessorImpl implements ServiceAccessor {
private ServiceConfiguration serviceConfiguration;
public ServiceAccessorImpl(ServiceConfiguration configuration) {
this.serviceConfiguration = configuration;
}
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
// this one should just have #ConfigurationProperties, not #Configuration
#Data
#ConfigurationProperties(prefix="config")
public class ServiceConfiguration {
private String serviceEndPoint;
}
then in your service you can have a Configuration providing both instances:
#Configuration
public class BeansConfiguration {
#Qualifier("service1")
#Primary // optional, Spring will autowire this instance by default if no qualifier is given
#Bean
public service1(#Autowired ServiceConfiguration config) {
// use the default config bean
return new ServiceAccessorImpl(config);
}
#Qualifier("service2")
#Bean
public service2() {
return new ServiceAccessorImpl(new ServiceConfiguration("www.endpoint2.com"));
}
}
then you may consume both by using the qualifiers (note that you don't have to inject the configs here):
public class ServiceAccessorController {
#Autowired
private ServiceAccessor serviceAccessor1;
#Autowired
#Qualifier("service2")
private ServiceAccessor serviceAccessor2;
Response CallEndpoint1(){
return serviceAccessor1.executeCall();
}
Response CallEndpoint2(){
return serviceAccessor2.executeCall();
}
}
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
I am developing a Java API for a service and I want to extract it to a library.
I am using spring 4.3.3
Right now there is a bean called ApiConfig which is simple pojo.
public class ApiConfig {
private String host;
private String username;
private String password;
}
and the values are read from a properties file.
I would like to be able to construct and provide this class before the context starts (several components have this class as #Autowired dependency).
For instance:
public class LoginService {
#Autowired
private ApiConfig apiConfig
[...]
}
Basically, I would like to do something like this:
public static MyApi get(ApiConfig apiConfig) {
//Here I want to provide this apiConfig as singleton bean that would be used everywhere
provide somehow this class as bean
// here all beans are loaded and the it fails because it cannot resolve ApiConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ContextConfig.class);
MyApi myApi= context.getBean(MyApi.class);
return myApi;
}
The method MyApi.get(AppConfig) would be used by other java applications by adding dependency in pom.xml
Is there a way I can do this? Providing the ApiConfig bean and then initialize all the application?
Basically to let Spring know that there is also this bean, before starting context with new AnnotationConfigApplicationContext(ContextConfig.class)
UPDATE
The idea would be this, in any application using this library.
public static void main(String asdas[]) {
ApiConfig config = new ApiConfig();
config.setUsername("BOBTHEUSER");
//config.set etc
MyApi api = MyApi.get(config);
api.doOperation();
Actually #Autowire is enough. Make the ApiConfig a Bean and autowire it where it's is necessary. Spring resolves the proper order.
If you have two beans and one need the second to be initialized before creation use #DependsOn annotation
#Configuration
public class MainConfig {
#Autowired
private ApiConfig apiConfig
#Bean(name="apiConfig")
public ApiConfig apiConfig(){
... init the config ...
return apiConfigInstance;
}
#Bean(name="myApi")
#DependsOn("apiConfig")
public MyApi myApi(){
MyApi api = new MyApi(apiConfig);
return api;
}
}
Code from the example modified
My project has a dependency on another one, and imports beans from it (using #ImportResource("foo.xml")).
foo.xml defines two datasources (datasource1 and datasource2), I would like to make datasource1 a primary (so all auto-configurations of Spring Boot will work).
Is it possible? I found out that there is a DefaultListableBeanFactory that has determinePrimaryCandidate method.
So the idea is to create my own ListableBeanFactory, that would extend the DefaultListableBeanFactory, but how to force Spring Boot to use my implementation?
Or maybe there is another, easier way to mark a given bean as primary (without changing the configuration where it is defined).
You can create a configuration in your project, which builds a new data source annotated as #Primary bean. This new data source will be the datasource1, which will be injected by spring to the new data source factory method. Here you have the working example.
The config:
#SpringBootApplication
public class BeanSpringExampleApplication
{
#Bean(name = "dataSource1")
public FakeDataSource dataSource1()
{
return new FakeDataSource("dataSource1");
}
#Bean(name = "dataSource2")
public FakeDataSource dataSource2()
{
return new FakeDataSource("dataSource2");
}
#Bean
#Primary
public FakeDataSource primaryDataSource(
#Qualifier("dataSource1") FakeDataSource dataSource1)
{
return dataSource1;
}
}
Here you see three beans (using FakeDataSource class), which simulate your situation. The primaryDataSource bean factory method simply returns the dataSource1 (it's just a mere data source selector).
The FakeDataSource is just a placeholder, to make example runnable:
public class FakeDataSource
{
private final String fakeProperty;
public FakeDataSource(String id)
{
fakeProperty = id;
}
/**
* #return the fakeProperty
*/
public String getFakeProperty()
{
return fakeProperty;
}
}
Finally, a test which proves everything is working:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BeanSpringExampleApplicationTests
{
#Autowired
private FakeDataSource fakeDataSource;
#Test
public void should_AutowirePrimaryDataSource() throws Exception
{
assertEquals("dataSource1", fakeDataSource.getFakeProperty());
}
}