With Ribbon, if you want to use a custom ServerList implementation instead of the default ConfigurationBasedServerList for a specific service, you can do it like this in the application configuration file:
my-service:
ribbon:
NIWSServerListClassName: com.myapp.MyCustomServerList
My issue is that I want to replace the default ConfigurationBasedServerList for all services I declare to use the MyCustomServerList.
I could just add the previous properties block for each service, but that could grow endlessly.
Is there a way to declare MyCustomServerList as default?
I've also tried adding this bean to my #Configuration class, but it only seems to work the first time I do a request:
#Bean
public ServerList<Server> ribbonServerList() {
return new MyCustomServerList();
}
See http://cloud.spring.io/spring-cloud-static/Dalston.SR1/#_customizing_the_ribbon_client
#RibbonClients(defaultConfiguration=MyConfig.class)
//...
class MyConfig {
#Bean
public ServerList<Server> ribbonServerList() {
return new MyCustomServerList();
}
}
Related
Please let me know if there is way to define #ReleaseStrategy with MessageGroup and associate it with #Aggregator.
I have POJO defined as below but not sure how would I associate a #Aggregator to it
public class FooReleaseStrategy {
#ReleaseStrategy
public boolean canRelease(MessageGroup group) {
return group.isComplete();
}
}
I have #Aggregator and #CorrelationStratgy defined part of configuration.
#Aggregator(inputChannel="sftpChannel" outputChannel="aggregateChannel")
public List<Message<?>> aggregateFiles(List<Message<?>> messages) {
return messages;
}
#CorrelationStrategy based on filename.
Would be very helpful if someone can shed some light on #ReleaseStrategy association with example if possible.
Based on the comments, I am planning on the create a aggregator factory bean to see if works for my use-case
#Bean
#ServiceActivator(inputChannel = "sftpChannel")
public FactoryBean<MessageHandler> aggregatorFactoryBean( ) {
AggregatorFactoryBean aggregatorBean = new AggregatorFactoryBean();
aggregatorBean.setProcessorBean(new CustomAggregator());
aggregatorBean.setMethodName("aggregate");
aggregatorBean.setMessageStore(new SimpleMessageStore());
aggregatorBean.setReleaseStrategy(messageGroup -> {
return messageGroup.isComplete();
});
aggregatorBean.setOutputChannel(aggregatorFileChannel());
aggregatorBean.setExpireGroupsUponTimeout(true);
aggregatorBean.setGroupTimeoutExpression(new ValueExpression<>(1000L));
aggregatorBean.setSendPartialResultOnExpiry(false);
aggregatorBean.setExpireGroupsUponCompletion(true);
return aggregatorBean;
}
If you want to use an #Aggregator, #ReleaseStrategy and #CorrelationStrategy, consider to configure an AggregatorFactoryBean as a #Bean and apply a #SerivceActivator annotation on it for those inputChannel and outputChannel.
See docs for more info: https://docs.spring.io/spring-integration/docs/5.4.0-M2/reference/html/message-routing.html#aggregator-annotations
When using that style of configuration, #Aggregator, #CorrelationStrategy and #ReleasStrategy are usually in the same bean.
You can, however, define a ReleaseStrategyFactoryBean bean that will provide an implementation of ReleaseStrategy based on your POJO method.
setTarget(myRSBean);
It will find the annotation.
I'm trying to achieve something like this:
#Controller
public SomeController {
#CustomConfig("var.a")
private String varA;
#CustomConfig("var.b")
private String varB;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String get() {
return varA;
}
}
CustomConfig would be an #Interface class that accepts one value parameter. The reason why we are not using #Value is because this will not come from config file but from API (such as https://getconfig.com/get?key=var.a). So we are going to make HTTP request to inject it.
So far I've only manage to make something work if the varA and varB is inside get() method as parameter, by using below in a class that extends WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CustomConfigResolver resolver = new CustomConfigResolver();
argumentResolvers.add(resolver);
}
And inside CustomComfigResolver.resolveArgument() we would do the HTTP query, but that's not really what we wanted, we need it to be injected as class variable.
Does anyone have experience in resolving it at class variable level?
Thank you
This could work if you use #Value instead of your own custom annotation. This uses the built in environment:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class TcpIpPropertySourceConfig implements InitializingBean {
#Autowired
private ConfigurableEnvironment env;
#Autowired
private RestTemplate rest;
public void afterPropertiesSet() {
// Call your api using Resttemplate
RemoteProperties props = //Rest Call here;
// Add your source to the environment.
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource("customSourceName", props)
}
}
What you are trying to achieve is difficult when you start to consider "unhappy" scenarios. Server down / not reachable. You need to account for all of that in the method above.
I would highly recommend to instead use Spring Cloud Config. Great guide on that is here: https://www.baeldung.com/spring-cloud-configuration
This provides:
- Reloading of your #Value() properties, so no custom annotation needed.
- A more stable server and great Spring integration out of the box.
Best of all, it is easy to apply Retries and Backoffs if the configuration server goes down (see https://stackoverflow.com/a/44203216/2082699). This will make sure your app doesn't just crash when the server is not available.
I am trying to pass an argument to my #RESTController Spring Boot class.
In the #POSTMapping method I want to use a method of a self defined Java class for processing the received body and returning a response.
The Spring application is launched in Application.java. The Controller-Object seems to get created implicitly.
I already tried adding a constructor to my RESTController class. But I couldn't find a way to call that constructor with an argument.
// Application.java
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
//ConnectorService.java
#RestController
public class ConnectorService {
private Solveable solver;
public ConnectorService() {}
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestBody Test test) {
return solver.solve(test);
}
}
Even though i could define a second constructor, i didn't find any way to call it with my Object.
Use #RequestParam annotation to pass an argument
You can pass parameter with #RequestParam annotation like this:
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestParam("paramName") String param, #RequestBody Test test) {
return solver.solve(test);
}
And you can put it with http request:
http://localhost:3000/maze?paramName=someValue
Assuming that you have POST request, there may be different ways to build this request, depending on the API testing tools you use.
#RestController follows the same rules for dependency injection as any other #Component in Spring framework.
If you have a single constructor, Spring will try to „inject” the parameters while instantiating the controller.
You need to register your dependency as a Spring bean.
It seems that you are new to Spring and you are starting with advanced topics like Spring Boot and rest controllers. Please find some time to read about the basics.
Yo can create a Bean configuration file to initialize your objects like:
#Configuration
#ComponentScan("com.xxx.xxx") // the base package you want to scan
public class Config {
#Bean
//where Solveable is a class and is annotated with an Spring's annotation
public Solveable solveable() {
return new Solveable();
}
}
And use the #Autowired annotation to inject the object in:
#Autowired
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
This last block will initialize or pass(what you want) the object to the ConnectorService class.
I want to implement a conditional Bean depending on a flag in my application.properties. Example:
// application.properties
service=foobar
The idea is to make different service implementations configurable, let assume I got a central configuration class for this service in Spring:
#Configuration
#Import({ServiceA.class, ServiceB.class, ...})
public class ServiceConfiguration {
...
}
And possible service implementations would look like
#Configuration
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// getProperty will alsways return null for some reason
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
// This will be null anyways
#Value("${service}")
private String confService;
}
Since the class implementing Condition (here just the same class ServiceA) will be initialized via default constructor #Value-injections won't work. How ever, by what I understand getProperty()should return the correct value. What am I doing wrong? How can I access application properties at this point?
I found at "dirty workarround", I really don't like that solution, how ever, it solves the problem. As mentioned here a #PropertySource fixes the problem (I haven't tried this before posting here since it wasn't an accpeted answer).
#Configuration
#PropertySource(value="file:config/application.properties")
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// Will work now
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
}
Although this works I don't like it for several reason:
With every implementation I have code redundancy (giving a path to a config file)
It's highly unmaintainable when having multiple configuration files
Example: Behavior like load default.properties <-then load and overwrite with -> customer.properties won't work anymore (altough this should be solvable using #PropertySources which would, on the other hand, increase code redundancy)
In the following Spring Java Config:
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class Config {
#Bean
public BasicBean basicBean1() {
return new BasicBean("1");
}
#Bean
public BasicBean basicBean2() {
return new BasicBean("2");
}
#Bean
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
}
I can create two ComplexBeans using either parameter injection, which is elegant, but has shortcomings if a have a few other beans of BasicBean type and only want a few (the parameters can of course be of type BasicBean and enumerate by name the beans I'm interested of, but it could turn out to be a very long list, at least for arguments sake). In case I wish to reference the beans directly I might use the complexBeanByReferences style, which could be useful in case of ordering or some other consideration.
But say I want to use the complexBeanByReference style to reference the bean complexBeanByParameters, that is something along the line of:
#Bean
public ComplexBeanRegistry complexBeanRegistry() {
return new ComplexBeanRegistry(
Arrays.asList(
complexBeanByParameters(), // but this will not work!
complexBeanByReferences()
)
);
}
How would I reference complexBeanByParameters, without having to specify a list of dependencies to complexBeanRegistry? Which, the latter in all honesty should be completely oblivious of.
There is the option to just use
public ComplexBeanRegistry complexBeanRegistry(List<ComplexBeans> complexBeans) {...}
of course, but this might not be an option in certain cases, specifically when using the CacheConfigurer from spring-context. In this case the Java Config is intended to
create the beans
by implementing CacheConfigurer, override the default instances of the CacheManager and KeyGenerator beans.
The requirement to implement CacheConfigurer means I can't change the signature to use parameter injection.
So the question is, is there a way to reference complexBeanByParameters using the "direct" reference style?
Maybe you could reference it with separation by Qualifier:
#Bean
#Qualifier("complexBeanParam")
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
#Qualifier("complexBeanRef")
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
and for example autowire:
#Autowired
#Qualifier("complexBeanParam")
private ComplexBean beanParam;