各位好,今天我们的内容是有关Spring 5以来有关注册bean的几种方式。前面两三个是比较常用的方式,最后两种是只有在特殊的场合下才会被用到和想到。我们会分别介绍和演示以下五种方式:
1、@Bean方式。
2、@Component方式。
3、@Bean和@Component混用。
4、BeanDefinitionRegistryPostProcessor方式。
5、ApplicationContextInitializer方式。
铺垫
接下来我们详细的展示具体的细节。
首先我们开始新建一个项目。
这里我们通过spring initializr新建一个spring boot 应用程序。
这里我使用diff.wiki这个国内的initializr来新建项目,你也可以通过spring官方的initializr来新建。
像上面一样填写好后,点击“Generate Project”,然后下载。
五种方式
一、@Bean方式
我们打开main类。注释掉下面两行代码。
//@SpringBootApplication
public class Spring5ConfigApplication {
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
}
}
然后我们新建两个service类
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
class BarService{
}
然后再在main 中实例化一个appplication context,一个annotated的context。
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
然后再给application类添加一个configuration注解。
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {...
然后我们添加两个bean配置
@Bean
BarService barService(){
return new BarService();
}
@Bean
FooService fooService(BarService barService){
return new FooService(barService);
}
最后样子是这样:
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {
@Bean
BarService barService(){
return new BarService();
}
@Bean
FooService fooService(BarService barService){
return new FooService(barService);
}
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
class BarService{
}
然后我们去测试类来测试上面的bean注册。
@RunWith(SpringRunner.class)
//@SpringBootTest
@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));
}
}
上面的测试类把springboottest注解去掉。然后添加ContextConfiguration注解指定配置类。
然后注入application context。
然后我们断言applicationcontext中是不是已经存在了barservice实例和fooservice实例 。
然后我们运行测试
ok,测试通过。说明barservice和fooservice已经被实例化并注册进了context。
二、@Component方式
---------------
接下来我们使用另外一种玩法,通过componentscan来注册。
还是来看看现在代码是什么样子
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {
@Bean
BarService barService(){
return new BarService();
}
@Bean
FooService fooService(BarService barService){
return new FooService(barService);
}
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
class BarService{
}
现在我们对上面的代码进行改造。
我们首先给application类添加一个componentscan注解
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {
然后我们给barservice 和 fooservice分别添加上@Component注解
@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
@Component
class BarService{
}
然后我们去掉下面两个bean配置
@Bean
BarService barService(){
return new BarService();
}
@Bean
FooService fooService(BarService barService){
return new FooService(barService);
}
现在代码变成这样了
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
@Component
class BarService{
}
然后我们运行测试
@RunWith(SpringRunner.class)
//@SpringBootTest
@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));
}
}
测试代码还之前一样,没动。来看看测试结果:
发现测试通过。两个service已然被实例化并已注册到了context。
@Bean 和 @Component 混用
---------
好,我们接下来把@bean配置方式和@component混用。这就是你过去把xml配置和基于注解混用是一样的概念。
像下面这样:
@Configuration
@ComponentScan
public class Spring5ConfigApplication {
@Bean //新加bean BarService
BarService barService(){
return new BarService();
}
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
//@Component //注释掉@Component
class BarService{
}
运行测试
自然是通过了。
BeanDefinitionRegistryPostProcessor方式
----------
现在我们再换一种注册方式,就是通过BeanDefinitionRegistryPostProcessor
来实现注册。
//通过实现BeanDefinitionRegistryPostProcessor来注册bean
@Component
public static class MyBRPP implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
beanDefinitionRegistry.registerBeanDefinition("barService",BeanDefinitionBuilder.genericBeanDefinition(BarService.class).getBeanDefinition());
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
然后我们运行测试(测试代码没动)
自然也是通过了。
OK,我们现在再把foo service也通过上面的方式注册到context吧。
现在同样把foo service的component注解去掉。
//@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
然后我们在BeanDefinitionRegistryPostProcessor的实现类中来注册吧。
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, new Supplier<FooService>() {
@Override
public FooService get() {
BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
return new FooService(beanFactory.getBean(BarService.class));
}
}).getBeanDefinition());
然后我们运行测试,也是通过的。
再来展示下整体类的情况:
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {
//通过实现BeanDefinitionRegistryPostProcessor来注册bean
@Component
public static class MyBRPP implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
//bar service
beanDefinitionRegistry.registerBeanDefinition("barService",BeanDefinitionBuilder.genericBeanDefinition(BarService.class).getBeanDefinition());
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, new Supplier<FooService>() {
@Override
public FooService get() {
BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
return new FooService(beanFactory.getBean(BarService.class));
}
}).getBeanDefinition());
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
public static void main(String[] args) {
//SpringApplication.run(Spring5ConfigApplication.class, args);
ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
//@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
//@Component //注释掉@Component
class BarService{
}
值得注意的是:
1、BeanDefinitionRegistryPostProcessor是一种让我们有机会在spring的标准注册之后,还有机会去修改和添加context内的bean注册。因为所有的bean都被加载后,这时候还并被没有实例化,这个时候我们就有机会在真正实例化之前去注册一些新的bean进去。
2、Supplier。我们使用了Supplier这个接口来去实例化一个类。这是一个functional风格的做法。Supplier是在1.8的时候才有的。是lambda表达式的一种需要。可以被用作lambda表达式的一种赋值对象。
3、lambda替换。由于上面的接口中只有一个方法。这时候我们可以把上面的代码轻松替换成lambda风格,如果你喜欢的话。像这样:
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, () -> {
BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
return new FooService(beanFactory.getBean(BarService.class));
}).getBeanDefinition());
4、静态导入优化。上面有一些静态类调用。我们可以改为静态导入,让代码看起来更加的简短(如果你喜欢的话),如下:
//bar service
beanDefinitionRegistry.registerBeanDefinition("barService", genericBeanDefinition(BarService.class).getBeanDefinition());
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService", genericBeanDefinition(FooService.class, () -> {
BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
return new FooService(beanFactory.getBean(BarService.class));
}).getBeanDefinition());
像这样:
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
ApplicationContextInitializer方式
---------------
ok,接下来我们再向你展示一种注册bean的方式。这也是今天的一个重头戏。
你还记得我们在一开始的时候,就把SpringBootApplication注解去掉了。
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {
现在让我们回到SpringBootApplication上来。我们把SpringBootApplication注解再加回来。然后去掉另外两个注解。
@SpringBootApplication
public class Spring5ConfigApplication {
同时我们把main方法中的context方式注释掉,重回springbootapplication。
public static void main(String[] args) {
SpringApplication.run(Spring5ConfigApplication.class, args);
//ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
然后我们新建一个类ApplicationContextInitializr类的实现。
class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{
@Override
public void initialize(GenericApplicationContext genericApplicationContext) {
System.err.println("Hello ,initializr");
}
}
为了尽早的看到效果,这里暂时我们先就写一句(为了一会显眼,我们打印一个error)
System.err.println("Hello ,initializr");
然后我们在resources目录下新建目录META-INF,然后在下面新建spring.factories文件。
文件内容:
org.springframework.context.ApplicationContextInitializer=com.importsource.spring.samples.config.ProgrammaticBeanDefinitionInitializr
值得注意的是,这里的做法叫做“自动配置”,也就是AutoConfiguration。有关自动配置的内容你可以移步这里了解:像Spring Boot那样创建一个你自己的Starter 。
ok,现在我们去运行测试。
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Spring5ConfigApplication.class)
//@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));
}
}
由于我们回到了spring boot 应用做法。 所以我们把之前的context configuration注释掉,重回spring boot test 注释,如上。
然后我们运行测试。
发现我们的application context initializr 已经生效了,我们看到了我们刚才写的打印输出了:Hello , initializr。
接下来,我们就通过application context initializr来进行注册bean。
下面我们把err打印注释掉,开始正式的注册工作。
class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{
@Override
public void initialize(GenericApplicationContext genericApplicationContext) {
//System.err.println("Hello ,initializr");
genericApplicationContext.registerBean(BarService.class);
genericApplicationContext.registerBean(
FooService.class,
()-> new FooService(genericApplicationContext.getBean(BarService.class))
);
}
}
值得注意的是,我们在这之前要把之前的那种BeanDefinitionRegistryPostProcessor方式的@Component注解注释掉,这样我们才能真正的看到
ProgrammaticBeanDefinitionInitializr方式的注册效果。
以上代码中我们使用initialize方法的参数genericApplicationContext来进行注册bean。我们分别注册了BarService以及FooService。其中FooService由于需要传入构造函数参数,所以还得通过registerBean的第二个参数来传入具体的实例化实现。
然后我们运行测试
发现已经测试通过。说明我们通过ProgrammaticBeanDefinitionInitializr的方式成功的将两个bean注册进入了context。
-----------
以下是本次演示的完整代码:
@SpringBootApplication
//@ComponentScan
//@Configuration
public class Spring5ConfigApplication {
//通过实现BeanDefinitionRegistryPostProcessor来注册bean
//@Component
public static class MyBRPP implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
//bar service
beanDefinitionRegistry.registerBeanDefinition("barService", genericBeanDefinition(BarService.class).getBeanDefinition());
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService", genericBeanDefinition(FooService.class, () -> {
BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
return new FooService(beanFactory.getBean(BarService.class));
}).getBeanDefinition());
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
public static void main(String[] args) {
SpringApplication.run(Spring5ConfigApplication.class, args);
//ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}
}
//@Component
class FooService{
private final BarService barService;
public FooService(BarService barService){
this.barService=barService;
}
}
//@Component //注释掉@Component
class BarService{
}
class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{
@Override
public void initialize(GenericApplicationContext genericApplicationContext) {
//System.err.println("Hello ,initializr");
genericApplicationContext.registerBean(BarService.class);
genericApplicationContext.registerBean(
FooService.class,
()-> new FooService(genericApplicationContext.getBean(BarService.class))
);
}
}
以及测试代码:
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Spring5ConfigApplication.class)
//@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));
}
}
总结
本文我们分别演示@Bean方式注册、@Component方式注册以及还有二者混用,然后我们向你演示了如何通过BeanDefinitionRegistryPostProcessor来实现bean的注册,最后又介绍了如何通过自定义一个ApplicationContextInitializer来实现bean的注册。
通过这么多的注册玩法让你对spring的bean注册有一个比较总体的了解。特别是最后一种通过非注解的方式,通过auto configuration的方式来定制ApplicationContextInitializer,然后把bean注册进去,这是一种纯粹的通过编码来注册的方式。这种方式的一个典型的场景就是web应用程序中会对应用程序上下文(也就是ApplicationContext)做一些初始化工作。通过这个切面我们就有机会去修改application context,从而满足我们具体的需求。
--------
点击“阅读原文”可获得本次演示的源代码。
本文分享自 ImportSource 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!