0

I'm trying to understand why the following code breaks with the below exception:

@Configuration
@EnableMetrics(proxyTargetClass = true)
public class MetricsConfig {

//................

    @Bean
    @ExportMetricWriter
    public MetricWriter statsdMetricWriter() {

        return new StatsdMetricWriter("13.127.9.150",8125);
     }

}

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'statsdMetricWriter' defined in class path resource [demo/MetricsConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.metrics.writer.MetricWriter]: Factory method 'statsdMetricWriter' threw exception; nested exception is java.lang.NoClassDefFoundError: com/timgroup/statsd/StatsDClientErrorHandler

while as soon as I add an annotation to statsdMetricWriter(): @ConditionalOnProperty(prefix = "statsd", name = {"host", "port"})

and provided the @ConfigurationProperties(prefix = "statsd") public class StatsdProperties{//...} exists the above Spring configuration runs fine.

What dictates to Spring machinery that new StatsdMetricWriter(String, int) shouldn't work?


Updates to address @Stéphane's confusion:

Yes, the question pertains to Spring in general as well, but I'm encountering it while using Spring Boot, hence that tag.

I'm trying to add a configuration class to an existing application to export its metrics to a statsd server, and found some relevant samples here and here based on which I'm building up my own Configuration class. Of course it's not empty, I simply copied here just the relevant pieces for brevity.

I don't necessarily want to use ConditionalOnProperty but it was part of the above samples and without it it doesn't seem to work.

The confusion on my side is, even in a minimal Configuration class like that shouldn't it be possible to say:


@Bean
@ExportMetricWriter
public MetricWriter statsdMetricWriter() {
    return new StatsdMetricWriter("10.101.7.130",8125);
 }

I wanted to think so, but the exception above seems to contradict my thinking. Revisiting the above links and qualifying the factory method with @ConditionalOnProperty(prefix = "statsd", name = {"host", "port"}) (without including any additional dependencies on the classpath) seems to eliminate the exception. That led to the question above. Hopefully that clarifies it a bit.


Update to the original question:

With the addition of missing dependencies as per comments below the original exception is gone but I'd like to clarify for myself how @ConditionalOnProperty operates. In my current setup there's a configuration bean:

@Configuration
@EnableMetrics(proxyTargetClass = true)
@EnableConfigurationProperties(StatsdProperties.class)
public class MetricsConfig {

@Value("${statsd.host:localhost}")
    private String host = "localhost";

    @Value("${statsd.port:8125}")
    private int port;

    @Autowired
    private StatsdProperties statsdProperties;

//......................

    @Bean
    @ConditionalOnProperty(prefix = "statsd", name = {"host", "port"})
    @ExportMetricWriter
    public MetricWriter statsdMetricWriter() {

        logger.info("creating statsdMetricWriter....");
        return new StatsdMetricWriter(
                statsdProperties.getPrefix(),
                statsdProperties.getHost(),
                statsdProperties.getPort()
        );
     }

}

and the related configuration properties bean:

@ConfigurationProperties(prefix = "statsd")
public class StatsdProperties {

    @Value("${statsd.host:13.127.9.150}")
    private String host;

    @Value("${statsd.port:8125}")
    private int port;

    private String prefix;

    //getters & setters

What's unclear is in its execution the @ConditionalOnProperty annotation:

  • how does it interpret the presence of the property - is it the value existence in the applicaiton.properties file (which seems to work) or is it the value existence as a default of an @Value annotations (which doesn't seem to be taken in the consideration in my testing)? Or something else perhaps?

  • what is a better practice - to have @Value inside the @Configuration classes or inside the @ConfigurationProperties classes or elsewhere?

Also, semantically, @ConditionalOnProperty seems a bit misleading, to me it implies the presence of a particular property in a class. Sounds what it really tries to assert is @ConditionalOnPropertyValue or am I not understanding something?

Thank you in advance.

Community
  • 1
  • 1
Simeon Leyzerzon
  • 18,658
  • 9
  • 54
  • 82
  • I am confused. You have a plain `@Configuration` class in your project that requires certain libraries and you don't put them in the classpath. Your class above has nothing to do with Spring Boot. It's just your app configuration. If you want to use condition and such, you should write an auto-configuration class instead. That being said, what do you expect Spring Boot to do with your `MetricsConfig`? – Stephane Nicoll Aug 31 '15 at 18:15
  • @StéphaneNicoll - I added a bit of explanation above. Thanks for answering. – Simeon Leyzerzon Aug 31 '15 at 18:53
  • The exception posted is a classpath problem. – chrylis -cautiouslyoptimistic- Aug 31 '15 at 18:55
  • @chrylis: that's what I'm asking about: does '@ConditionalOnProperty' add something else on the classpath. And if I don't want to use '@ConditionalOnProperty', what is it (besides com.ryantenney.metrics, io.dropwizard.metrics, and spring-boot-starter-actuator itself) that I'm missing on the classpath which would resolve that dependency? Does it make sense? – Simeon Leyzerzon Aug 31 '15 at 19:03
  • The annotation doesn't add anything, but it can suppress full class loading of negative matches, and so hide a runtime dependency. – chrylis -cautiouslyoptimistic- Aug 31 '15 at 19:10
  • @chrylis: I'm not sure I follow - could you elaborate? – Simeon Leyzerzon Aug 31 '15 at 19:21
  • The `@ConditionalOnProperty` does exactly that it makes the bean conditional based on the existence of that property. If the property isn't part of the configuration the bean isn't loaded and hence the missing class error doesn't appear. The `StatsdMetricsWriter` requires an additional library to be able to produce statsd message. That is what the class error is telling you and what the other commenters try to explain as well. You need [this](http://search.maven.org/#artifactdetails%7Ccom.timgroup%7Cjava-statsd-client%7C3.1.0%7Cjar) as additional dependency to make it work. – M. Deinum Sep 01 '15 at 05:25
  • And to extend Martijn answer, this has nothing to do with Spring Boot or `@ConditionalOnProperty`. You're just having some class requiring an extra jar and that jar is not present. Nothing magic about it. – Stephane Nicoll Sep 01 '15 at 07:19
  • Great, thanks everyone for the comments - now that the classpath is resolved and I can walk the source and see its inner workings I can see that StatsdMetricWriter depends on NonBlockingStatsDClient which comes from that jar that Martijn mentioned. May I offer a suggestion: could the documentation for statsd metrics (http://docs.spring.io/spring-boot/docs/1.3.0.M4/reference/htmlsingle/#production-ready-metric-writers-export-to-statsd) be updated with the required dependency as per Martijn's suggestion as without knowing the internals it's not obvious what needs to be added to the classpath. – Simeon Leyzerzon Sep 01 '15 at 16:07
  • I've opened [an issue](https://github.com/spring-projects/spring-boot/issues/3862) to update the documentation. – Andy Wilkinson Sep 02 '15 at 06:09

0 Answers0