4

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.

Community
  • 1
  • 1
MissBonbon
  • 141
  • 1
  • 4
  • 16
  • You are instantiating your logger outside spring (`logger = new LoggerImpl("ServiceName");`). Why? – pleft Nov 04 '16 at 14:06

2 Answers2

5

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.

trent
  • 25,033
  • 7
  • 51
  • 90
Bartosz Bilicki
  • 12,599
  • 13
  • 71
  • 113
  • 1
    First I want to thank you for investing time to answer. The usage of @Configuration will force me to do for every class a new Bean. In my current situation, I have more than 15 classes. So I would create as less 15 Beans manually. What do you mean with @Qualifier? Can you give me an example or an link for that? – MissBonbon Nov 04 '16 at 14:33
  • See my updated edit (LoggerFactory). I agree that creating separate Bean every time makes no sense in your situation. For Qualifier, also see edit to post. – Bartosz Bilicki Nov 04 '16 at 14:36
  • Just two more comments. There are many loging frameworks around. Do you really need your own? Also, Logger usually has no dependencies and cant be created directly from constructor or static factory method. In that case it is not managed by Spring. No big deal, because usually there is no need to mock Logger. So dependency on static factory is perfectly fine. – Bartosz Bilicki Nov 04 '16 at 14:39
  • Sounds well. But not resolve the problem. When have a closer look. Then it is clear, why it do not work. I just created a workaround with the same result. In the new LoggerFactory I'm also creating a new instance, even not in the application context. – MissBonbon Nov 04 '16 at 14:54
  • Yes, there are lots of Logging API's and they are pretty well. But I need to do my own. :-( I loved the slf4j logger. But in my case, I can't use it. – MissBonbon Nov 04 '16 at 14:56
  • If someone interested for the solution: I used the LoggerFactory from @Bartosz as basic. I created a LoggerFactory which is Inject. This has the database communication. To get the database access from the LoggerFactory to the LoggerImpl, the Implementation of the Logger is an inner class from the LoggerFactory. Now it works :) – MissBonbon Nov 07 '16 at 12:19
  • @MissBonbon, thanks for update. It is always good to know that provided answer was helpfull. But I am still not convinced about need of writing new logging framework. I understood you need to log to database, but all modern loging frameworks (slf4j, log4j) support that. For example see http://logback.qos.ch/manual/appenders.html#DBAppender Even if you want to log to more exotic (noSQL?) database you just need to write new appender, not new logging framework. Existing frameworks cover a lot use cases for you (configuration file format, log-levels, etc). – Bartosz Bilicki Nov 07 '16 at 12:58
5

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.

Ruben
  • 3,986
  • 1
  • 21
  • 34
  • This is not clear for me. When I'm use the @Autowired to the Logger, where I set the DeclaredType of IP? I get just the name of the Interface Logger. That is not what I want to know. – MissBonbon Nov 07 '16 at 09:36
  • 1
    @MissBonbon you are right, sorry. I used the wrong method of InjectionPoint. With `injectionPoint.getMember().getDeclaringClass()` you should now get the class that is getting the `Logger` injected (edited the answer) – Ruben Nov 07 '16 at 12:22