Skip to content

@Bean on Java 8 default methods in interfaces [SPR-10919] #15547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Sep 16, 2013 · 10 comments
Closed

@Bean on Java 8 default methods in interfaces [SPR-10919] #15547

spring-projects-issues opened this issue Sep 16, 2013 · 10 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Sep 16, 2013

Thomas Darimont opened SPR-10919 and commented

It would be great if the JavaConfig configuration style would support the definition of @Bean Bean-Definitions via Java 8's default methods.

To get this working it is necessary, that default methods are also considered while scanning a type for @Bean Bean-Definitions.

Here is a small example on how this would look like:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class InterfaceBasedJavaConfigExample {

    interface Service0 {}

    interface Service1 {}

    interface DataStore {}

    interface PersistenceConfig {
        @Bean
        default DataStore dataStore() {
            return mock(DataStore.class);
        }
    }

    interface ServiceConfig {
        @Bean
        default Service1 service1() {
            return mock(Service1.class);
        }
    }

    static class Application {

        final Service0 service0;
        final Service1 service1;
        final DataStore dataStore;

        @Autowired
        public Application(Service0 service0, Service1 service1, DataStore dataStore) {
            this.service0 = service0;
            this.service1 = service1;
            this.dataStore = dataStore;
        }
    }

    @Configuration
    static class ApplicationConfig implements ServiceConfig, PersistenceConfig {

        @Bean
        public Service0 service0() {
            return mock(Service0.class);
        }

        @Bean
        public Application application() {
            return new Application(service0(), service1(), dataStore());
        }
    }

    @Autowired
    Application application;

    @Test
    public void bootstrap() {
        assertNotNull("app should not be null", application);
        assertNotNull("app.dataStore should not be null", application.dataStore);
        assertNotNull("app.service1 should not be null", application.service1);
    }
}

Affects: 4.0 M3

Issue Links:

Referenced from: commits 1924629

4 votes, 11 watchers

@spring-projects-issues
Copy link
Collaborator Author

Thomas Darimont commented

An extension to this would be the support of pure interface based configurations - but I think this is another issue by itself, since it requires a proxy (or an instance of a dynamically generated class) to be created in order to be able invoke the default methods.

Here is a small example in the context of the scenario mentioned above:

@Configuration
interface ApplicationConfig extends ServiceConfig, PersistenceConfig {
    @Bean
    default Application application() {
        return new Application(service1(), dataStore());
    }
}

@spring-projects-issues
Copy link
Collaborator Author

Thomas Darimont commented

Added PR: #368

@spring-projects-issues
Copy link
Collaborator Author

Oliver Drotbohm commented

As per the discussion with Juergen Hoeller today, this ticket can be expanded to the general support of abstract JavaConfig types. This is achieved by actually defining semantics of (currently unsupported) abstract @Bean methods in configuration classes.

The suggested semantics are that an abstract method is interpreted like a manual (XML) bean definition with only the class attribute defined. So with the approach outlined above, this could look something like this:

@Configuration
interface ApplicationConfig extends ServiceConfig, PersistenceConfig {

  @Bean default Application application() {
    return new Application(service1(), dataStore());
  }

  @Bean MyApplicationComponent beanName();
}

The container would create a BeanDefinition for MyApplicationComponent an register it under beanName. To create an instance for the bean definition default semantics apply (either through an @Autowired constructor or a default constructor). Bean definition customizations (scopes, qualifiers) can be expressed on the @Bean method just like with any implemented method.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I'm pushing this back to the 4.x backlog since we're on a rather tight schedule towards 4.1 now (with a GA target in July). So let's rather consider this for 4.2 (which is what the 4.x backlog means at the moment).

I did play a bit with the code there... There are a couple of challenges: introspecting @Bean annotations on interfaces, instantiating @Configuration interfaces, etc. There's also a design question whether we even want that model to work. All in all, this needs some proper focus and should be a major theme for 4.2 rather than a side note for 4.1.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Thomas Darimont commented

Btw. another use case for @Bean-Definitions from default methods could be testing.

Instead of creating a custom @Configuration annotated config class with some @Bean definitions like mocks, stubs etc. and referencing it in the test class, one could just define a potentially shared interface with some default @Bean definitions and let the particular test class just implement that interface.

A test context listener (defined via SpringJUnit4ClassRunner) could then recognize the defined
default @Bean definitions and use them for autowiring.

interface FooService{}
interface CommonMocks{
	default @Bean FooService fooService() { return Mockito.mock(FooService.class); }
}
...
class SomeTest implements CommonMocks{
	
	@Autowired FooService fooService;

	@Test
	public void someTest(){

	   assertThat(fooService.businessMethod(), ....)
	}
}

@spring-projects-issues
Copy link
Collaborator Author

Oliver Drotbohm commented

Keep the examples coming, Thomas! I like them quite a lot and they'd just make the story more convincing.

@spring-projects-issues
Copy link
Collaborator Author

Thomas Darimont commented

Great thanks :)

As an alternative to the above - one could also create: SAM Type interfaces for Mock components:

public interface CommonMocks {

	public static interface WithMockDataService {

		@Bean
		default DataService dataService() {
			return mock(DataService.class);
		}
	}

	public static interface WithMockNotificationService {

		@Bean
		default NotificationService notificationService() {
			return mock(NotificationService.class);
		}
	}
}
import static ...CommonMocks.*;

...
class SomeTest implements WithMockDataService{ //Just import the mocking variant you need
	
	@Autowired DataService dataService;

	@Test
	public void someTest(){

	   assertThat(dataService.businessMethod(), ....)
	}
}

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Dec 6, 2014

cemo koc commented

This is not directly related to this issue but I think that It would be great to revisit to create bean from abstract types such as Spring Data Interface Repositories. I have already filed an issue here #16286 but It seems It does not have enough attention.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 18, 2015

Juergen Hoeller commented

Let's reduce this ticket to @Bean on Java 8 default methods again, since we have #16362 for class-only @Bean declarations in the meantime, as well as #16286 for the abstract component scanning part.

The good news: With this reduced scope, it's basically done and will make it into master by the end of this week!

Note that we're only supporting default methods on implemented interfaces at this point. Configuration classes still need to be concrete; they may implement interfaces and inherit default @Bean methods from there just like they can from a superclass.

Configuration interfaces purely based on default methods would be quite a significant step beyond that, and raise some serious questions about whether that constitutes a misuse of default methods. This is not planned for 4.2, and might not ever happen.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 19, 2015

Juergen Hoeller commented

The @Bean default methods case is covered along with #17419 now. This includes an extension of MethodMetadata for the detection of @Bean default methods through identifying non-abstract methods in interfaces.

Juergen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants