Making a defined spring bean primary - java

I have 3 beans of the same type defined in spring.xml. This is inside a jar file which I cannot edit. I want to make one of these primary in my spring-boot application using annotation. Is there a way to do it?

A straightforward approach is to use a bridge configuration, which will register the desired bean as a new primary bean. A simple example:
The interface:
public interface Greeter { String greet(); }
The configuration which you don't control:
#Configuration
public class Config1 {
#Bean public Greeter british(){ return () -> "Hi"; }
#Bean public Greeter obiWan(){ return () -> "Hello there"; }
#Bean public Greeter american(){ return () -> "Howdy"; }
}
The bridge configuration:
#Configuration
public class Config2 {
#Primary #Bean public Greeter primary(#Qualifier("obiWan") Greeter g) {
return g;
}
}
The client code:
#RestController
public class ControllerImpl {
#Autowired
Greeter greeter;
#RequestMapping(path = "/test", method = RequestMethod.GET)
public String test() {
return greeter.greet();
}
}
Result of curl http://localhost:8080/test will be
Hello there

You can use #Qualifier("___beanName__") annotation to choose the correct one

I tried #jihor solutions but it doesn't work. I have a NullPointerException in defined configuration.
Then I find the next solution on Spring Boot
#Configuration
public class Config1 {
#Bean
#ConfigurationProperties(prefix = "spring.datasource.ddbb")
public JndiPropertyHolder ddbbProperties() {
return new JndiPropertyHolder();
}
#ConditionalOnProperty(name = "spring.datasource.ddbb.primary", matchIfMissing = false, havingValue = "true")
#Bean("ddbbDataSource")
#Primary
public DataSource ddbbDataSourcePrimary() {
return new JndiDataSourceLookup().getDataSource(ddbbProperties().getJndiName());
}
#ConditionalOnProperty(name = "spring.datasource.ddbb.primary", matchIfMissing = true, havingValue = "false")
#Bean("ddbbDataSource")
public DataSource ddbbDataSource() {
return new JndiDataSourceLookup().getDataSource(ddbbProperties().getJndiName());
}
}
And my application.properties if I need this datasource as primary, otherwise don't set the property or set false.
spring.datasource.ddbb.primary=true

Related

Testing conditional Bean creation with #ConditionalOnProperty and #FeignClient

I have a Rest Controller, a Service and a Feign Client being used inside the service.
Now, I need to conditionally create the controller bean based on an environment variable. I have been able to set up the configuration and it looks like it should work. So far so good. But I am having a hard time testing the conditional bean creation.
The REST Controller:
#RestController
#RequestMapping("/api/path")
public class AbcController {
private final AbcService abcService;
#Autowired
public AbcController(AbcService abcService) {
this.abcService = abcService;
}
...
}
The Service:
#Service
public class AbcService {
private final AbcClient abcClient;
#Autowired
public AbcService(AbcClient abcClient) {
this.abcClient = abcClient;
}
Feign Client:
#Component
#FeignClient(name = "abc", url = "${abc.url}", decode404 = true)
public interface AbcClient {
#RequestMapping(
value = "/match",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE
)
MatchResponse getSsoIds(RequestDto requestDto);
}
Configuration for conditionally loading the Controller class:
#Configuration
public class AbcConfiguration {
#Bean
#ConditionalOnProperty(
value="feature.enabled",
havingValue = "true",
matchIfMissing = true
)
public AbcController abcController(AbcService abcService) {
return new AbcController(abcService);
}
}
The Test class for testing the conditional bean creation:
public class AbcControllerTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AbcConfiguration.class));
#Test
public void loadsControllerBeanWhenPropertyIsEnabled() {
this.contextRunner
.withPropertyValues("feature.enabled=true")
.run(context -> context.assertThat().hasSingleBean(AbcController.class));
}
#Test
public void loadsControllerBeanWithoutDefinedProperty() {
this.contextRunner
.run(context -> context.assertThat().hasSingleBean(AbcController.class));
}
#Test
public void doesNotLoadControllerBeanWhenPropertyIsDisabled() {
this.contextRunner
.withPropertyValues("feature.enabled=false")
.run(context -> context.assertThat().doesNotHaveBean(AbcController.class));
}
}
I have tried to run the test but the first 2 tests always ends up complaining about missing or non-configurable beans. The last test runs as expected.
With the above test setup it complains about missing AbcService bean, which we can solve by adding
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = AbcService.class)
to the top of the test class, but then it complains No qualifying bean of type 'de.xxx.xxx.AbcClient' and we cannot create a bean of the feignClient AbcClient because it is an interface.
Help needed! How to make these tests pass?

