我的Java Web之路 - Spring IoC(4)- 使用Java配置生产和装配Bean

本文深入探讨Spring IoC容器的三种配置方式:基于XML、基于Java注解及基于Java配置。重点讲解了Java配置方式的工作原理,包括开发业务组件、配置元数据和实例化容器的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

前面的文章介绍了Spring IoC容器生产和装配Bean的两种方式:

本篇文章介绍最后一种方式,使用Java配置,其基本流程与基于XML的方式类似,也是Spring IoC工作模式的三部曲:

  • 准备食材:开发业务组件;
  • 编写菜品制作清单:Spring IoC的配置元数据;
  • 聘请厨师兼菜品管理员:实例化Spring IoC容器。

仍然以spring-ioc-test工程为基础进行修改。

工程结构

与之前的工程结构基本一样:
工程结构.png
仅仅多了一个类AppConfig,显然这就是提供配置元数据的地方,不过与基于XML不同的是,它采用的是Java代码的方式。

当然,spring-ioc-test.xml这个文件我并没有删除,实际上没有用到它,是可以删除的。

准备食材 - 开发业务组件

实际上,我们的业务组件与采用基于XML的方式时完全一样:

  • 服务层的ServiceA、ServiceB
  • 数据访问层的RepositoryA、RepositoryB

其代码参考基于XML的文章

菜品制作清单 - 配置元数据

这里是与基于XML的方式最大的不同,直接抛弃了原来的XML文档,重新建立一个Java类来实现Spring IoC配置元数据的提供。当然,类名可以随便取,只要符合要求就行。

AppConfig.java:

package test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import test.repository.RepositoryA;
import test.repository.RepositoryB;
import test.service.ServiceA;
import test.service.ServiceB;

@Configuration
public class AppConfig {

	@Bean
	public RepositoryA repositoryA() {
		return new RepositoryA();
	}
	
	@Bean
	public RepositoryB repositoryB() {
		return new RepositoryB();
	}
	
	@Bean
	public ServiceB serivceB() {
		ServiceB svcB = new ServiceB();
		svcB.setRepositoryB(repositoryB());
		return svcB;
	}
	
	@Bean
	public ServiceA serviceA() {
		return new ServiceA(serivceB(), repositoryA());
	}
}

乍一看,其实与普通的Java类没有什么大的不同,唯一不同的是在类定义上使用@Configuration注解修饰,在方法上使用@Bean注解修饰。不过,这可与基于注解的方式是不一样的,两者引入的Spring版本也是不同的,参考官方文档

实际上,这两个注解的作用非常类似于基于XML的<beans>标记和<bean>标记。

  • @Configuration注解:类似于<beans>标记,表示这是提供配置元数据的地方。当然,此注解修饰的类还可以被基于注解的自动扫描生成Bean(正如@Service等注解一样),并将其作为提供配置元数据的地方。但前提是要开启了自动扫描。
  • @Bean注解:类似于<bean>标记,表示这是一个Bean定义,告诉Spring IoC要生成该Bean,而生成该Bean就是要执行<bean>标记所修饰的方法,所生成的Bean的id默认就是方法名,所以看到这里的方法名跟一般Java规范不太一样,它们都是名词,而不是动词。

那么剩下的Bean装配(即依赖注入)如何配置呢?基于XML的方式提供了<constructor-arg>标记和<property>标记,上面没有见到有对应的注解啊?

我们仔细看一下每一个方法里面的代码,就会发现该Bean所依赖的Bean,已经作为参数传进构造方法或setter方法的调用中了。

所以,基于Java配置的依赖注入,其实就是调用@Bean注解所修饰的方法

实际上,Spring IoC会使用AOP技术(面向切面编程)拦截@Bean注解所修饰的方法调用(除第一次调用外),而转换为从容器里面寻找对应的Bean(与Bean的Scope也有关系)。也就是说,假设还有ServiceC也依赖于RepositoryA

	@Bean
	public ServiceC serviceC() {
		return new ServiceC(repositoryA());
	}

看似serviceA()serviceC()这两个方法中都调用了repositoryA(),即repositoryA()被调用了两次,会new了两个RepositoryA的对象,然而不会,Spring IoC容器会拦截第二次调用,返回最初生成的那个Bean。

聘请厨师兼菜品管理员 - Spring IoC容器

既然菜品制作清单换了一种形式,那么必然要聘请能解读该形式的厨师,SpringMain.java改成如下:

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import test.config.AppConfig;
import test.service.ServiceA;

public class SpringMain {

	public static void main(String[] args) {
		//ApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc-test.xml");
		ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		ServiceA svcA = context.getBean("svcA", ServiceA.class);
		svcA.doWork();
	}
}

改动不是太大,仅仅使用AnnotationConfigApplicationContext替换了原来的ClassPathXmlApplicationContext,参数由XML文档路径变成了提供Java配置的类。

验证

好,一切准备就绪,咱运行验证一下,结果出现异常:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'svcA' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:769)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1221)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:294)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1111)
	at test.SpringMain.main(SpringMain.java:15)

显然是找不到名字为svcA的Bean,还记得上面我们说过基于Java配置生成的Bean,它的名字默认是方法名吗?所以我们只要把getBean()方法传入的参数改为方法名"serviceA"即可。

总结

到此为止,我们已经学习完Spring IoC的三种提供配置元数据的方式,那么什么时候该用哪种方式呢?答案就是视情况而定。

总的来说,基于XML的方式与源码完全解耦,修改后并不需要重新编译源码,它可以用在自己的和第三方的组件中,因此是非侵入式的。

而基于注解的方式是最简洁的,直接在组件上加上@Service等注解,在属性和方法上加@Autowired注解即可。但不能用在第三方组件中,因为你无法直接在第三方组件上添加注解,就是说此种方式是侵入式的。而且,修改后要重新编译源码,配置将分散在各个组件上,依赖关系得不到集中管理导致混乱。

基于Java配置的方式也是非侵入式的,不过也是修改后要重新编译源码。

不过,Spring支持三种方式的混合使用,后续再讨论。

还有一个问题,就是我们的例子都是独立(standalone)应用,而不是web应用。那么如何在web应用中使用Spring IoC容器呢?后续再讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值