Spring-boot: Skip scoped targets when determining endpoint beans

Created on 1 Nov 2018  路  24Comments  路  Source: spring-projects/spring-boot

Hi, this is a first-timers-only issue. This means we've worked to make it more legible to folks who either haven't contributed to our codebase before, or even folks who haven't contributed to open source before.

If that's you, we're interested in helping you take the first step and can answer questions and help you out as you do. Note that we're especially interested in contributions from people from groups underrepresented in free and open source software!

If you have contributed before, consider leaving this one for someone new, and looking through our general ideal-for-contribution issues. Thanks!

Background

The @Endpoint annotation identifies a type as being an actuator endpoint. Spring Boot's actuator infrastructure looks for beans that have this annotation.

Problem

When Spring Cloud's @RefreshScope is used on an @Endpoint bean, it registers another bean for that endpoint with the bean name prefixed by scopedTarget. Spring Boot discovers this bean as well when looking for @Endpoint beans an fails because it finds two endpoint beans with the same ID.

Solution

We should filter out the scopedTarget. version of the endpoint bean using ScopedProxyUtils.isScopedTarget(name) as was done for a different type of bean in this commit.

The code for discovering endpoint beans can be found here.

The test can register two beans for the same @Endpoint class, one with the name prefixed by scopedTarget. and assert that only the original @Endpoint bean is discovered.

Steps to Fix

  • [x] Claim this issue with a comment below and ask any clarifying questions you need
  • [ ] Set up a repository locally following the Contributing Guidelines
  • [ ] Try to fix the issue following the steps above
  • [ ] Commit your changes and start a pull request.
first-timers-only superseded bug

All 24 comments

I want to work on this please.

@vpondala Cool! Please feel free to ask any questions you have. We'll look forward to your pull request.

Try on this issue, first time.

Thank you, @jbresesti, but @vpondala has already claimed this issue. Please keep an eye out for another first-timers-only issue and we鈥檇 be delighted to guide you through the process then.

Thank you, @jbresesti, but @vpondala has already claimed this issue. Please keep an eye out for another first-timers-only and we鈥檇 be delighted to guide you through the process then.

Can I follow this process in any way to learn?, How?, thank you.

The process is described in this issue鈥檚 description including links with information on setting up a local copy of the Boot repository and importing the code into your IDE.

To avoid dragging this issue further off topic, please follow up on Gitter with any further questions.

How are you getting on @vpondala? If there's anything we can do to help please don't hesitate to ask.

Apologies for the delay here @wilkinsona. I will be submitting PR in a day or so.

@wilkinsona @mbhave My sincere apologies. I am unable to work on this at the moment. Please reassign this to anyone else interested in contributing. Apologies again.

Can I solve this issue?

Also, Is there a sample project which i can use to reproduce the error and also to test it ?

I want to work on this please

According to the order of the comments, @jbresesti expressed interest first.

@jbresesti Please let us know if you are still interested in working on this issue. If not, we can let @rahul404 claim it.

Hi, can I claim this issue now?

@rahul404 It's all yours. Thank you. If you need us, we're here to answer any questions you have while working on the changes.

Hi, is there a code snippet which I can use to reproduce the error.

There are some details about how to reproduce the problem in @mbhave's description above:

register two beans for the same @Endpoint class, one with the name prefixed by scopedTarget.

This would look something like this:

@Configuration
static class ScopedTargetEndpointConfiguration {

    @Bean
    public TestEndpoint testEndpoint() {
        return new TestEndpoint();
    }

    @Bean(name = "scopedTarget.testEndpoint")
    public TestEndpoint scopedTargetTestEndpoint() {
        return new TestEndpoint();
    }

}

I would add a configuration class like this to EndpointDiscovererTests and then add a test that loads ScopedTargetEndpointConfiguration, creates a TestEndpointDiscoverer using the loaded context, calls getEndpoints() and asserts that only one endpoint is returned.

Before you make any changes to EndpointDiscoverer this test will fail as getEndpoints() will throw an exception due to both TestEndpoint beans being discovered.

Ok got it.
Maybe I misunderstood the directions, I did the following to reproduce the issue

@Configuration
@ImportAutoConfiguration(RefreshAutoConfiguration.class)
    static class RefreshEndpointClashingConfiguration{
        @Bean
        @RefreshScope
        public RefreshScopeTestEndpoint refreshScopeTestEndpoint() {
            return new RefreshScopeTestEndpoint();
        }
    }

@Endpoint(id = "refreshTest")
    static class RefreshScopeTestEndpoint{

    }

I will make use of your snippet instead.
But just to confirm, is my code an improper way of reproducing it?

No, that's a fine way to reproduce it and is what caused the problem that was originally reported. The recommendation to use the snippet above is because @RefreshScope is part of Spring Cloud. Spring Cloud depends on Spring Boot so we can't depend upon it in Spring Boot, even in a test, as doing so would create a circular dependency between the two projects.

Under the covers, @RefreshScope causes an extra bean to be created with scopedTarget. added to name of the original bean. ScopedTargetEndpointConfiguration is simulating what @RefreshScope does so that we can reproduce the problem without a dependency on Spring Cloud.

I have solved the issue but I am stuck at one of the test cases.
Following is the test case I have written.

@Test
public void getEndpointsWhenEndpointsArePrefixedWithScopedTargetShouldRegisterOnlyOneEndpoint() {
        load(ScopedTargetEndpointConfiguration.class, (
                context) -> {
            Collection<TestExposableEndpoint> collection =
                    new TestEndpointDiscoverer(context).getEndpoints();
            assertThat(collection).hasSize(1);
            TestExposableEndpoint endpoint = collection.iterator().next();
        });
    }

I want to include a test case as follows :

assertThat(beanName).doesNotStartWith("scopedTarget");

But the collection.iterator().next().getEndpointBean() returns object of type Object

Is there a way of getting bean name?

The process is described in this issue鈥檚 description including links with information on setting up a local copy of the Boot repository and importing the code into your IDE. ...

@wilkinsona

Sent with GitHawk

@rahul404 As you've noticed, the discoverer provides access to the beans themselves and not to the bean definition. It's the definition that would be needed to check the name and it isn't readily available.

I can see why it would be good to verify the name of the bean as it allows you to check that scopedTarget.testEndpoint has been filtered out rather than testEndpoint. You could do that by checking that the endpoint bean is the same as what is returned from the testEndpoint() method on ScopedTargetEndpointConfiguration. Something like this:

assertThat(endpoints.iterator().next().getEndpointBean()).isSameAs(context
        .getBean(ScopedTargetEndpointConfiguration.class).testEndpoint());

Thanks very much for the PR, @rahul404. I'll close this one in favour of it.

Thank you. You were a great help. I look forward to making more contributions.

Was this page helpful?
0 / 5 - 0 ratings