Spring 学习笔记①:IoC容器、Bean与注入


收藏这三篇笔记,完整回顾Spring常见问题及使用方式速查:

  1. Spring 学习笔记①:IoC容器、Bean与注入(即本篇)
  2. Spring 学习笔记②:动态代理及面向切面编程
  3. Spring 学习笔记③:JDBC与事务管理

0. 基本概念

  • 控制反转(IoC):被调用者的实例不再由调用者创建,而是由 Spring 容器创建,这称为控制反转。
  • 依赖注入(DI):Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入。

文档、JAR包:官方仓库地址


1. 示例pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>8</source>
            <target>8</target>
          </configuration>
        </plugin>
      </plugins>
    </build>

    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

2. IOC容器

二者相比,前者功能简单且没有初始化自检。

2.1 BeanFactory(不常用)

Resource resource = new ClassPathResource("ApplicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
beanFactory.getBean("$BeanName");

2.2 ApplicationContext(示例)

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="service0" class="Service">
        <constructor-arg name="name" type="java.lang.String" value="service0:name"/>
    </bean>
        <!-- 构造标签详见下 -->
    <bean id="dao0" class="DAO">
        <constructor-arg name="id" type="java.lang.Integer" value="7"/>
        <constructor-arg name="name" type="java.lang.String" value="dao0"/>
        <constructor-arg name="service" type="Service" ref="service0"/> <!-- 实例依赖-->
    </bean>
</beans>

Service.java

@Data  // 使用了 lombok
@AllArgsConstructor
public class Service {
    private String name;
}

DAO.java

@Data
@AllArgsConstructor
public class DAO {
    private Integer id;
    private String name;
    private Service service; // 依赖Service,采用`ref`
}

测试代码:

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        DAO a = (DAO)context.getBean("dao0");
        System.out.println(a);
    }
}

目录结构为:

2.3 通过注解进行装配

通过注解进行装配,编写方便、无需写xml标签文件,但若有更改需要重编译。

2.3.1 常用注解

  1. @Component :可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可;将会将其实例化和注入依赖并加入IoC容器。
  2. @Repository :用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  3. @Service :通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  4. @Controller :通常作用在控制层(如 Struts2 的 Action),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  5. @Autowired :用于对 Bean 的属性变量、属性的 Set 方法及构造函数进行标注,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
  6. @Resource :其作用与 Autowired 一样。其区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。
  7. @Qualifier :与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

附 · 使用JacaConfig类配置Bean所用到的注解:

  • @Configuation 等价于 <beans></beans> ,不可用于标注 final 、匿名类,必须为静态类。
  • @Bean 等价于 <bean></bean>
  • @ComponentScan 等价于 <context:component-scan base-package="com.dxz.demo"/>

2.3.2 示例:使用注解装配的MVC

目录结构:

TIM截图20200609151507.png

此处可以选择构建一个配置类以取代 ApplicationContext.xml

@Configuration // 该注解同样也需要被扫描到,因此同样要在xml文件中进行配置
// @ComponentScan("MVC") // 等价于<context:component-scan base-package="MVC"/> //由于目录结构中该配置类与MVC同包,会被xml配置直接扫描到,因此此处不需要。
public class SpringConfiguration {
    // 等价于
    // <bean id="user0" class="xxx.User">
    //  <constructor-arg name="id" type="java.lang.Integer" value="0"></constructor-arg>
    //     <constructor-arg name="name" type="java.lang.String" value="Jack"></constructor-arg>
    // </bean>
    @Bean(name = "user0")
    public User UserBeanJack(){
        return new User(0, "Jack");
    }
}

构建一个控制器:

@Controller
public class UserController {
    @Resource // 此处使用Resource,默认采用首字母小写的形式去找到类, 等价于 getBean("userService")
    private UserService userService;

    public void acceptRequest(ApplicationContext context, Object request){
        System.out.println("Accept request.");
        User user = (User)context.getBean("user0"); // 假设根据Request构建Model
        System.out.println("Controller find user: " + user);
        this.userService.service(user); // 找到Model对应的业务
        System.out.println("MVC Controller Over.");
    }
}

构建Model层的各个组件:,首先是业务逻辑:

@Service("userService") // 默认是类名首字母小写, 等价于 <bean id="userService" class="xxx.UserService"/>
public class UserService {
    private UserDao userDao;

    @Autowired // 此处使用自动装配的形式
    public UserService(UserDao userDao){
        this.userDao = userDao;
    }

    public void service(User user){
        System.out.println("MVC Service sth. with " + user);
        this.userDao.save(user);
        System.out.println("MVC Service Over.");
    }
}

数据持久层:

@Repository("userDao") // 等价于 <bean id="userDao" class="xxx.UserDao"/>
public class UserDao {
    public void save(User user){
        System.out.println("数据库已保存" + user);
    }
}

一个POJO类:

@Data // 使用了lombok简化代码编写
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
}

最后,还需要让Spring扫描到这些注解:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 增加第3, 8, 9行-->
    <context:component-scan base-package="MVC"/>
</beans>

测试:

public class Main {

    public static void main(String[] args){
        final ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserController userController = (UserController)context.getBean("userController");
        // 注意不能用 UserController userController = new UserController();
        userController.acceptRequest(context, null);
    }
}
/*打印结果如下:
Accept request.
Controller find user: User(id=0, name=Jack)
MVC Service sth. with User(id=0, name=Jack)
数据库已保存User(id=0, name=Jack)
MVC Service Over.
MVC Controller Over.*/

  • 需要注意的是,所有的实例都要从IoC容器里获取,自行new出来的实例不会被Spring注入,会导致Null异常。

2.3.3 示例所使用的流程图

image.png

2.4 往容器内配置Bean的三种方式

  1. 使用xml进行配置,bean的名字为 id 字段。
  2. 使用JavaConfig进行配置( @Configuration ),默认bean的名字为 @Bean 所标注的方法名。
  3. 使用注解进行配置,默认Bean的名字为类名(首字母小写)。

2.5 @Autowired 和 @Resource 的区别

  • 前者默认按类型装配,当容器内存在多种同一类型的Bean时,需要使用 @Qualifier(value = "$BeanName") 才可以找到被注入的对象。
  • 后者在不指定 name 字段时,也是以同一类型的Bean进行注入,若存在多个同类型对象,会报错;当指定 name 字段时等同于 getBean("$BeanName") (某些框架中,找不到会再根据类型寻找)。
  • 二者都可以标注在 setter() 或构造器方法上; @Autowired 不被推荐在字段上直接标注(缺点见下文)。

2.5.1 注入方式及字段注入的缺点

对于二者,注入方式都有三种:构造器注入、setter注入、field注入,区别如下:

public class UserController {
    @Autowired // field注入,也称为反射注入或字段注入,对访问级别无要求
    private UserService userService;
    
    @Autowired // 构造器注入(需要为public) 适用于强制依赖项
    public UserController(UserService userService){
        this.userService = userService;
    }
    
    @Autowired // setter注入(需要为public) 适用于可选依赖项
    public void setUserService(UserService userService){
        this.userService = userService;
    }
}

使用字段注入,其最大缺点为:

  1. 类会和Spring强耦合,由于无构造器或setter方法,无法在容器外使用。
  2. 无法使用属性注入的方式构建不可变对象。
  3. 同第一小点,在测试时无法脱离容器进行测试,即 new 出来的 Mock 对象无法被引入测试单元。
  4. 强依赖和可选依赖都被隐藏,无法区分,违背单一职责原则。

3. Bean的常见概念

3.1 Bean的常用属性

属性名称 描述
id 是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成
name Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开
class 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名
scope 用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其【默认值】是 singleton。
property 元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名
constructor-arg 元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
constructor-arg:name/index 标定一个bean的属性名称/构造函数的第index个参数,用于初始化实例
ref/value 和 等元素的子元素,用于直接指定一个引用/常量值;亦可作为初始化时注入集合类元素的标签
list 用于封装 List 或数组类型的依赖注入
set 用于封装 Set 或数组类型的依赖注入
map 用于封装 Map 或数组类型的依赖注入
entry 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值
factory-bean 当class是一个工厂类时,指定其作为工厂
factory-method 当class是一个工厂类时,使用该标签标注的静态方法(或若已指定一个factory-bean标注的实例)得到实例
autowire 是否自动装配,详见下文表格

3.1.1 自动装配

属性取值 描述
byName 根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType 根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor 根据构造方法的参数的数据类型,进行 byType 模式的自动装配。
autodetect 如果发现默认的构造方法,则用 constructor 模式,否则用 byType 模式。
no 默认】情况下,不使用自动装配,Bean 依赖必须通过 ref 元素定义。

3.2 Bean的作用域

Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。

  1. singleton :单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 【默认】的作用域。
  2. prototype :原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
  3. request :在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
  4. session :在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
  5. global Session :在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

3.3 Bean的生命周期

5-1ZF1100325116.png

  1. 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean,包含以下四个预处理项目:
  • ResouceLoader 加载配置信息;
  • BeanDefintionReader 解析配置信息,生成若干个 BeanDefintion
  • BeanDefintionBeanDefintionRegistry管理起来;
  • BeanFactoryPostProcessor 对配置信息进行加工(也就是处理配置的信息,一般通过PropertyPlaceholderConfigurer来实现);
  1. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  2. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  3. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  4. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  5. 如果 BeanPostProcessor (BBP) 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  6. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  7. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  8. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization() 。此时,Bean 已经可以被应用系统使用了。
  9. 如果在 <bean> 中指定了该 Bean 的作用范围为 scope="singleton" ,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用范围为 scope="prototype" ,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  10. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章