How to read Qualifier from property file in spring boot? - java

I have a Qualifier where I read from
public class TestController{
#Autowired
#Qualifier("jdbc")
private JdbcTemplate jtm;
//.....
}
The qualifier "jdbc" is the bean defined as
#Bean(name = "jdbc")
#Autowired
public JdbcTemplate masterJdbcTemplate(#Qualifier("prod") DataSource prod) {
return new JdbcTemplate(prod);
}
This is the which returns the datasource for that qualifier and works fine.
Now I want to make the Qualifier name to be read from the application.properties. So I changed my code to
public class TestController{
#Autowired
#Qualifier("${database.connector.name}")
private JdbcTemplate jtm;
//.....
}
where database.connector.name=jdbc in my application.properties.
But when i do this this throws an error of
APPLICATION FAILED TO START
Description:
Field userService in main.java.rest.TestController required a bean of
type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jdbc.core.JdbcTemplate' in
your configuration.
Any help is appreciated.

Qualifier doesn't resolve placeholder. You can write your TestController class as
public class TestController {
#Value("${database.connector.name}")
private String name;
private JdbcTemplate jtm;
#Autowired
public void setJdbcTemplate(ApplicationContext context) {
jtm = (JdbcTemplate) context.getBean(name);
}
}

As #Hemant already mentioned default QualifierCandidateResolver does not resolve properties.
But you can make one, which does:
import java.lang.annotation.Annotation;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
#Component
public static class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
private static class EnvironmentAwareQualifierAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
private static class ResolvedQualifier implements Qualifier {
private final String value;
ResolvedQualifier(String value) { this.value = value; }
#Override
public String value() { return this.value; }
#Override
public Class<? extends Annotation> annotationType() { return Qualifier.class; }
}
#Override
protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
if (annotation instanceof Qualifier) {
Qualifier qualifier = (Qualifier) annotation;
if (qualifier.value().startsWith("${") && qualifier.value().endsWith("}")) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) this.getBeanFactory();
ResolvedQualifier resolvedQualifier = new ResolvedQualifier(bf.resolveEmbeddedValue(qualifier.value()));
return super.checkQualifier(bdHolder, resolvedQualifier, typeConverter);
}
}
return super.checkQualifier(bdHolder, annotation, typeConverter);
}
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) beanFactory;
bf.setAutowireCandidateResolver(new EnvironmentAwareQualifierAnnotationAutowireCandidateResolver());
}
}
With that you will be able to use #Qualifier in a way you've asked #Qualifier("${database.connector.name}")
Full example:
#SpringBootApplication
public class SO50208018Application {
public static void main(String[] args) { SpringApplication.run(SO50208018Application.class, args); }
interface MyBean { }
static class MyBeanImpl1 implements MyBean { }
static class MyBeanImpl2 implements MyBean { }
#Bean #Qualifier("impl1")
MyBean bean1() { return new MyBeanImpl1(); }
#Bean #Qualifier("impl2")
MyBean bean2() { return new MyBeanImpl2(); }
#Component
public static class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
// configurer from above
}
#Bean
CommandLineRunner run(#Qualifier("${spring.application.bean}") MyBean bean) {
return (args) -> System.out.println(bean.getClass().getName());
}
}
Run with spring.application.bean=impl1:
com.stackoverflow.java.SO50208018Application$MyBeanImpl1
Run with spring.application.bean=impl2:
com.stackoverflow.java.SO50208018Application$MyBeanImpl2

If you keep the #Qualifier("jdbc"),
you can vary the bean that is injected by providing different test configuration files and loading the desired one for each test class.

Related

Consider defining a bean of type 'com.issuemanagement.repository.IssueHistoryRepository' in your configuration