Can't access #Value in Spring project accross different packages and Environment doesn't contain the values either

I cannot access #Value("${app.version}") or event environment.getProperty("app.version") or any property in my controllers or services.
My project structure looks like this
src/main/java
-configuration/
AppConfig.java
EnvConfig.java
JpaConfig.java
UiConfig.java
ServicesConfig.java
UiAppInitializer.java
-repositories/
....
-models/
....
-services/
....
-controllers/
....
My UiAppInitializer is pretty straight forward,
getRootConfigClassess() returns AppConfig.class and getServletConfigClasses() returns UiConfig.class
AppConfig.java
#Configuration
#Import({
EnvConfig.class,
UiConfig.class,
ServicesConfig.class
})
public class AppConfig{}
EnvConfig
#Configuration
public class EnvConfig implements InitializingBean {
#Value("${app.version}")
private String appVersion
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
pc.setLocations(new ClassPathResource("application.properties"));
return pc;
}
#Override
public void afterpropertiesSet() throws Exception {
log.debug("App Version is " + appVersion);
}
}
A simple controller
#RestController
#RequestMapping(value = "/version")
public class VersionContoller {
#Value("${app.version}")
private String version;
#GetMapping()
public String getVersion() {
return version;
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {
"my.packages.path.ui"
})
public class UiConfig implements WebMvcConfigurer {
....
}
The controller just returns "${app.version}" but the afterpropertiesSet correctly logs the version.
What am I doing wrong here? I have other controllers that connect to the repository successfully which was setup in JpaConfig that usues #Value for all the properties also
Note not using Spring Boot
It seems the controller is getting initialised as a bean before the properties() bean has had setLocations() called.
You could remove classpath scanning (I assume you have it on to find the controller bean?) and in your EnvConfig declare a new method that is a bean declaration for the Controller that passes in the version String. Obviously requiring a change to the controller constructor too
#Configuration
public class EnvConfig implements InitializingBean {
#Value("${app.version}")
private String appVersion
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
pc.setLocations(new ClassPathResource("application.properties"));
return pc;
}
#Bean
public VersionContoller controller() {
return new VersionController(appVersion);
}
#Override
public void afterpropertiesSet() throws Exception {
log.debug("App Version is " + appVersion);
}
}

Parametrize spring configuration

Assuming you have the following spring configuration:
#Configuration
public class Config {
#Bean
public SomeBean someBean() {
SomeBean someBean = new SomeBean();
someBean.setVar("foobar");
return someBean;
}
}
Then I can use this configuration in some other class for example by importing it with #Import(Config.class). Now, say you don't want to hardcode the string "foobar" but pass it as a parameter to that configuration. How would I do that? It would be nice to create a custom annotation like #FooBarConfiguration(var = "foobar"). Is that possible?
The #Ben answer is the classic and better approach. But if you don't want to use a property file, you can use a #Bean for that. Each #Bean holds a value that you would like to inject.
Full code example:
#SpringBootApplication
public class So49053082Application implements CommandLineRunner {
#Bean
String beanValueFooBar() {
return "fooBar";
}
#Bean
String beanValueBarFoo() {
return "barFoo";
}
private class SomeBean {
private String var;
public void setVar(final String var) {
this.var = var;
}
}
#Configuration
public class Config {
#Bean
public SomeBean someBean(String beanValueBarFoo) {
SomeBean someBean = new SomeBean();
System.out.println(beanValueBarFoo);
someBean.setVar(beanValueBarFoo);
return someBean;
}
}
#Override
public void run(String... args) {
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So49053082Application.class, args);
context.close();
}
}
Consider using the #Value annotation:
#Configuration
public class Config {
#Value("${myParamValue}")
public String myParam;
#Bean
public SomeBean someBean() {
SomeBean someBean = new SomeBean();
someBean.setVar(myParam);
return someBean;
}
}
you'll need to put the parameters into the environment somehow: there are various techniques using the OS environment, runtime parameters or configuration files, as suits your purposes.

