Spring IoC / DI 实现步骤
- 配置元数据(基于XML、基于注解、基于配置类)
实例化IoC容器(可从本地文件系统、CLASSPATH等加载配置元数据)
获取Bean(组件)
1. 完全XML配置方式
1.1 Bean信息声明配置
基于无参数构造函数的实例化
- 默认需要空构造函数
<bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/>
基于静态工厂方法的实例化
1 2 3 4 5 6 7 8
| public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {}
public static ClientService createInstance() { return clientService; } }
|
1 2 3
| <bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
|
- factory-method: 指定静态工厂方法,该方法必须是static方法
基于实例工厂方法的实例化
1 2 3 4 5 6 7
| public class DefaultServiceLocator { private static ClientServiceImpl clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() { return clientService; } }
|
1 2 3 4 5 6 7
| <bean id="serviceLocator" class="examples.DefaultServiceLocator"/>
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
|
1.2 Bean依赖注入配置
基于构造函数的依赖注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <beans> <bean id="userService" class="x.y.UserService"> <constructor-arg value="18"/> <constructor-arg value="赵伟风"/> <constructor-arg ref="userDao"/> </bean> <bean id="userDao" class="x.y.UserDao"/> </beans>
<beans> <bean id="userService" class="x.y.UserService"> <constructor-arg name="name" value="赵伟风"/> <constructor-arg name="userDao" ref="userDao"/> <constructor-arg name="age" value="18"/> </bean> <bean id="userDao" class="x.y.UserDao"/> </beans>
<beans> <bean id="userService" class="x.y.UserService"> <constructor-arg index="1" value="赵伟风"/> <constructor-arg index="2" ref="userDao"/> <constructor-arg index="0" value="18"/> </bean> <bean id="userDao" class="x.y.UserDao"/> </beans>
|
constructor-arg:可以引用构造参数
- name属性指定参数名
- index属性指定参数角标
- value属性指定普通属性值
- ref引用其他bean的标识
基于Setter方法的依赖注入
1 2 3 4 5 6 7 8 9 10
| <bean id="simpleMovieLister" class="examples.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder" />
<property name="movieName" value="消失的她"/> </bean>
<bean id="movieFinder" class="examples.MovieFinder"/>
|
1.3 IoC容器创建和使用
容器实例化
1 2 3 4 5 6 7 8
|
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
ApplicationContext context = new ClassPathXmlApplicationContext(); iocContainer1.setConfigLocations("services.xml", "daos.xml"); iocContainer1.refresh();
|
Bean对象获取
1 2 3 4 5 6 7 8 9 10 11
|
HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("bean的id标识");
HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
HappyComponent happyComponent = iocContainer.getBean("bean的id标识", HappyComponent.class);
|
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,
只要返回的是true就可以认定为和类型匹配,能够获取到。
1.4 Bean作用域和周期方法配置
组件作用域
作用域可选值
- singleton:单实例,在IOC容器初始化时创建,为默认作用域
- prototype:多实例,在获取bean时创建
如果是在 WebApplicationContext 环境下还会有另外两个作用域(不常用)
- request:请求范围内有效的实例,每次请求创建
- session:会话范围内有效的实例,每次会话创建
作用域配置
1 2 3 4 5 6 7
| <bean id="happyMachine8" scope="prototype" class="com.atguigu.ioc.HappyMachine"> <property name="machineName" value="happyMachine"/> </bean>
<bean id="happyComponent8" scope="singleton" class="com.atguigu.ioc.HappyComponent"> <property name="componentName" value="happyComponent"/> </bean>
|
周期方法
概念:当IoC容器实例化和销毁组件对象的时候进行调用的方法
声明:方法命名随意,但是要求方法必须是 public void 无形参列表
1 2 3 4 5 6 7 8 9 10 11
| public class BeanOne { public void init() { } }
public class BeanTwo { public void cleanup() { } }
|
配置
1 2 3 4
| <beans> <bean id="beanOne" class="examples.BeanOne" init-method="init" /> <bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" /> </beans>
|
1.5 FactoryBean特性和使用
FactoryBean
接口是Spring IoC容器实例化逻辑的可插拔性点。
- 配置复杂的Bean对象时,可以将创建过程存储在
FactoryBean
的getObject方法
FactoryBean
接口提供三种方法:
T getObject()
: 返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!
boolean isSingleton()
: 如果此 FactoryBean 返回单例,则返回 true。此方法的默认实现返回 true
Class<?> getObjectType()
: 返回 getObject()
方法返回的对象类型,如果事先不知道类型,则返回null
使用场景
代理类的创建
第三方框架整合
- 复杂对象实例化等
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@Data public class HappyFactoryBean implements FactoryBean<HappyMachine> {
private String machineName; @Override public HappyMachine getObject() throws Exception { HappyMachine happyMachine = new HappyMachine(); happyMachine.setMachineName(this.machineName); return happyMachine; } @Override public Class<?> getObjectType() { return HappyMachine.class; } }
|
配置文件
1 2 3 4 5 6
|
<bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean"> <property name="machineName" value="iceCreamMachine"/> </bean>
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void testExperiment07() {
ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml");
HappyMachine happyMachine = iocContainer.getBean("happyMachine7",HappyMachine.class); System.out.println("happyMachine = " + happyMachine);
Object bean = iocContainer.getBean("&happyMachine7"); System.out.println("bean = " + bean); }
|
FactoryBean和BeanFactory区别
FactoryBean 是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。
- 一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!
BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。
配置文件导入外部属性文件
1 2
| <context:property-placeholder location="classpath:jdbc.properties" />
|
2. 注解+XML方式
2.1 Bean注解标记和扫描
Spring提供了以下注解, 功能上没有区别
@Component
: 泛化的注解, 表示容器中的一个组件
@Repository
@Service
@Controller
配置文件确定扫描范围
- 基本扫描配置
1 2 3 4 5 6
|
<context:component-scan base-package="com.atguigu.components"/>
|
- 排除指定组件
1 2 3 4 5 6 7 8
| <context:component-scan base-package="com.atguigu.components"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
|
type的一些可选项
使用注解: https://www.baeldung.com/spring-componentscan-filter-type
使用配置文件: https://blog.csdn.net/aa1049372051/article/details/46928977
- 扫描指定组件
1 2 3 4 5 6 7 8
|
<context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
|
组件BeanName问题
- 默认情况为类名首字母小写
- 可以在注解上使用value属性指定(注解中只设置一个属性时,value属性名可以省略)
2.2 Bean作用域和周期方法注解
作用域注解
1 2 3
| @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class BeanOne {}
|
周期方法注解
- 周期方法要求:方法命名随意,但必须是 public void 无形参列表
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class BeanOne { @PostConstruct public void init() { } }
public class BeanTwo { @PreDestroy public void cleanup() { } }
|
2.3 Bean属性赋值
引用类型
使用 @Autowired
,标记位置:
当类型匹配的 bean 不止一个时:
- 没有
@Qualifier
注解:根据 @Autowired
标记位置成员变量的变量名作为 bean 的 id 进行匹配
- 使用
@Qualifier
注解:根据 @Qualifier
注解中指定的名称(value属性)作为 bean 的 id 进行匹配
当 @Autowired
注解设置 required = false 表示如果没有匹配的就不装配
基本类型
@Value
通常用于注入外部属性
首先编写外部属性文件,然后配置文件中引入外部文件
1
| catalog.name=MovieCatalog
|
1 2
| <context:property-placeholder location="application.properties" />
|
使用 @Value
注解读取配置
https://www.baeldung.com/spring-value-annotation
1 2 3 4 5 6 7 8
|
@Value("${catalog:hahaha}") private String name;
|
2.4 @Resource
也可用于依赖注入,与 @Autowired
区别如下:
@Resource(name='test') == @Autowired + @Qualifier(value='test')
- @Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动通过类型装配
- @Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用
- @Resource注解用在属性上、setter方法上。
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,【高于JDK11或低于JDK8需要引入以下依赖】
1 2 3 4 5
| <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>
|
JSR(Java Specification Requests)是Java平台标准化进程中的一种技术规范,而JSR注解是其中一部分重要的内容。按照JSR的分类以及注解语义的不同,可以将JSR注解分为不同的系列,主要有以下几个系列:
- JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解:
@Deprecated
@Override
@SuppressWarnings
: 抑制编译时产生的警告消息。
@SafeVarargs
: 标识一个有安全性警告的可变参数方法。
@FunctionalInterface
: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。
- JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。包括:
@Resource
: 标识一个需要注入的资源
@PostConstruct
: 标识一个方法作为初始化方法。
@PreDestroy
: 标识一个方法作为销毁方法。
@RolesAllowed
: 标识授权角色
@PermitAll
: 标识一个活动无需进行身份验证。
@DenyAll
: 标识不提供针对该方法的访问控制。
@DeclareRoles
: 声明安全角色。
- JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:
@SupportedAnnotationTypes
: 标识注解处理器所处理的注解类型。
@SupportedSourceVersion
: 标识注解处理器支持的Java源码版本。
- JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:
@Named
: 标识一个被依赖注入的组件的名称。
@Inject
: 标识一个需要被注入的依赖组件。
@Singleton
: 标识一个组件的生命周期只有一个唯一的实例。
JSR只规定了注解和注解的含义,不提供实现,由第三方框架(Spring)和库来实现
3. 注解+配置类方式
3.1 配置类和注解扫描
创建配置类
1 2 3 4 5 6 7
| @Configuration
@PropertySource("classpath:application.properties")
@ComponentScan(basePackages = {"com.atguigu.components"}) public class MyConfiguration {}
|
创建IoC容器
1 2 3 4 5 6 7 8 9
| ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);
ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext();
iocContainerAnnotation.register(MyConfiguration.class);
iocContainerAnnotation.refresh();
|
3.2 @Bean
@Bean定义组件
将第三方jar包中的类,添加到IoC容器中,例如将Druid连接池对象存储到IoC容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Bean public DataSource createDataSource(@Value("${jdbc.user}") String username, @Value("${jdbc.password}")String password, @Value("${jdbc.url}")String url, @Value("${jdbc.driver}")String driverClassName){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driverClassName); return dataSource; }
|
@Bean生成BeanName问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public @interface Bean { @AliasFor("name") String[] value() default {}; @AliasFor("value") String[] name() default {}; boolean autowireCandidate() default true;
String initMethod() default ""; String destroyMethod() default "(inferred)"; }
|
@Bean("name") == @Bean(value="name") == @Bean(name="name")
缺省情况下 Bean 名称与方法名称相同
@Bean指定初始化和销毁方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class BeanOne { public void init() { } public void cleanup() { } }
@Configuration public class AppConfig { @Bean(initMethod = "init", destroyMethod = "cleanup") public BeanOne beanOne() { return new BeanOne(); } }
|
@Bean Scope作用域
同2.2
1 2 3 4 5
| @Bean @Scope("prototype") public Encryptor encryptor() { }
|
@Bean方法之间依赖
可以通过在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean
通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系
- 如果该类型只有一个Bean,直接指定类型即可
- 如果有多个bean,参数名即为要指定的bean名称
3.3 @Import扩展
@Import
注解允许从另一个配置类加载 @Bean
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration public class ConfigA { @Bean public A a() { return new A(); } }
@Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
|
同时,在实例化上下文时不需要同时指定 ConfigA.class
和 ConfigB.class
,只需显式提供 ConfigB
1 2 3 4 5
| ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
A a = ctx.getBean(A.class); B b = ctx.getBean(B.class);
|
整合 Spring 与 JUnit
- 不需要创建IOC容器对象
- 任何需要的bean都可以在测试类中直接享受自动装配
导入依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.6</version> <scope>test</scope> </dependency>
|
测试注解使用
1 2 3 4 5 6 7 8 9 10 11 12
| @SpringJUnitConfig(value = {BeanConfig.class}) public class Junit5IntegrationTest { @Autowired private User user; @Test public void testJunit5() { System.out.println(user); } }
|