I get this error. What can be done in this case?
APPLICATION FAILED TO START
Description: Parameter 0 of constructor in com.issuemanagement.service.impl.IssueHistoryServiceImpl required a
bean of type 'com.issuemanagement.repository.IssueHistoryRepository'
that could not be found.
Action: Consider defining a bean of type 'com.issuemanagement.repository.IssueHistoryRepository' in your
configuration.
IssueHistoryRepository.java
public interface IssueHistoryRepository extends JpaRepository<IssueHistory, Long>{
}
IssueHistoryService.java
public interface IssueHistoryService {
IssueHistory save(IssueHistory issueHistory);
IssueHistory getById(Long id);
Page<IssueHistory> getAllPageable(Pageable pageable);
Boolean delete(IssueHistory issueHistory);
}
IssueHistoryServiceImpl.java
#Service
public class IssueHistoryServiceImpl implements IssueHistoryService {
private final IssueHistoryRepository issueHistoryRepository;
private final ModelMapper modelMapper;
public IssueHistoryServiceImpl(IssueHistoryRepository issueHistoryRepository, ModelMapper modelMapper) {
this.issueHistoryRepository = issueHistoryRepository;
this.modelMapper = modelMapper;
}
#Override
public IssueHistory save(IssueHistory issueHistory) {
if(issueHistory.getDate()==null) {
throw new IllegalArgumentException("Issue cannot be null");
}
issueHistory = issueHistoryRepository.save(issueHistory);
return issueHistory;
}
#Override
public IssueHistory getById(Long id) {
return issueHistoryRepository.getOne(id);
}
#Override
public Page<IssueHistory> getAllPageable(Pageable pageable) {
return issueHistoryRepository.findAll(pageable);
}
#Override
public Boolean delete(IssueHistory issueHistory) {
issueHistoryRepository.delete(issueHistory);
return Boolean.TRUE;
}
}
IssueManagementApplication.java
#SpringBootApplication
#ComponentScan({"com.issuemanagement"})
#EnableAutoConfiguration
public class IssueManagementApplication {
public static void main(String[] args) {
SpringApplication.run(IssueManagementApplication.class, args);
}
#Bean
public ModelMapper getModelMapper() {
return new ModelMapper();
}
}
#SpringBootApplication encapsulates #Configuration, #EnableAutoConfiguration, and #ComponentScan annotations with their default attributes. The default value for #ComponentScan means that all the sub packages on the package the #ComponentScan is used are scanned. That is why it is usually a good practice to include the main class in the base package of the project. Having said that, please drop #ComponentScan and #EnableAutoConfiguration from your main class as follows:
#SpringBootApplication
public class IssueManagementApplication {
public static void main(String[] args) {
SpringApplication.run(IssueManagementApplication.class, args);
}
#Bean
public ModelMapper getModelMapper() {
return new ModelMapper();
}
}

required single bean but 2 were found in simple spring-boot app