Why doesn't #Getmapping work in some instances?

In my controller, the following use of #GetMapping works:
#GetMapping(value = "/new")
public String newEssay(){
return "articles/essay_new";
}
But it doesn't work like this:
#GetMapping(value = "/essays/{essayId: [0-9]+}")
//#RequestMapping(value = "/essays/{essayId:[0-9]+}", method = RequestMethod.GET)
public String getEssay(Model model,
#PathVariable("essayId") long essayId) throws NoFindException, ForBiddenException, ParseException {
JsEssay jsEssay = jsBiz.get(JsEssay.class, essayId);
model.addAttribute("jsEssay", jsEssay);
return "articles/essay";
}
I tried it with Spring 4.3.3 and 5.0.0.M5.
Config:
#Configuration
#ComponentScan( basePackages = {"me.freezehome.blog"},
excludeFilters = {
#ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
}
)
public class RootConfig {
}
#Configuration
#EnableWebMvc
#Import({WebSecurityConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
Google results:
Add support for #GetMapping, #PostMapping etc. introduced in Spring 4.3 in ControllerLinkBuilder #471
GetMapping and PostMapping annotations Ask
github source: lbfreeze-blog-develop
Please remove the space after essayId:
Also, you don't need to write value = for #GetMapping.

How to use multiple vhosts in a Spring RabbitMQ project?

I've the following two Configuration classes:
#Configuration
#EnableRabbit
#Import({ LocalRabbitConfigA.class, CloudRabbitConfigA.class })
public class RabbitConfigA {
#Autowired
#Qualifier("rabbitConnectionFactory_A")
private ConnectionFactory rabbitConnectionFactory;
#Bean(name = "admin_A")
AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean(name = "Exchange_A")
DirectExchange receiverExchange() {
return new DirectExchange("Exchange_A", true, false);
}
}
And
#Configuration
#EnableRabbit
#Import({ LocalRabbitConfigB.class, CloudRabbitConfigB.class })
public class RabbitConfigB {
#Autowired
#Qualifier("rabbitConnectionFactory_B")
private ConnectionFactory rabbitConnectionFactory;
#Bean(name = "admin_B")
AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean(name = "Exchange_B")
DirectExchange receiverExchange() {
return new DirectExchange("Exchange_B", true, false);
}
}
Note that the LocalRabbitConfigA and LocalRabbitConfigB classes define the connectionFactory which connects to a different VHost.
When starting the application (within Tomcat), all the Exchanges are created in both VHosts.
The question is how to define that a certain Exchange/Queue is created by a specific ConnectionFactiory ?
So that VHost A contains only the Exchange_A, and VHost B only Exchange_B ?
See conditional declaration.
Specifically:
#Bean(name = "Exchange_B")
DirectExchange receiverExchange() {
DirectExchange exchange = new DirectExchange("Exchange_B", true, false);
exchange.setAdminsThatShouldDeclare(amqpAdmin());
return exchange;
}
We can achieve this using SimpleRoutingConnectionFactory, where we create multiple connection factories each for a vhost and configure it to SimpleRoutingConnectionFactory.
From the spring documentation: spring doc
public class MyService {
#Autowired
private RabbitTemplate rabbitTemplate;
public void service(String vHost, String payload) {
SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);
rabbitTemplate.convertAndSend(payload);
SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());
}
}
I have created a git repo showing how to do this: spring-boot-amqp-multiple-vhosts

Categories

Resources