Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot - How to @Autowired a Bean with non-default constructor?

I found a lot informations about the usage of @Autowired with a non-empty constructor here. But I'm even not able to resolve my current problem.

I have an interface for logging. I'm logging every entry in the database. For this reason for me it is important to know the class name, which class uses the logger.

My first idea: Create a new implementation of the Logger, which has a non-empty constructor. So, while creating a new instance of this logger, you have to set a name.

My Interface looks like this:

public interface Logger{
  public void log(String name, String msg);
}

The implementation of that looks like this:

@Service
public class LoggerImpl implements Logger{
 private String className;

 LoggerImpl(String className){ this.className = className }

 @Override
 public void log(String name, String msg) { // print and save in the database }
}

And everywhere of my application I want to use this logger - Interface. For example in some business classes I'm using the Logger in this way:

@Service
public class ServiceName {
 private Logger logger;

 @Autowired
 public ServiceName(){
   logger = new LoggerImpl("ServiceName");
 }

 public void someMethod(){
    logger.log("name", "this is a log!");
 }
}

But my application tells me, that he has no database-communication and throw a NullPointerException, while saving the log in the database. Before creating a non-empty constructor this communication worked. So my guess is, while I creating this LoggerImpl manually, this object is not in the spring application context any more. Any ideas to resolve? Thank you. Edit: I'm working with Spring Boot and I do not have any XML-files.

like image 620
MissBonbon Avatar asked Jan 06 '23 02:01

MissBonbon


2 Answers

I think that in this case annotating with @Service is not enough. Spring will not be able to create instance of LoggerImpl.

You must tell Spring how to create instance. When using XML configuration you should do

<bean id="loggerForX" class="LoggerImpl ">
  <constructor-arg index="0" value="X" />
</bean>

When using javaconfig

@Configuration
public class LoggerConfiguration {

  @Bean
  public Logger loggerForX() {
    return new LoggerImpl("X");
  }

}

Then you will be able to @Autowire Logger as

@Autowire  Logger loggerForX;

If this does not work, try autowiring loggerForX variable ab

@Autowire @Qualifier("loggerForX") Logger loggerForX;

With this approach you have to create separate bean for every class that wants logger. It may be better to create LoggerFactory as bean, and inject that LoggerFactory. LoggerFactory must have method to create Logger.

@Service
class LoggerFactory  {

  public Logger loggerFor(String name) {
    return new LoggerImpl(name);
    }

}

LoggerFactory has default constructor and can easily be @Autowired everywhere you want.

like image 99
Bartosz Bilicki Avatar answered Jan 13 '23 10:01

Bartosz Bilicki


You may want to have a look at the new InjectionPoint

@Configuration
public class LoggerConfig {

  @Bean
  @Scope("prototype")
  public Logger logger(InjectionPoint injectionPoint) {
    //this is the class where the logger will be used     
    Class<?> theClassYouAreLookingFor = injectionPoint.getMember().getDeclaringClass();
    return findOrCreateYourLogger(theClassYouAreLookingFor);
  }
}

To use the Logger just inject it as you would normally do with any other Bean.

like image 38
Ruben Avatar answered Jan 13 '23 09:01

Ruben