I am new to spring boot. So I am working on a simple application where I have 2 services EnglishLanguageService and SpanishLanguageService. In the end, I would like to print Hello and Hola when the app runs but I am getting the error
Parameter 0 of constructor in controller.ConstructorInjectedController required a single bean, but 2 were found:
- profileEnglishLanguageService: defined in file [/Users/user/Downloads/depdency-injection-example/target/classes/services/EnglishLanguageService.class]
- profileSpanishLanguageService: defined in file [/Users/user/Downloads/depdency-injection-example/target/classes/services/SpanishLanguageService.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I am not sure where I am going wrong. I will paste my code down below.
MainApplication
#SpringBootApplication
#ComponentScan(basePackages= {"controller" , "services"})
public class DepdencyInjectionExampleApplication {
public static void main(String[] args) {
// returns application context
ApplicationContext ctx = SpringApplication.run(DepdencyInjectionExampleApplication.class, args);
System.out.println(ctx.getBean(PropertyInjectedController.class).getGreeting());
System.out.println(ctx.getBean(ConstructorInjectedController.class).getGreetings());
}
ConstructorInjectedController class
#Component
public class ConstructorInjectedController {
private final GreetingService greetingService;
public ConstructorInjectedController(GreetingService greetingService) {
this.greetingService = greetingService;
}
public String getGreetings() {
return greetingService.sayGreetings();
}
PropertyInjectedControllerClass
#Controller
public class PropertyInjectedController {
public GreetingService greetingService;
public String getGreeting() {
return greetingService.sayGreetings();
}
}
GreetingService interface
public interface GreetingService {
String sayGreetings();
}
EnglishLanguageService class
#Service
public class EnglishLanguageService implements GreetingService {
private GreetingService greetingService;
public EnglishLanguageService(#Qualifier("english")
GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
EnglishServiceImpl
#Profile("english")
public class EnglishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hello";
}
SpanishLanguageService
#Service
public class SpanishLanguageService implements GreetingService {
private GreetingService greetingService;
public SpanishLanguageService(#Qualifier("spanish")
GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
SpanishLanguageServiceImpl
#Profile("spanish")
public class SpanishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hola";
}
}
You are using the annotations wrong. #Profile is used for something completely other than you think so it should be removed.
You are basically saying to spring "please inject a specific greeting service in class X please".
Right now spring doesn't know which bean you want since you have two different ones implementing the same interface. So we use the #Qualifier annotation to tag a bean, and also to target it during injection.
Fist your implementation should have either a #Service or a #Component so that spring finds it during the component scan, and instantiates it as a spring managed bean. This combined with a #Qualifier so that we tag it, so we later can pick this tag during injection:
#Component
#Qualifier("english")
public class EnglishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hello";
}
}
Here we inject the greeting service and use our qualifier to tell spring which bean we want to inject of the available ones.
#Service
public class EnglishLanguageService implements GreetingService {
private GreetingService greetingService;
public SpanishLanguageService(#Qualifier("english") GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
}

how to use '#Qualifier' dynamically specifying parameters?

I want to use #Qualifier to dynamically specifying parameters? how to do it ?
#Qualifier("two") 'two' as a parameter ,can be 'one' 'three' or other.
Can i use aop dynamically design 'two'?
means I want to change the name of service with a #Qualifier by parameters.
the parameter from the url 'Token'.
case: url: http://localhost:8080/insert/order, token has a parameter: companyId = one
#RestController
public class ApiWebService {
#Autowired
#Qualifier("two")
//#Qualifier("one")
private BaseService baseService;
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
#Service("one")
public class CompanyOneService extends BaseService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends BaseService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
three
four
...
#Service
public class BaseService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
你好 !
No you cannot , mostly because the attribute in Java annotation does not allow to assign with variables.
Actually you want to choose an implementation to use based on some runtime conditions(i.e.companyId in your case). You can achieve it using factory pattern with #Configuration and #Bean which is much more elegant and easier to understand than your ugly AOP solution:
First define a factory:
#Configuration
public class ServiceFactory{
#Bean
public BaseService companyOneService(){
return new CompanyOneService();
}
#Bean
public BaseService companyTwoService(){
return new CompanyTwoService();
}
public BaseService getService(Integer companyId){
if(companyId == 1){
return companyOneService();
}else if(company==2){
return companyTwoService();
}else{
//blablablab
}
}
}
In the controller , inject the ServiceFactory to get the related Service based on the the company Id
#RestController
public class ApiWebService {
#Autowired
private ServiceFactory serviceFactory;
#GetMapping("insert/order")
public void test() {
Integer companyId = getCompanyIdFromToken(httpServletRequest);
BaseService service = serviceFactory.getService(companyId);
service.blablabla();
}
}
Inject (autowire) ApplicationContext into your class and use one of getBeans* method to find the exact bean you need.
aspect
#Aspect
#Component
public class ApiAspect {
#Pointcut("execution(* com.example.demo.control.ApiWebService.*(..))")
public void apiInputWebService() {
}
#Before("apiInputWebService()")
public void apiInputAuth(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String token = request.getHeader("Authorization");
//compangId can be from token
String compangId = "one";
Object target = joinPoint.getTarget();
Method method = target.getClass().getMethod("before", String.class);
method.invoke(target, compangId);
}
}
control
#RestController
public class ApiWebService {
private ApiService baseService;
#Autowired
private ApplicationContext applicationContext;
public void before(String company) {
baseService = (ApiService) applicationContext.getBean(company);
}
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
service
#Service
public class ApiService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
#Service("one")
public class CompanyOneService extends ApiService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends ApiService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}

Autowired annotation doesn't work inside custom dozer converter

I am using next dozer custom converter
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
#Autowired
private AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
My problem is when it goes through convertTo method inside the converter, I always got appConfig instance with null value which of course cause a null pointer exception
Note: my spring boot class have these annotations above:
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan({"com.xxx"})
#EntityScan("com.xxx")
#EnableJpaRepositories("com.xxx")
I solved this by next trick:
1- Using static with appConfig property.
2- instantiate it by spring so when dozer use default empty constructor it will find appConfig have
a value already (which assigned before to it by spring)
And here are the code i used for this:
#Component //import org.springframework.stereotype.Component;
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
private static AppConfig appConfig;
// dozer needs this constructor to create an instance of converter (so it's a mandatory constructor)
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
}
#Autowired // Spring will pass appConfig to constructor
public MyCustomDozerConverter(AppConfig appConfig) {
this();
this.appConfig = appConfig;
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
UPDATE: Another solution
Another trick is using Spring ApplicationContextAware to get a singleton object from getBean method:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
Then create a static method inside AppConfig class and return an instance of the single bean matching the required type:
import org.springframework.context.annotation.Configuration;
import com.tripbru.ms.experiences.ApplicationContextHolder;
#Configuration
public class AppConfig {
// Static method used to return an instatnce
public static AppConfig getInstance() {
return ApplicationContextHolder.getContext().getBean(AppConfig.class);
}
// Properties
}
Then calling it direct inside the dozer converter by AppConfig.getInstance();
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
private static AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
appConfig = AppConfig.getInstance(); // Here are we intializing it by calling the static method we created.
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
Try constructor dependency injection
private AppConfig appConfig;
#Autowired
MyCustomerDozerConverter(AppConfig appConfig)
{
this.appConfig = appConfig;
}
You can put following line in the CustomConverter so that Spring will autowire it.
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
#Autowired
private AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
...
}

Inject bean into enum

I have the DataPrepareService that prepare data for reports and I have an Enum with report types, and I need to inject ReportService into Enum or have access to ReportService from enum.
my service:
#Service
public class DataPrepareService {
// my service
}
my enum:
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
REPORT_3("name", "filename")
public abstract Map<String, Object> getSpecificParams();
public Map<String, Object> getCommonParams(){
// some code that requires service
}
}
I tried to use
#Autowired
DataPrepareService dataPrepareService;
, but it didn't work
How can I inject my service into enum?
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename");
#Component
public static class ReportTypeServiceInjector {
#Autowired
private DataPrepareService dataPrepareService;
#PostConstruct
public void postConstruct() {
for (ReportType rt : EnumSet.allOf(ReportType.class))
rt.setDataPrepareService(dataPrepareService);
}
}
[...]
}
weekens' answer works if you change inner class to static so spring can see it
Maybe something like this:
public enum ReportType {
#Component
public class ReportTypeServiceInjector {
#Autowired
private DataPrepareService dataPrepareService;
#PostConstruct
public void postConstruct() {
for (ReportType rt : EnumSet.allOf(ReportType.class))
rt.setDataPrepareService(dataPrepareService);
}
}
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
...
}
There is one another approach you may like to explore. However instead of injecting a bean into enum it associates a bean with an enum
Say you have an enum WidgetType and Widget class
public enum WidgetType {
FOO, BAR;
}
public class Widget {
WidgetType widgetType;
String message;
public Widget(WidgetType widgetType, String message) {
this.widgetType = widgetType;
this.message = message;
}
}
And you want to create Widgets of this type using a Factory BarFactory or FooFactory
public interface AbstractWidgetFactory {
Widget createWidget();
WidgetType factoryFor();
}
#Component
public class BarFactory implements AbstractWidgetFactory {
#Override
public Widget createWidget() {
return new Widget(BAR, "A Foo Widget");
}
#Override
public WidgetType factoryFor() {
return BAR;
}
}
#Component
public class FooFactory implements AbstractWidgetFactory {
#Override
public Widget createWidget() {
return new Widget(FOO, "A Foo Widget");
}
#Override
public WidgetType factoryFor() {
return FOO;
}
}
The WidgetService is where most of the work happens. Here I have a simple AutoWired field which keeps tracks of all the registered WidgetFactories. As a postConstruct operation we create a map of the enum and the associated factory.
Now clients could inject the WidgetService class and get the factory for the given enum type
#Service
public class WidgetService {
#Autowired
List<AbstractWidgetFactory> widgetFactories;
Map<WidgetType, AbstractWidgetFactory> factoryMap = new HashMap<>();
#PostConstruct
public void init() {
widgetFactories.forEach(w -> {
factoryMap.put(w.factoryFor(), w);
});
}
public Widget getWidgetOfType(WidgetType widgetType) {
return factoryMap.get(widgetType).createWidget();
}
}
Enums are static, so you have to figure out a way to access to the beans from a static context.
You can create a class named ApplicationContextProvider that implements the ApplicationContextAware interface.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext appContext = null;
public static ApplicationContext getApplicationContext() {
return appContext;
}
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
this.appContext = appContext;
}
}
then add this your application context file:
<bean id="applicationContextProvider" class="xxx.xxx.ApplicationContextProvider"></bean>
after that you could access to the application context in a static way like this:
ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
it will be hard to control that the spring container is already up and running at the time the enum is instantiated (if you had a variable with this type in a test-case, your container will usually not be there, even aspectj autowiring won't help there). i would recommend to just let the dataprepare-service or something give you the specific-params with a lookup-method with the enum-parameter.
I think this what you need
public enum MyEnum {
ONE,TWO,THREE;
}
Autowire the enum as per usual
#Configurable
public class MySpringConfiguredClass {
#Autowired
#Qualifier("mine")
private MyEnum myEnum;
}
Here is the trick, use the factory-method="valueOf" and also make sure
lazy-init="false"
so the container creates the bean upfront
<bean id="mine" class="foo.bar.MyEnum" factory-method="valueOf" lazy-init="false">
<constructor-arg value="ONE" />
</bean>
and you are done!
Just pass it to the method manually
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
REPORT_3("name", "filename")
public abstract Map<String, Object> getSpecificParams();
public Map<String, Object> getCommonParams(DataPrepareService dataPrepareService){
// some code that requires service
}
}
As long as you call the method only from managed beans, you can inject it in these beans and pass the reference to the enum on each call.
Maybe you can use this solution ;
public enum ChartTypes {
AREA_CHART("Area Chart", XYAreaChart.class),
BAR_CHART("Bar Chart", XYBarChart.class),
private String name;
private String serviceName;
ChartTypes(String name, Class clazz) {
this.name = name;
this.serviceName = clazz.getSimpleName();
}
public String getServiceName() {
return serviceName;
}
#Override
public String toString() {
return name;
}
}
And in another class which you need the bean of the Enum :
ChartTypes plotType = ChartTypes.AreaChart
Object areaChartService = applicationContext.getBean(chartType.getServiceName());

Categories