From 963dd3f804657f4002ea4a68dc8d915a00261198 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 27 Sep 2017 00:09:00 +0200 Subject: [PATCH] Clarify @Bean return type recommendation in case of multiple interfaces Issue: SPR-15973 --- src/asciidoc/core-beans.adoc | 166 +++++++++++++++-------------------- 1 file changed, 70 insertions(+), 96 deletions(-) diff --git a/src/asciidoc/core-beans.adoc b/src/asciidoc/core-beans.adoc index aa8a32aa661..9462a53a4b4 100644 --- a/src/asciidoc/core-beans.adoc +++ b/src/asciidoc/core-beans.adoc @@ -170,8 +170,7 @@ as the local file system, from the Java `CLASSPATH`, and so on. [source,java,indent=0] [subs="verbatim,quotes"] ---- - ApplicationContext context = - new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); + ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); ---- [NOTE] @@ -715,7 +714,6 @@ factory method itself with the `factory-method` attribute. public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); - private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; @@ -747,9 +745,8 @@ One factory class can also hold more than one factory method as shown here: public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); - private static AccountService accountService = new AccountServiceImpl(); - private DefaultServiceLocator() {} + private static AccountService accountService = new AccountServiceImpl(); public ClientService createClientServiceInstance() { return clientService; @@ -758,7 +755,6 @@ One factory class can also hold more than one factory method as shown here: public AccountService createAccountServiceInstance() { return accountService; } - } ---- @@ -836,7 +832,6 @@ has no dependencies on container specific interfaces, base classes or annotation } // business logic that actually uses the injected MovieFinder is omitted... - } ---- @@ -859,7 +854,6 @@ being instantiated. Consider the following class: public Foo(Bar bar, Baz baz) { // ... } - } ---- @@ -905,7 +899,6 @@ by type without help. Consider the following class: this.years = years; this.ultimateAnswer = ultimateAnswer; } - } ---- @@ -978,7 +971,6 @@ then have to look as follows: this.years = years; this.ultimateAnswer = ultimateAnswer; } - } ---- -- @@ -1009,7 +1001,6 @@ on container specific interfaces, base classes or annotations. } // business logic that actually uses the injected MovieFinder is omitted... - } ---- @@ -1155,7 +1146,9 @@ part of a Spring XML configuration file specifies some bean definitions: public class ExampleBean { private AnotherBean beanOne; + private YetAnotherBean beanTwo; + private int i; public void setBeanOne(AnotherBean beanOne) { @@ -1169,7 +1162,6 @@ part of a Spring XML configuration file specifies some bean definitions: public void setIntegerProperty(int i) { this.i = i; } - } ---- @@ -1201,7 +1193,9 @@ in the XML file. The following example uses constructor-based DI: public class ExampleBean { private AnotherBean beanOne; + private YetAnotherBean beanTwo; + private int i; public ExampleBean( @@ -1210,7 +1204,6 @@ in the XML file. The following example uses constructor-based DI: this.beanTwo = yetAnotherBean; this.i = i; } - } ---- @@ -1253,7 +1246,6 @@ told to call a `static` factory method to return an instance of the object: // some other operations... return eb; } - } ---- @@ -2322,7 +2314,6 @@ the following class, with a method computeValue, which we want to override: } // some other methods... - } ---- @@ -3127,7 +3118,6 @@ see <>. For example, the following: public void init() { // do some initialization work } - } ---- @@ -3147,7 +3137,6 @@ see <>. For example, the following: public void afterPropertiesSet() { // do some initialization work } - } ---- @@ -3189,7 +3178,6 @@ With Java config, you use the `destroyMethod` attribute of `@Bean`, see public void cleanup() { // do some destruction work (like releasing pooled connections) } - } ---- @@ -3209,7 +3197,6 @@ is exactly the same as: public void destroy() { // do some destruction work (like releasing pooled connections) } - } ---- @@ -3266,7 +3253,6 @@ following example. throw new IllegalStateException("The [blogDao] property must be set."); } } - } ---- @@ -3356,7 +3342,6 @@ lifecycle requirements (e.g. starts and stops some background process): void stop(); boolean isRunning(); - } ---- @@ -3373,7 +3358,6 @@ defined within that context. It does this by delegating to a `LifecycleProcessor void onRefresh(); void onClose(); - } ---- @@ -3407,7 +3391,6 @@ another option, namely the `getPhase()` method as defined on its super-interface public interface Phased { int getPhase(); - } ---- @@ -3419,7 +3402,6 @@ another option, namely the `getPhase()` method as defined on its super-interface boolean isAutoStartup(); void stop(Runnable callback); - } ---- @@ -3496,9 +3478,7 @@ declared on the `ConfigurableApplicationContext` interface: public final class Boot { public static void main(final String[] args) throws Exception { - - ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( - new String []{"beans.xml"}); + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); // add a shutdown hook for the above context... ctx.registerShutdownHook(); @@ -3506,7 +3486,6 @@ declared on the `ConfigurableApplicationContext` interface: // app runs here... // main method exits, hook is called prior to the app shutting down... - } } ---- @@ -3526,7 +3505,6 @@ with a reference to that `ApplicationContext`. public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; - } ---- @@ -3562,7 +3540,6 @@ a reference to the name defined in its associated object definition. public interface BeanNameAware { void setBeanName(String name) throws BeansException; - } ---- @@ -3876,17 +3853,14 @@ Find below the custom `BeanPostProcessor` implementation class definition: public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is - public Object postProcessBeforeInitialization(Object bean, - String beanName) throws BeansException { + public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; // we could potentially return any object reference here... } - public Object postProcessAfterInitialization(Object bean, - String beanName) throws BeansException { + public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } - } ---- @@ -4326,7 +4300,6 @@ example: } // ... - } ---- @@ -4364,15 +4337,15 @@ You can apply the `@Autowired` annotation to constructors: } // ... - } ---- [NOTE] ==== -As of Spring Framework 4.3, the `@Autowired` constructor is no longer necessary if the -target bean only defines one constructor. If several constructors are available, at -least one must be annotated to teach the container which one it has to use. +As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is +no longer necessary if the target bean only defines one constructor to begin with. +However, if several constructors are available, at least one must be annotated to +teach the container which one to use. ==== As expected, you can also apply the `@Autowired` annotation to "traditional" setter @@ -4391,7 +4364,6 @@ methods: } // ... - } ---- @@ -4415,7 +4387,6 @@ arguments: } // ... - } ---- @@ -4437,10 +4408,23 @@ You can apply `@Autowired` to fields as well and even mix it with constructors: } // ... - } ---- +[TIP] +==== +Make sure that your target components (e.g. `MovieCatalog`, `CustomerPreferenceDao`) +are consistently declared by the type that you are using for your `@Autowired`-annotated +injection points. Otherwise injection may fail due to no type match found at runtime. + +For XML-defined beans or component classes found through a classpath scan, the container +usually knows the concrete type upfront. However, for `@Bean` factory methods, you need +to make sure that the declared return type is sufficiently expressive. For components +implementing several interfaces or for components potentially referred to by their +implementation type, consider declaring the most specific return type on your factory +method (at least as specific as required by the injection points referring to your bean). +==== + It is also possible to provide __all__ beans of a particular type from the `ApplicationContext` by adding the annotation to a field or method that expects an array of that type: @@ -4454,7 +4438,6 @@ of that type: private MovieCatalog[] movieCatalogs; // ... - } ---- @@ -4473,7 +4456,6 @@ The same applies for typed collections: } // ... - } ---- @@ -4501,7 +4483,6 @@ corresponding bean names: } // ... - } ---- @@ -4516,13 +4497,12 @@ indicating __required__ dependencies. This behavior can be changed as demonstrat private MovieFinder movieFinder; - @Autowired(required=false) + @Autowired(required = false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... - } ---- @@ -4558,7 +4538,6 @@ automatically resolved, with no special setup necessary. } // ... - } ---- @@ -4598,7 +4577,6 @@ _primary_ `MovieCatalog`. public MovieCatalog secondMovieCatalog() { ... } // ... - } ---- @@ -4614,7 +4592,6 @@ With such configuration, the following `MovieRecommender` will be autowired with private MovieCatalog movieCatalog; // ... - } ---- @@ -4668,7 +4645,6 @@ chosen for each argument. In the simplest case, this can be a plain descriptive private MovieCatalog movieCatalog; // ... - } ---- @@ -4692,7 +4668,6 @@ method parameters: } // ... - } ---- @@ -4809,6 +4784,7 @@ Then you can provide the custom qualifier on autowired fields and parameters: @Autowired **@Genre("Action")** private MovieCatalog actionCatalog; + private MovieCatalog comedyCatalog; @Autowired @@ -4817,7 +4793,6 @@ Then you can provide the custom qualifier on autowired fields and parameters: } // ... - } ---- @@ -4889,7 +4864,6 @@ Then add the annotation to the field or property to be autowired: private MovieCatalog offlineCatalog; // ... - } ---- @@ -4960,7 +4934,6 @@ for both attributes: `genre` and `format`. private MovieCatalog comedyBluRayCatalog; // ... - } ---- @@ -5040,7 +5013,6 @@ configuration: public IntegerStore integerStore() { return new IntegerStore(); } - } ---- @@ -5129,7 +5101,6 @@ demonstrated in this example: public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } - } ---- @@ -5149,7 +5120,6 @@ name "movieFinder" injected into its setter method: public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } - } ---- @@ -5189,7 +5159,6 @@ dependency type `ApplicationContext`. } // ... - } ---- @@ -5223,7 +5192,6 @@ pre-populated upon initialization and cleared upon destruction. public void clearMovieCache() { // clears the movie cache upon destruction... } - } ---- @@ -5381,7 +5349,6 @@ are eligible for such autodetection: public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } - } ---- @@ -5565,7 +5532,6 @@ annotated classes. Here is a simple example: public void doWork() { // Component method implementation omitted } - } ---- @@ -5621,7 +5587,6 @@ support for autowiring of `@Bean` methods: public TestBean requestScopedInstance() { return new TestBean("requestScopedInstance", 3); } - } ---- @@ -6013,7 +5978,7 @@ used as follows: import javax.inject.Inject; import javax.inject.Named; - @Named("movieListener") // @ManagedBean("movieListener") could be used as well + @Named("movieListener") // @ManagedBean("movieListener") could be used as well public class SimpleMovieLister { private MovieFinder movieFinder; @@ -6160,7 +6125,6 @@ The simplest possible `@Configuration` class would read as follows: public MyService myService() { return new MyServiceImpl(); } - } ---- @@ -6420,10 +6384,9 @@ the method name. The following is a simple example of a `@Bean` method declarati public class AppConfig { @Bean - public TransferService transferService() { + public TransferServiceImpl transferService() { return new TransferServiceImpl(); } - } ---- @@ -6446,6 +6409,39 @@ Both declarations make a bean named `transferService` available in the transferService -> com.acme.TransferServiceImpl ---- +You may also declare your `@Bean` method with an interface (or base class) +return type: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + public class AppConfig { + + @Bean + public TransferService transferService() { + return new TransferServiceImpl(); + } + } +---- + +However, this limits the visibility for advance type prediction to the specified +interface type (`TransferService`) then, with the full type (`TransferServiceImpl`) +only known to the container once the affected singleton bean has been instantiated. +Non-lazy singleton beans get instantiated according to their declaration order, +so you may see different type matching results depending on when another component +tries to match by a non-declared type (such as `@Autowired TransferServiceImpl` +which will only resolve once the "transferService" bean has been instantiated). + +[TIP] +==== +If you consistently refer to your types by a declared service interface, your +`@Bean` return types may safely join that design decision. However, for components +implementing several interfaces or for components potentially referred to by their +implementation type, it is safer to declare the most specific return type possible +(at least as specific as required by the injection points referring to your bean). +==== + [[beans-java-dependencies]] ==== Bean dependencies @@ -6465,7 +6461,6 @@ parameter: public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } - } ---- @@ -6498,12 +6493,14 @@ on the `bean` element: [subs="verbatim,quotes"] ---- public class Foo { + public void init() { // initialization logic } } public class Bar { + public void cleanup() { // destruction logic } @@ -6521,7 +6518,6 @@ on the `bean` element: public Bar bar() { return new Bar(); } - } ---- @@ -6562,6 +6558,7 @@ method directly during construction: ---- @Configuration public class AppConfig { + @Bean public Foo foo() { Foo foo = new Foo(); @@ -6570,7 +6567,6 @@ method directly during construction: } // ... - } ---- @@ -6604,7 +6600,6 @@ The default scope is `singleton`, but you can override this with the `@Scope` an public Encryptor encryptor() { // ... } - } ---- @@ -6657,7 +6652,6 @@ resulting bean. This functionality can be overridden, however, with the `name` a public Foo foo() { return new Foo(); } - } ---- @@ -6679,7 +6673,6 @@ annotation accepts a String array for this purpose. public DataSource dataSource() { // instantiate, configure and return DataSource bean... } - } ---- @@ -6705,7 +6698,6 @@ annotation can be used: public Foo foo() { return new Foo(); } - } ---- @@ -6740,7 +6732,6 @@ as having one bean method call another: public Bar bar() { return new Bar(); } - } ---- @@ -6837,7 +6828,6 @@ The following example shows a `@Bean` annotated method being called twice: public ClientDao clientDao() { return new ClientDaoImpl(); } - } ---- @@ -6905,7 +6895,6 @@ another configuration class: public B b() { return new B(); } - } ---- @@ -6961,7 +6950,6 @@ classes, each depending on beans declared in the others: public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } - } @Configuration @@ -6971,7 +6959,6 @@ classes, each depending on beans declared in the others: public AccountRepository accountRepository(DataSource dataSource) { return new JdbcAccountRepository(dataSource); } - } @Configuration @@ -6982,7 +6969,6 @@ classes, each depending on beans declared in the others: public DataSource dataSource() { // return new DataSource } - } public static void main(String[] args) { @@ -7023,7 +7009,6 @@ work on the configuration class itself since it is being created as a bean insta public TransferService transferService() { return new TransferServiceImpl(accountRepository); } - } @Configuration @@ -7040,7 +7025,6 @@ work on the configuration class itself since it is being created as a bean insta public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } - } @Configuration @@ -7051,7 +7035,6 @@ work on the configuration class itself since it is being created as a bean insta public DataSource dataSource() { // return new DataSource } - } public static void main(String[] args) { @@ -7100,7 +7083,6 @@ configuration classes themselves: // navigate 'through' the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); } - } ---- @@ -7129,7 +7111,6 @@ abstract class-based `@Configuration` classes. Consider the following: @Bean AccountRepository accountRepository(); - } @Configuration @@ -7139,11 +7120,10 @@ abstract class-based `@Configuration` classes. Consider the following: public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } - } @Configuration - @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! + @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean @@ -7259,7 +7239,6 @@ properly. public TransferService transferService() { return new TransferService(accountRepository()); } - } ---- @@ -7370,7 +7349,6 @@ bare minimum. public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } - } ---- @@ -7854,6 +7832,7 @@ a call to `testBean.getName()` will return "myTestBean". @Configuration **@PropertySource("classpath:/com/myco/app.properties")** public class AppConfig { + @Autowired Environment env; @@ -7876,6 +7855,7 @@ environment. For example: @Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") public class AppConfig { + @Autowired Environment env; @@ -7935,7 +7915,6 @@ To enable load-time weaving add the `@EnableLoadTimeWeaving` to one of your @Configuration @EnableLoadTimeWeaving public class AppConfig { - } ---- @@ -8125,7 +8104,6 @@ converted into Strings and inserted into placeholders in the lookup message. new Object [] {"userDao"}, "Required", null); System.out.println(message); } - } ---- @@ -8271,7 +8249,6 @@ simple class that extends Spring's `ApplicationEvent` base class: } // accessor and other methods... - } ---- @@ -8304,7 +8281,6 @@ example demonstrates such a class: } // send email... } - } ---- @@ -8332,7 +8308,6 @@ demonstrates such a class: public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } - } ---- @@ -8407,7 +8382,6 @@ follows: public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } - } ----