Spring IoC三种配置方式

Spring IoC介绍

IoC(Inversion of Control) 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

Spring IoC实现步骤

  1. 配置 配置元数据,编写交给SpringIoC容器管理组件的信息,配置方式有三种。分别是XML方式,注解方式,配置类方式。
  2. 实例化IoC容器 选择一个合适的容器实现类,进行IoC容器的实例化工作。
1
2
//实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
AnnotationConfigApplicationContext 通过读取Java配置类创建 IOC 容器对象
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。
  1. 获取Bean
1
2
//获取ioc容器的组件对象
PetStoreService service = context.getBean("petStore", PetStoreService.class);

XML方式

1.配置元数据

  • 无参构造
1
2
3
4
5
6
7
8
9
package com.atguigu.ioc;


public class HappyComponent {
    
    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }
}
1
<bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/>
  • 静态工厂
1
2
3
4
5
6
7
8
9
public class ClientService {
  private static ClientService clientService = new ClientService();
  private ClientService() {}

  public static ClientService createInstance() {
  
    return clientService;
  }
}
1
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
  • 实例工厂
1
2
3
4
5
6
7
public class DefaultServiceLocator {
  private static ClientServiceImplclientService = new ClientServiceImpl();
    
  public ClientService createClientServiceInstance() {
    return clientService;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- 将工厂类进行ioc配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</bean>

<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<!-- factory-bean属性:指定当前容器中工厂Bean 的名称。 -->
<!-- factory-method:  指定实例工厂方法名。-->
<bean id="clientService"
  factory-bean="serviceLocator"
  factory-method="createClientServiceInstance"/>
  • 有参构造(单参数)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class UserDao {
}
// #----------------------# //
public class UserService {
    
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}
1
2
3
4
5
6
7
8
9
<beans>
  <!-- 引用类bean声明 -->
  <bean id="userService" class="x.y.UserService">
   <!-- 构造函数引用 -->
    <constructor-arg ref="userDao"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>
  • 有参构造(多参数)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class UserDao {
}

public class UserService {
    private UserDao userDao;
    private int age;
    private String name;

    public UserService(int age , String name ,UserDao userDao) {
        this.userDao = userDao;
        this.age = age;
        this.name = name;
    }
}
 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
33
34
35
36
<!-- 场景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">
    <!-- value直接注入基本类型值 -->
    <constructor-arg name="name" value="赵伟风"/>
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="age"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>

<!-- 场景2: 多参数,可以按照相应构造函数的角标注入数据 
           index从0开始 构造函数(0,1,2....)-->
<beans>
    <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg index="1" value="赵伟风"/>
    <constructor-arg index="2" ref="userDao"/>
    <constructor-arg index="0"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>
  • Set方法依赖注入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public Class MovieFinder{

}

public class SimpleMovieLister {
  private MovieFinder movieFinder;
  private String movieName;

  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }
  public void setMovieName(String movieName){
    this.movieName = movieName;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
  <!-- setter方法,注入movieFinder对象的标识id
	   property标签: 可以给setter方法对应的属性赋值
       name = 属性名  ref = 引用bean的id值-->
  <property name="movieFinder" ref="movieFinder" />
  <!-- setter方法,注入基本数据类型movieName
       name = 属性名 value= 基本类型值-->
  <property name="movieName" value="消失的她"/>
</bean>
<bean id="movieFinder" class="examples.MovieFinder"/>

2.创建IoC容器与读取Bean

  1. 创建IoC容器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//方式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");
//后配置的文件,需要调用refresh方法,触发刷新配置
iocContainer1.refresh();           
  1. 读取Bean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//方式1: 根据id获取
//没有指定类型,返回为Object,需要类型转化!
HappyComponent happyComponent = 
        (HappyComponent) iocContainer.getBean("bean的id标识");
        
//使用组件对象        
happyComponent.doWork();

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

//方式3: 根据id和类型获取
HappyComponent happyComponent = iocContainer.getBean("bean的id标识", HappyComponent.class);
happyComponent.doWork();

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,
只要返回的是true就可以认定为和类型匹配,能够获取到。

3.组件周期方法与作用域

周期方法

在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class BeanOne {
  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  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>

作用域

<bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!

在IoC容器中,这些<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)!

这意味着,BeanDefinition概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。

具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!

取值 含义 创建对象的时机 默认值
singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时
prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!--bean的作用域 
    准备两个引用关系的组件类即可!!-->
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<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>

注解方式

  1. 添加注解。

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * projectName: com.atguigu.components
 * description: controller类型组件
 */
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例  二选一
@Controller(value = "指定值BeanName")
public class XxxController {
   //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  @PostConstruct  //注解制指定初始化方法
  public void init() {
    // 初始化逻辑
  }
}

public class BeanTwo {
  
  @PreDestroy //注解指定销毁方法
  public void cleanup() {
    // 释放资源逻辑
  }
}
  1. 配置文件确定扫描范围
 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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="com.atguigu.components"/>
</beans>


<!--############################分割线####################################### -->
<!-- 情况二:指定不扫描的组件 -->
<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>


<!--############################分割线####################################### -->
<!-- 情况三:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- 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>
  1. 依赖注入
  • 引用类型
1
2
3
4
5
6
7
8
@Controller(value = "tianDog")
public class SoldierController {
    @Autowired
    @Qualifier(value = "maomiService222")
    //@Resource(name='test') == @Autowired + @Qualifier(value='test') JSR-250注解
    // 根据面向接口编程思想,使用接口类型引入Service组件
    private ISoldierService soldierService;
}
  • 基本类型
1
2
# 声明外部配置文件application.properties
catalog.name=MovieCatalog
1
<context:property-placeholder location="application.properties" />
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Component
public class CommonComponent {
    /**
     * 情况1: ${key} 取外部配置key对应的值!
     * 情况2: ${key:defaultValue} 没有key,可以给与默认值
     */
    @Value("${catalog:deFault}")
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
  1. 运行测试
1
2
3
4
5
6
7
8
public class ControllerTest {
    @Test
    public  void testRun(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");
        StudentController studentController = applicationContext.getBean(StudentController.class);
        studentController.findAll();
    }
}

配置类方式

img

  1. 配置类
 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
//标注当前类是配置类,替代application.xml    
@Configuration
//引入jdbc.properties文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {

    //如果第三方类进行IoC管理,无法直接使用@Component相关注解
    //解决方案: xml方式可以使用<bean标签
    //解决方案: 配置类方式,可以使用方法返回值+@Bean注解
    @Bean(name = "myThing" destroyMethod = "cleanup") 
    @Scope("prototype")
    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;
    }
}

@Configuration指定一个类为配置类,可以添加配置注解,替代配置xml文件

@ComponentScan(basePackages = {“包”,“包”}) 替代<context:component-scan标签实现注解扫描

@PropertySource(“classpath:配置文件地址”) 替代 <context:property-placeholder标签

  1. IoC容器声明
1
2
3
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext iocContainerAnnotation = 
new AnnotationConfigApplicationContext(MyConfiguration.class);

总结

XML方式配置总结

  1. 所有内容写到xml格式配置文件中
  2. 声明bean通过<bean标签
  3. <bean标签包含基本信息(id,class)和属性信息 <property name value / ref
  4. 引入外部的properties文件可以通过<context:property-placeholder
  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

XML+注解方式配置总结

  1. 注解负责标记IoC的类和进行属性装配
  2. xml文件依然需要,需要通过<context:component-scan标签指定注解范围
  3. 标记IoC注解:@Component,@Service,@Controller,@Repository
  4. 标记DI注解:@Autowired @Qualifier @Resource @Value
  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

完全注解方式配置总结

  1. 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现
  2. xml文件替换成使用@Configuration注解标记的类
  3. 标记IoC注解:@Component,@Service,@Controller,@Repository
  4. 标记DI注解:@Autowired @Qualifier @Resource @Value
  5. <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {“com.atguigu.components”})替代
  6. <context:property-placeholder引入外部配置文件使用@PropertySource({“classpath:application.properties”,“classpath:jdbc.properties”})替代
  7. <bean 标签使用@Bean注解和方法实现
  8. IoC具体容器实现选择AnnotationConfigApplicationContext对象
updatedupdated2023-10-272023-10-27