上篇说的是无需半行 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 {
- @Rule
- public final StandardOutputStreamLog log = new StandardOutputStreamLog();
- @Autowired
- private MediaPlayer player;
- @Autowired
- private CompactDisc cd;
- @Test
- public void cdShouldNotBeNull() {
- assertNotNull(cd);
- }
- @Test
- public 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 more
- 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)}
- 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 more
- soundsystem.CDPlayerTest > play FAILED
- org.springframework.beans.factory.BeanCreationException
- Caused by: org.springframework.beans.factory.BeanCreationException
- Caused 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 对象。
- @Bean
- public 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 了。于是我们可以这样声明
- @Bean
- public 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 {
- @Rule
- public final StandardOutputStreamLog log = new StandardOutputStreamLog();
- @Autowired
- private MediaPlayer player;
- @Test
- public void play() {
- player.play();
- assertEquals(
- "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n",
- log.getLog());
- }
- }
测试通过。
表明 CDPlayer 注入成功。前面通过 Java 代码注册的 Bean 有效。
如果您觉得阅读本文对您有帮助,请点一下 "推荐" 按钮,您的 "推荐" 将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注 JackieZheng 的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
来源: