David M. Karr
David M. Karr

Reputation: 15215

How can I manually evaluate the expression in a Spring @Value annotation?

My SpringBoot application has a bunch of @Value annotations. When the application is deployed to our Kubernetes cluster, it ends up using a properties file that is interpolated through a couple of different mechanisms. When it finally gets there, if developers make simple mistakes, the container can fail to start up, simply because they didn't set all the properties correctly. It's not easy to discover that this is what happened, until well after the mistake is made.

Note that virtually all of these @Value annotations will use the "${}" syntax, as opposed to "#{}". The primary concern is reading particular properties from a properties file, not Spring bean properties.

So, what I want to write is a little validation script (a small Java class), which does something like this:

  1. Obtain the path to the generated properties file
  2. Load that properties file into a Properties object
  3. Scan the classpath for all classes (with a base package), and all fields in those classes, for @Value annotations
  4. For each found @Value annotation, do some simple validation and evaluate the expression
  5. If the validation or the evaluation fails, print an error message with all relevant details.

This script will run before the "kubectl rollout" happens. If we see these error messages before the rollout, we will save time diagnosing these problems.

I've been able to achieve everything so far except doing something with the loaded properties file and evaluating the expression. I know that Spring uses a bean postprocessor, but I don't know how I can manually call that.

Any idea how to fulfill that missing link?

Update:

I still don't have an answer to this.

I was thinking that perhaps the answer would be found in a BeanPostProcessor in the Spring codebase, so I cloned the spring-framework repo. I found a couple of potential ones, being "AutowiredAnnotationBeanPostProcessor", "BeanFactoryPostProcessor", "CommonAnnotationBeanPostProcessor", and "BeanPostProcessor", but I just don't see anything in any of these that looks like evaluating the expression in the Value annotation. I would have tried setting a breakpoint in the "value()" method of the annotation, but of course you can't set a breakpoint in a method like that.

Update:

To be clear, this expression is not a "Spring EL" expression. Those reference bean properties (or loaded properties) and begin with "#{". I'm working with expressions that just reference properties, which begin with "${". I did try parsing the expression with Spring EL, but it just thinks there's nothing there.

Upvotes: 0

Views: 1600

Answers (1)

David M. Karr
David M. Karr

Reputation: 15215

I've managed to figure this out. The key is the "PropertyPlaceholderHelper.replacePlaceholders(String, Properties)" method. Using that, I developed something like this:

        PropertyPlaceholderHelper   propertyPlaceholderHelper   =
                new PropertyPlaceholderHelper("${", "}", ":", true);

        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);

        boolean foundAtLeastOneUnfoundProperty  = false;

        for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
            String beanClassName = bd.getBeanClassName();
            Class<?>    clazz   = Class.forName(beanClassName);
            for (Field field : clazz.getDeclaredFields()) {
                Value valueAnnotation = field.getAnnotation(Value.class);
                if (valueAnnotation != null) {
                    Matcher matcher = propertyRefPattern.matcher(valueAnnotation.value());
                    if (matcher.matches()) {
                        String  resultingValue  = propertyPlaceholderHelper.replacePlaceholders(valueAnnotation.value(), properties);
                        if (resultingValue.equals(valueAnnotation.value())) {
                            // This means that the property was not found.
                            System.out.println("ERROR: Expression \"" + valueAnnotation.value() +
                                               "\" on field \"" + field.getName() + "\" in class \"" + beanClassName +
                                               "\" references a property which is not defined.");
                            foundAtLeastOneUnfoundProperty  = true;
                        }
                    }
                }
            }
        }

Upvotes: 2

Related Questions