Spring(1) IoC
2023-10-30 15:01:13 # Backend # Spring

Spring IoC / DI 实现步骤

  1. 配置元数据(基于XML、基于注解、基于配置类)
  2. 实例化IoC容器(可从本地文件系统、CLASSPATH等加载配置元数据)

  3. 获取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
<!-- 将工厂类进行ioc配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"/>

<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
  • factory-bean: 指定当前容器中工厂Bean的名称。

  • factory-method: 指定实例工厂方法名。实例方法必须是非static的

img

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
<!-- 场景1: 多参数,可以按照相应构造函数的**顺序**注入数据 -->
<beans>
<bean id="userService" class="x.y.UserService">
<!-- value直接注入基本类型值 -->
<constructor-arg value="18"/>
<constructor-arg value="赵伟风"/>
<constructor-arg ref="userDao"/>
</bean>
<!-- 被引用类bean声明 -->
<bean id="userDao" class="x.y.UserDao"/>
</beans>

<!-- 场景2: 多参数,可以按照相应构造函数的**名称**注入数据 -->
<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>

<!-- 场景3: 多参数,可以按照相应构造函数的**角标**注入数据
index从0开始 构造函数(0,1,2....) -->
<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">
<!-- setter方法,注入movieFinder对象的标识id
name = 属性名 ref = 引用bean的id值 -->
<property name="movieFinder" ref="movieFinder" />
<!-- setter方法,注入基本数据类型movieName
name = 属性名 value = 基本类型值 -->
<property name="movieName" value="消失的她"/>
</bean>

<bean id="movieFinder" class="examples.MovieFinder"/>
  • property:可以给setter方法对应的属性赋值

    • name: set方法标识
    • ref: 引用bean的标识id
    • value: 基本属性值

1.3 IoC容器创建和使用

容器实例化

1
2
3
4
5
6
7
8
//方式1: 实例化并且指定配置文件
//参数:String...locations 传入一个或者多个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

//方式2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作 [springmvc源码和contextLoadListener源码方式]
ApplicationContext context = new ClassPathXmlApplicationContext();
iocContainer1.setConfigLocations("services.xml", "daos.xml");
iocContainer1.refresh();

Bean对象获取

1
2
3
4
5
6
7
8
9
10
11
//方式1: 根据id获取
//没有指定类型,返回为Object,需要类型转化!
HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("bean的id标识");

//方式2: 根据类型获取
//根据类型获取,要求同类型(当前类,或者子类,或者接口的实现类)只能有一个对象交给IoC容器管理
//配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);

//方式3: 根据id和类型获取
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. 复杂对象实例化等

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 实现FactoryBean接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
@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
<!-- FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
<bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean">
<!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
<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");

//注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象
HappyMachine happyMachine = iocContainer.getBean("happyMachine7",HappyMachine.class);
System.out.println("happyMachine = " + happyMachine);

//如果想要获取FactoryBean对象, 直接在id前添加&符号即可! &happyMachine7 这是一种固定的约束
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. 基本扫描配置
1
2
3
4
5
6
<!-- 配置自动扫描的包 -->
<!-- 1.包要精准,提高性能!
2.会扫描指定的包和子包内容
3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等
-->
<context:component-scan base-package="com.atguigu.components"/>
  1. 排除指定组件
1
2
3
4
5
6
7
8
<!-- 指定不扫描的组件 -->
<context:component-scan base-package="com.atguigu.components">

<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<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. 扫描指定组件
1
2
3
4
5
6
7
8
<!-- 仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false">

<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<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) //单例, 默认值
//@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例
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,标记位置:

  • 成员变量
  • 构造函数
  • setter方法

当类型匹配的 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
/**
* 情况1: @Value("str") 直接注入常量
* 情况2: ${key} 取外部配置key对应的值!
* 情况3: ${key:defaultValue} 没有key,可以给与默认值
* 情况4: #{someBean.someValue} 从其他bean取值
*/
@Value("${catalog:hahaha}")
private String name; //hahaha

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注解分为不同的系列,主要有以下几个系列:

  1. JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解:
  • @Deprecated
  • @Override
  • @SuppressWarnings: 抑制编译时产生的警告消息。
  • @SafeVarargs: 标识一个有安全性警告的可变参数方法。
  • @FunctionalInterface: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。
  1. JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。包括:
  • @Resource: 标识一个需要注入的资源
  • @PostConstruct: 标识一个方法作为初始化方法。
  • @PreDestroy: 标识一个方法作为销毁方法。
  • @RolesAllowed: 标识授权角色
  • @PermitAll: 标识一个活动无需进行身份验证。
  • @DenyAll: 标识不提供针对该方法的访问控制。
  • @DeclareRoles: 声明安全角色。
  1. JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:
  • @SupportedAnnotationTypes: 标识注解处理器所处理的注解类型。
  • @SupportedSourceVersion: 标识注解处理器支持的Java源码版本。
  1. JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:
  • @Named: 标识一个被依赖注入的组件的名称。
  • @Inject: 标识一个需要被注入的依赖组件。
  • @Singleton: 标识一个组件的生命周期只有一个唯一的实例。

JSR只规定了注解和注解的含义,不提供实现,由第三方框架(Spring)和库来实现

3. 注解+配置类方式

img

3.1 配置类和注解扫描

创建配置类

1
2
3
4
5
6
7
//标注当前类是配置类,替代application.xml    
@Configuration
//使用注解读取外部配置,替代<context:property-placeholder>标签
@PropertySource("classpath:application.properties")
//使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan>标签
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {}

创建IoC容器

1
2
3
4
5
6
7
8
9
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
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
//如果第三方类进行IoC管理,无法直接使用@Component相关注解
//解决方案: xml方式可以使用<bean标签
//解决方案: 配置类方式,可以使用方法返回值+@Bean注解
@Bean
public DataSource createDataSource(@Value("${jdbc.user}") String username,
@Value("${jdbc.password}")String password,
@Value("${jdbc.url}")String url,
@Value("${jdbc.driver}")String driverClassName){
//使用Java代码实例化
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 {
//name与value都可以指定Bean的id
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};

//autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
//autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
//可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
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() {
// initialization logic
}
public void cleanup() {
// destruction logic
}
}

@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方法之间依赖

  1. 可以通过在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean

  2. 通过方法参数传递 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.classConfigB.class,只需显式提供 ConfigB

1
2
3
4
5
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

// now both beans A and B will be available...
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
<!--junit5测试-->
<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(locations = {"classpath:spring-context.xml"})  //指定配置文件xml
@SpringJUnitConfig(value = {BeanConfig.class}) //指定配置类
public class Junit5IntegrationTest {

@Autowired
private User user;

@Test
public void testJunit5() {
System.out.println(user);
}
}