why
Spring实战中说:
默认情况下,Spring中的bean都是单例的,我们并没有必要创建第二个完全相同(甚至可能不同,由@Bean注解的方法提供)的Bean实例。
......
借助JavaConfig实现注入
看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。
因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。
@Configuration
现在我们知道为什么有以下几个规则的原因了:
- @Configuration注解的类都必须是open类(类可以隐式地子类化,并且不能是最终的);
- @Configuration中的@Bean方法必须可重写;
- 为什么有@Configuration注解,明明@Component一样可以配置Bean;
因为@Bean方法会被代理拦截!
@Configuration
open class A : InitializingBean, ApplicationContextAware {
@Bean open fun getStr(): String { // 该方法实际只会被调用一次
println("@Bean方法被调用")
return "Hello"
}
var ctx: ApplicationContext? = null
override fun afterPropertiesSet() {
var value = ctx!!.getBean(String::class.java)
println("bean = $value")
value = getStr() // getStr被代理!
println("bean = $value")
value = getStr()
println("bean = $value")
}
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.ctx = applicationContext
}
}
输出是:
@Bean方法被调用
bean = Hello
bean = Hello
bean = Hello
@Bean方法确实只被调用了一次,实在是匪夷所思。
测试
package test
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import java.util.*
@RunWith(SpringJUnit4ClassRunner::class)
@ContextConfiguration(classes = [TestConfigBean::class])
@ComponentScan
class TestConfigBean {
@Autowired lateinit var ctx: ApplicationContext
@Test fun `时间戳正确性`() {
val ta = currentTimeStamp()
val tb = currentTimeStamp()
println(ta)
println(tb)
Assert.assertNotEquals(ta, tb)
}
@Test fun `@Bean会被代理`() {
val beanA = ctx.getBean(BeanA::class.java)
println(beanA.productBeanA())
println(beanA.productBeanA())
Assert.assertEquals(beanA.productBeanA(), beanA.productBeanA())
}
}
@Configuration // <------- 此处如果换成@Component或者@Service,测试都会失败!说明只有使用@Configuration注解的类才会被代理
open class BeanA {
@Bean
open fun productBeanA(): TimeStamp {
println("only once!")
return currentTimeStamp()
}
}
data class TimeStamp(val value: Long)
fun currentTimeStamp(): TimeStamp {
Thread.sleep(1)
return TimeStamp(Date().time)
}