David M. Karr
David M. Karr

Reputation: 15205

Why is applicationContext.getBean(beanName) returning null, when it clearly exists?

I'm working on an experimental method that will take a bean name, property name, and value expression, and use the Spring SPeL to assign that property of that bean with that value. The class with this method is a ManagedResource, so I can reach it from JMX.

I then defined another simple class, gave it a property and a Component annotation. I also Autowire this class into the jmx bean class, just to verify the bean of that type exists.

I then fired up the SpringBoot service.

I then called the method from VisualVM.

It failed, saying it couldn't find the bean with that name.

So, now with more detail.

Here's the first class:

@Component
@ManagedResource
public class JMXDemonstration {
    @Autowired
    private ApplicationContext  applicationContext;
    @Autowired
    private SomeRandomThing thing;

    @Value("${jmxDemonstration.name}")
    private String name;

    @ManagedAttribute
    public String getName() { return name; }

    @ManagedAttribute
    public void setName(String name) { this.name = name; }

    @ManagedOperation
    public String buildHelloWorldMessage() {
        return "Hello, " + name + ": " + thing.getId();
    }

    @ManagedOperation
    public void assignValueToBeanProperty(String beanName, String propertyName, String expression) {
        Object  bean    = applicationContext.getBean(beanName);

        ExpressionParser        parser      = new SpelExpressionParser();
        SimpleEvaluationContext evalContext = SimpleEvaluationContext.forReadWriteDataBinding().build();

        parser.parseExpression(propertyName).setValue(evalContext, bean, expression);
    }
}

And here's the other class:

@Component
public class SomeRandomThing {
    private String  id;

    public String getId() { return id; }

    public void setId(String id) { this.id = id; }
}

When I call the method from VisualVM, I pass "SomeRandomThing", "id", and "xxx".

This fails with:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'SomeRandomThing' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:685)

I also set a breakpoint in the method and looked at "this", and confirmed that there is a valid "thing" property (if there wasn't, the service wouldn't have started).

So do I have the default bean name algorithm wrong? It seems hard to believe, because I also ran a test of "JMXDemonstration", "name", and "George", and that worked perfectly fine.

Update:

Also note that calling "applicationContext.getBean(SomeRandomThing.class)" returns the bean instance of that class, and also calling "applicationContext.getBeanDefinitionNames()" returns an array that does not contain "SomeRandomThing", but it does contain "JMXDemonstration".

Why is SomeRandomThing available as a bean through autowiring and through the type, but not through it's bean name?

Update:

Oh, because the bean name is "someRandomThing", not "SomeRandomThing". I guess I would have expected the former originally, but when I saw that the bean name for "JMXDemonstration" was "JMXDemonstration", I assumed it would then be "SomeRandomThing", not "someRandomThing".

Upvotes: 0

Views: 8763

Answers (1)

Ryuzaki L
Ryuzaki L

Reputation: 39988

Bean Naming

@Component is a class level annotation. During the component scan, Spring Framework automatically detects classes annotated with @Component.

@Component
class CarUtility {
   // ...
}

By default, the bean instances of this class have the same name as the class name with a lowercase initial. On top of that, we can specify a different name using the optional value argument of this annotation.

Annotation based Configuration

For stereotype annotation based bean, if the name is not explicitly specified with the value field of stereotype annotations, then the name is again generated by AnnotationBeanNameGenerator which is an implementation of the BeanNameGenerator strategy interface here

If the annotation's value doesn't indicate a bean name, an appropriate name will be built based on the short name of the class (with the first letter lower-cased). For example:

com.xyz.FooServiceImpl -> fooServiceImpl

With component scanning in the classpath, Spring generates bean names for unnamed components, following the rules described earlier: essentially, taking the simple class name and turning its initial character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by doc java.beans.Introspector.decapitalize (which Spring uses here).

Upvotes: 1

Related Questions