上篇說的是無需半行xml配置完成bean的自動化注入。這篇仍然不要任何xml配置,通過Java代碼也能達到同樣的效果。
這么說,是要把上篇的料拿出來再煮一遍? 當然不是,上篇我們幾乎都在用注解的方式如@ComponentScan @Component等就完成了自動化注入,但是這些注解不是無所不能的,有些地方它也是望塵莫及的,比如第三方類庫,你總不能跑到人家jar包或者庫里面悄悄的加上這些注解再偽裝成原來的樣子用吧,太丑了! 所以,Spring可以通過顯示配置的方式來解決,第一種前面有介紹過,就是通過xml來顯示聲明bean,第二種就是這里要介紹的基于Java代碼方式裝配bean。
?
基于注解的自動化注入固然優雅,但是它也有鞭長莫及的時候,這時候就來看看Java代碼如何裝配bean的。
還記的上篇的配置類CDPlayerConfig是長這樣的
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
有了這個萬能的組件掃描注解,一切都是那么只能,只需要在bean類上加上如@Component注解,Spring就會自動為該類創建相應的bean類。
但是現在因為我們有一些第三方類庫,我們沒辦法去深入類中加上這些標記了,所以@ComponentScan就失去了威力和意義。
?
這篇的CDPlayerConfig應該長這樣
@Configuration
public class CDPlayerConfig {
}
@Configuration注解表示該類是一個配置類。顯然要創建的bean的信息是要放到這個類中的。
基于其他類以及類中的方法不變比如CompactDisc, CDPlayer, SgtPeppers等。編寫測試方法如下
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {@Rulepublic final StandardOutputStreamLog log = new StandardOutputStreamLog();@Autowiredprivate MediaPlayer player;@Autowiredprivate CompactDisc cd;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}@Testpublic void play() {player.play();assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n",log.getLog());}}
?
首先從代碼來看就會出現如下的注入錯誤
?
這里顯示MediaPlayer無法注入。同時運行程序得到結果如下
Testing started at 0:20 ...
0:20:03: Executing external tasks 'cleanTest test'...
:cleanTest
:compileJava
:processResources UP-TO-DATE
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
一月 15, 2017 12:20:06 上午 org.springframework.test.context.TestContextManager retrieveTestExecutionListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
一月 15, 2017 12:20:06 上午 org.springframework.test.context.TestContextManager retrieveTestExecutionListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
一月 15, 2017 12:20:06 上午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@6adf0b01: startup date [Sun Jan 15 00:20:06 CST 2017]; root of context hierarchy
一月 15, 2017 12:20:06 上午 org.springframework.test.context.TestContextManager prepareTestInstance
嚴重: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@61c715e8] to prepare test instance [soundsystem.CDPlayerTest@1081b08c]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'soundsystem.CDPlayerTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private soundsystem.MediaPlayer soundsystem.CDPlayerTest.player; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:293)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:331)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:213)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:290)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:292)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)at org.junit.runners.ParentRunner.run(ParentRunner.java:309)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:105)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:56)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private soundsystem.MediaPlayer soundsystem.CDPlayerTest.player; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509)at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290)... 46 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1118)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:967)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:481)... 48 moreError creating bean with name 'soundsystem.CDPlayerTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private soundsystem.MediaPlayer soundsystem.CDPlayerTest.player; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'soundsystem.CDPlayerTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private soundsystem.MediaPlayer soundsystem.CDPlayerTest.player; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:293)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:331)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:213)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:290)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:292)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)at org.junit.runners.ParentRunner.run(ParentRunner.java:309)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:105)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:56)at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private soundsystem.MediaPlayer soundsystem.CDPlayerTest.player; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509)at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290)... 46 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [soundsystem.MediaPlayer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1118)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:967)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:481)... 48 moresoundsystem.CDPlayerTest > play FAILEDorg.springframework.beans.factory.BeanCreationExceptionCaused by: org.springframework.beans.factory.BeanCreationExceptionCaused by: org.springframework.beans.factory.NoSuchBeanDefinitionException
1 test completed, 1 failed
:test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///C:/Users/Administrator/Desktop/spring_video/SpringiA4_SourceCode/Chapter_02/stereo-javaconfig/build/reports/tests/index.html
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 4.59 secs
There were failing tests. See the report at: file:///C:/Users/Administrator/Desktop/spring_video/SpringiA4_SourceCode/Chapter_02/stereo-javaconfig/build/reports/tests/index.html
究其原因,主要是既沒有在xml中沒有聲明相應的bean,也沒有添加@ComponentScan啟動自動掃描組件機制。
所以這里還是需要在CDPlayerConfig配置類中做文章。
?
聲明bean
通過注解@Bean聲明一個bean對象。
@Beanpublic CompactDisc compactDisc() {return new SgtPeppers();}
該聲明好比在xml中添加
<bean id="sgtPeppers" class="soundsystem.SgtPeppers"/>
通過這種方式,Spring會默認為SgtPeppers創建一個名為sgtPeppers的bean,如果你想換個名字,只需要在@Bean后面加上類似(name="lonelyHeartClubBand")這樣的屬性即可。
?
如果你的野心遠不止滿足于通過Java代碼創建一個bean的話,其實你還試試如何在CDPlayer中播放CompactDisc,那就需要在CDPlayer中注入CompactDisc的bean了。于是我們可以這樣聲明
@Beanpublic CDPlayer cdPlayer(CompactDisc compactDisc) {return new CDPlayer(compactDisc);}
加上@Bean注解,表明cdPlayer()方法會創建一個bean實例并將其注冊到Spring的應用上下文中,顯然bean的名字默認就是cdPlayer。在執行這個方法的時候,Spring會攔截所有對方法的調用,當然這里會傳入Spring注冊好的CompactDisc的實例bean給cdPlayer方法來確保返回的是創建好的cdPlayer的bean。注意這里的bean是單例的,其實在xml中配置的bean如果沒有特殊聲明,默認也是單例的。也就是說即使這里創建了多個類似cdPlayer的方法,得到的仍然是同一個cdPlayer的bean。
?
這時候運行測試類CDPlayerTest
?
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {@Rulepublic final StandardOutputStreamLog log = new StandardOutputStreamLog();@Autowiredprivate MediaPlayer player;@Testpublic void play() {player.play();assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n",log.getLog());}}
?
?
測試通過。
表明CDPlayer注入成功。前面通過Java代碼注冊的Bean有效。
?
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優質文章。