Spring学习笔记(一) - Bean与依赖注入

Spring学习笔记(一) - Bean与依赖注入

知识分子没文化
2021-11-05 / 0 评论 / 979 阅读 / 4,733 字数 / 正在检测是否收录...

目录:

一、Spring简介

Spring 是一个开源的 Java 平台框架,广泛用于构建企业级应用程序。它由 Rod Johnson 在 2003 年首次发布,旨在简化 Java 开发,提供全面的基础设施支持。Spring 的核心特性包括依赖注入(DI)和面向切面编程(AOP),是开发Java EE应用程序的必备。

pom.xml 文件中导入 Spring 的坐标依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

二、Bean配置

Bean 是 Spring 框架中最核心的概念之一,它是构成应用程序主干并由 Spring IoC 容器管理的对象。

Spring Bean 本质上是由 Spring IoC 容器实例化、组装和管理的对象,可以是任何 Java 类的实例,如一个简单的 POJO(Plain Old Java Object),比如一个表示用户信息的 User 类,在 Spring 容器中可以被定义为一个 Bean,然后 Spring 可以对 User 类的实例进行各种诸如依赖注入等的管理操作。除此之外,Bean 也可以是一个复杂的企业级组件。

在 Java 中,对象通常通过 new 关键字来创建,但在 Spring 中,对象的创建和依赖注入由 Spring IoC 容器负责。这种机制允许开发者将对象的生命周期管理和依赖关系交给 Spring 容器,从而简化了开发过程并提高了代码的可维护性和灵活性。

2.1、基础配置

<!-- bean标签用来配置bean
     id属性是bean的名字,是唯一的,不能重复
     class属性是bean的类型,值是全路径类名 -->
<bean id="userDao" class="com.wlplove.dao.impl.UserDaoImpl"/>

<bean id="userService" class="com.wlplove.service.impl.UserServiceImpl">
    <!--  property标签用来配置当前bean中的的属性,需要其有set方法
          name属性指定配置的哪一个属性,是属性的名称
          ref属性表示参照哪一个bean,是当前容器中存在的bean  -->
    <property name="userDao" ref="userDao"/>
</bean>

在 XML 文件中经过上面的配置之后,就将这个类交给了 Spring IoC 容器进行管理,之后需要使用该类时就可以从容器中获取到实例对象了。

2.2、别名配置

name 属性表示 Bean 的别名,可以设置多个,使用逗号、分号、空格分开。

别名配置完成后,就可以通过别名获取 Bean 或者在其他 Bean 的配置中通过别名引用这个 Bean。

<bean id="bookService" name="service service2 bookEbi" class="com.wlplove.service.impl.BookServiceImpl"/>

2.3、作用域

Spring Bean 的 Scope 指的是 Bean 的作用域,它定义了 Bean 实例在 Spring 容器中的生命周期和可见性。一般常用的是 singleton(单例)、prototype(原型/非单例):

  • singleton:默认作用域,Spring 容器只会创建一个 Bean 实例,并在整个应用程序中共享。这个实例从容器启动时被初始化,直到容器销毁才会被释放。这种模式适用于无状态的服务对象,如 DAO 组件、控制器等。
  • prototype:每次请求 Bean 时都会创建一个新的实例。这些实例由 Spring 容器创建,但容器不会跟踪它们的管理和销毁,这些任务交由 Java 的垃圾回收机制来处理。适用于需要频繁创建且生命周期较短的 Bean,如用户会话数据对象。

作用域可以通过 XML 配置:

<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl" scope="singleton"/>

也可以使用注解 @Scope 定义作用域:

public class AppConfig {
    // @Scope("prototype")
    @Scope("singleton")
    public MyBean myBean() {
        return new MyBean();
    }
}

除此之外,还有 requestsessionapplication 等作用域。

三、Bean的生命周期

Spring Bean 的生命周期包括实例化属性赋值初始化使用销毁五个阶段。

3.1、实例化

实例化指的是由 Spring 容器创建 Bean 实例的过程。

在这个阶段,Spring 容器通过反射机制来创建 Bean 的实例。

3.2、属性赋值

Spring 容器根据 Bean 信息,为 Bean 的属性进行赋值操作。

这一阶段通常涉及到依赖注入(DI),Spring 容器会自动填充 Bean 的属性并将其他 Bean 的引用注入到当前 Bean 的属性中。

3.3、初始化

初始化阶段是 Spring Bean 生命周期中的一个重要环节,它标志着 Bean 已经准备好被使用。

在这个阶段,Spring 容器会执行一系列初始化操作,其中包括开发者自定义的初始化方法,开发者可以通过以下几种方式自定义初始化操作:

  • XML 配置时在 <bean> 标签中定义属性 init-method
  • 使用 @PostConstruct 注解定义初始化方法
  • 实现接口 InitializingBeanafterPropertiesSet() 方法

3.4、使用

这一阶段,Bean 已经被完全初始化,并且可以被 Spring 容器提供给其他组件或服务进行调用。Bean 的使用通常是通过依赖注入(DI)的方式完成的,即在需要使用 Bean 的地方直接注入即可。

3.5、销毁

销毁阶段是 Spring Bean 生命周期的最后一个阶段,它发生在 Bean 不再被需要时。

在这个阶段,Spring 容器会执行一系列的清理操作,包括开发者自定义的 Bean 的销毁方法,开发者可以通过以下几种方式:

  • XML 配置时在 <bean> 标签中定义 destroy-method 属性
  • 使用 @PreDestroy 注解定义销毁方法
  • 实现接口 DisposableBeandestroy() 方法

这些操作旨在释放 Bean 所占用的资源,确保系统的稳定性和性能。

3.6、示例:通过XML配置自定义初始化和销毁操作

Java 代码:

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("save ...");
    }

    publid void init() {
        System.out.println("init ....");
    }

    public void destory() {
        System.out.println("destory ...");
    }
}

XML 配置:

<!-- “init-method”方法指定Bean的初始化方法,“destory-method”方法指定Bean的销毁方法 -->
<bean id="userService" class="com.wlplove.service.impl.UserServiceImpl" init-method="init" destroy-method="destory" />

四、实例化Bean的方式

4.1、无参构造方法

Java 类:

public class UserServiceImpl implements UserService {
    private UserServiceImpl() {
        System.out.println("constructor is running ....");
    }

    public void save() {
        System.out.println("save ...");
    }
}

Spring 配置:

<bean id="userService" class="com.wlplove.service.impl.UserServiceImpl"/>

在接口继承类中必须要提供无参构造方法,如果不存在,将抛出 BeanCreationException 异常

4.2、静态工厂

Java 类:

public class UserServiceImpl implements UserService {
    // ...
}

// 工厂类
public class UserServiceFactory {
    // 静态工厂方法
    public static UserService getUserService() {
        return new UserServiceImpl();
    }
}

Spring 配置:

<!-- class属性指定工厂类,factory-method属性指定使用工厂类中的哪个静态方法实例化Bean -->
<bean id="userService" class="com.wlplove.factory.UserServiceFactory" factory-method="getUserService"/>

4.3、实例工厂

4.3.1、方式一

Java 类:

// 工厂类
public class UserServiceFactory {
    // 工厂方法(非静态)
    public UserService getUserService() {
        return new UserServiceImpl();
    }
}

Spring 配置:

<!-- 工厂Bean -->
<bean id="userServiceFactory" class="com.wlplove.factory.UserServiceFactory" />

<!-- 用前面定义好的工厂类userServiceFactory中的方法getUserService来实例化userService -->
<!-- factory-bean 指定工厂类,factory-method 方法指定用工厂类的哪个方法实例化 Bean -->
<bean id="userService" factory-bean="userServiceFactory" factory-method="getUserService" />
4.3.2、方式二(重要)

工厂对象继承 FactoryBean<T> 这个对象,并重写其中的 getObject()getObjectType() 方法

FactoryBean 是 Spring 框架中用于创建复杂对象的一个特殊接口,它允许开发者创建一个工厂类,用于生成其他对象。这个接口定义了三个主要方法:

  1. getObject()
    • 作用:返回由该 FactoryBean 创建的对象实例。这是 FactoryBean 最核心的方法,用于实际创建对象实例。
    • 注意:这个方法可能会抛出异常,因为对象的创建过程可能涉及复杂的逻辑和资源管理。
  2. getObjectType()
    • 作用:返回由该 FactoryBean 创建的对象的类型。这有助于 Spring 容器在运行时了解 Bean 的类型信息,从而进行类型匹配和依赖注入。
    • 默认实现:如果未重写此方法,则默认返回 Object.class。
  3. isSingleton()
    • 作用:指定返回的实例是否为单例。如果返回 true,则表示这个 FactoryBean 返回的对象在整个 Spring 容器中是唯一的;如果返回 false,则每次请求都会创建一个新的实例。
    • 默认实现:如果没有明确指定,默认返回 true,即单例模式。

Java 类:

// 省略 UserService 接口与 UserServiceImpl 继承类的定义...

public class UserServiceFactoryBean implements FactoryBean<UserService> {
    @Override
    public UserServce getObject() throws Exception {
        return new UserServiceImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }

    @Override
    public boolean isSingleton() {
        // true 返回单例对象,false 返回非单例对象
        return ture;
    }
}

Spring 配置:

<bean id="userService" class="com.wlplove.factory.UserServiceFactoryBean" />

五、依赖注入(DI)

依赖注入(Dependency Injection,简称 DI)是 Spring 框架的核心概念之一,这种设计模式用于实现控制反转(Inversion of Control,即 IoC)。它的核心思想是:对象的依赖关系由外部容器(如 Spring 容器)在运行时注入,而不是由对象自己创建或查找依赖

举个例子,假设有一个 UserService 类,它依赖于 UserRepository 类。在没有依赖注入的情况下,UserService 需要自己创建 UserRepository 的实例。这种方式的问题在于,UserServiceUserRepository 紧密耦合在一起,难以测试和维护。

使用依赖注入后,UserService 不再自己创建 UserRepository,而是通过构造函数、Setter 方法或自动装配的方式,由 Spring 容器提供 UserRepository 的实例。

Spring 提供了三种主要的依赖注入方式:setter 注入、构造器注入、自动装配。

5.1、setter 注入

5.1.1、注入简单类型

Java 类中提供成员的 set 方法:

public class User {
    // 姓名
    private String name;
    // 年龄
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Spring 配置中用 <property> 标签来定义具体成员:

<!-- 简单类型 (通过 value 属性注入普通类型值) -->
<bean id="user" class="com.wlplove.dto.User">
    <property name="name" value="test" />
    <property name="age" value="20" />
</bean>
5.1.2、注入引用类型

同样的,在 Java 类中提供对象的 set 方法:

// 省略接口UserDao接口的定义 ...

// 接口继承类
public class UserServiceImpl implements userService {
    private UserDao userDao;

    private setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.insert();
    }
}

Spring 配置:

<bean id="userDao" class="com.wlplove.dao.UserDao" />

<!-- 引用类型 (通过 ref 属性注入引用类型值) -->
<bean id="userServiceImpl" class="com.wlplove.service.UserServiceImpl">
    <property name="userDao" ref="userDao" />
</bean>

5.2、构造器注入

在对象创建时,所有的依赖都通过构造器参数传入,类似于 setter 注入需要提供成员 set 方法,构造方法注入需要在类中提供构造方法,并且 XML 配置里使用构造器注入的参数顺序必须要与 Java 类构造方法的形参顺序相同

5.2.1、注入简单类型

构造方法:

public class User {
    // 姓名
    private String name;
    // 年龄
    private int age;

    public void User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Spring 配置:

<!-- 普通类型 (通过 value 属性注入引用类型值,name属性值是构造方法中的形参)-->
<bean id="user" class="com.wlplove.dto.User">
    <!-- 这里的参数顺序要与构造函数中的形参顺序相同 -->
    <construct-arg name="name" value="test" />
    <construct-arg name="age" value="20" />
</bean>
5.2.2、注入引用类型
// 接口继承类
public class UserServiceImpl implements userService {
    private UserDao userDao;

    private UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.insert();
    }
}

Spring 配置:

<bean id="userDao" class="com.wlplove.dao.UserDao" />

<!-- 引用类型 (通过 ref 属性注入引用类型值,name 属性的值是构造方法形参的值)-->
<bean id="userServiceImpl" class="com.wlplove.service.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao" />
</bean>
5.2.3、构造器注入的其他XML配置写法
5.2.3.1、type方式

constructor-arg 标签中使用 type 属性:

<bean id="userDao" class="com.wlplove.dao.UserDao" />

<bean id="userServiceImpl" class="com.wlplove.service.UserServiceImpl">
    <constructor-arg type="int" value="123" />
    <constructor-arg type="java.lang.String" value="test" />
    <constructor-arg type="com.wlplove.dao.UserDao" ref="userDao" />
</bean>

type 方式解决了构造器注入时 name 属性绑定了形参名称从而造成强耦合的问题,如果一个 Bean 中某种类型(比如 int)的属性只有一个,那么可以用这种方式,反之,当相同类型的属性有很多个时,用这种方式就会给所有这种类型的属性都注入相同的值。

4.2.3.2、index索引方式

constructor-arg 标签中使用 index 属性:

<!-- index索引方式,解决参数类型重复问题,使用索引进行参数匹配对应 -->
<bean id="userDao" class="com.wlplove.dao.UserDao" />

<bean id="user" class="com.wlplove.dao.dto.User">
    <constructor-arg index="0" value="20" />
    <constructor-arg index="1" value="test" />
    <constructor-arg index="2" ref="userDao" />
</bean>

5.3、基于XML文件的自动装配

所谓自动装配就是省略手动在 XML 中配置 Bean 依赖的步骤,而使 Spring 容器自动解析 Bean 之间的依赖关系,并将依赖的 Bean 注入到目标 Bean 中。需要注意的是,自动装配只针对于 Bean 中引用类型的依赖,简单类型的依赖无法被自动装配。

5.3.1、byType

Spring 容器会根据属性的类型来自动装配。它会在容器中查找与需要注入的属性类型相同的 Bean,并将其注入到该属性中。

要使 Spring 容器按 Bean 类型自动装配,加入 autowire="byType"

<bean id="bookService" class="com.wlplove.service.UserServiceImpl" autowire="byType" />

使用 byType 这种方式,必须保证配置文件中所有 Bean 的 class 属性值是唯一的,否则就会报错

5.3.2、byName

Spring 容器会根据属性名称来自动装配,在容器中查找与需要注入的属性名称相同的 Bean,并将其注入到该属性中。严格来说,此处的属性名称并非是指成员变量名称,而是指 set 方法名去掉 set 之后将首字母小写的名称,比如 User 类中 age 属性的 set 方法为 setAge1(),那么将 set 方法处理之后得到的属性名称为 age1,Spring 容器根据属性名称 age1 自动装配,而不是 age

要使 Spring 容器按 Bean 名称自动装配,加入 autowire="byName"

<bean id="bookService" class="com.wlplove.service.UserServiceImpl" autowire="byName" />
5.3.3、constructor

基于构造函数的自动装配。Spring 容器会查找与构造函数中形参的数量与类型相同的 Bean,并通过构造函数注入。

<bean id="bookService" class="com.wlplove.service.UserServiceImpl" autowire="constructor" />

5.4、基于注解的自动装配

除了 XML 配置,也可以使用 @Autowired 注解来实现自动装配:

  • 构造器注入:将 @Autowired 注解标注在构造器上,Spring 容器根据构造器参数类型来注入依赖。
  • Setter方法注入:将 @Autowired 注解标注在 set 方法上,Spring 容器根据 set 方法的名称和参数类型来注入依赖。
  • 字段注入:直接将 @Autowired 注解标注在字段上,Spring 容器根据字段类型来注入依赖。

六、集合的注入

接下来的例子以 setter 注入为例进行说明,构造器注入类似,将 <property></property> 标签修改为 <constructor-arg></constructor-arg> 即可。

6.1、注入数组

<!-- name 属性设置将数据注入哪一个数组 -->
<property name="test_array">
    <!-- 此处 array 与 list 可以混用 -->
    <array>
        <!-- 引用类型 -->
        <value>123</value>
        <value>456</value>
        <value>789</value>
        <!-- 引用类型 -->
        <!-- <ref bean="beanId"></ref> -->
    </array>
</property>

6.2、注入 Set 对象

<property name="test_set">
    <set>
        <value>set_qwe1</value>
        <value>set_asd2</value>
        <value>set_zxc3</value>
        <!-- 重复会自动过滤 -->
        <value>set_zxc3</value>
    </set>
</property>

6.3、注入 List 对象

<property name="test_list">
    <!-- 此处 array 与 list 可以混用 -->
    <list>
        <value>list_qwe1</value>
        <value>list_asd2</value>
        <value>list_zxc3</value>
    </list>
</property>

6.4、注入 Map 类型数据

注入 Map 对象:

<property name="test_map">
    <map>
        <entry key="contry" value="China"></entry>
        <entry key="province" value="Gansu"></entry>
        <entry key="city" value="Lanzhou"></entry>
    </map>
</property>

6.5、注入 property 类型数据

<property name="test_properties">
    <props>
        <prop key="contry">China</prop>
        <prop key="province">Gansu</prop>
        s<prop key="city">Lanzhou</prop>
    </props>
</property>
  • <property> 标签表示 setter 方式注入,构造方式注入 <constructor-arg> 标签内部也可以写 <array><list><set><map><props> 标签
  • List 的底层也是通过数组实现的,所以 <list><array> 标签可以混用
  • 若要在集合中添加引用类型,只需要把 <value> 标签改成 <ref> 标签,但这种方式用的比较少

七、Spring容器的核心操作

7.1、创建容器

通过类路径来加载配置文件的方式创建容器:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

// 加载多个文件,也就是将多个XML文件的配置都加载到一个Spring容器中:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml", "applicationContext2.xml");

也可以通过文件路径来加载配置文件的方式创建容器:

ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\applicationContext.xml");

// 加载多个文件,也就是将多个XML文件的配置都加载到一个Spring容器中:
ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\applicationContext1.xml", "E:\\applicationContext2.xml");

7.2、从Srping容器获取Bean

7.2.1、根据Bean名称/ID
// 需要类型强制转换
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
7.2.2、根据Bean类型获取
// 参数是 类名.class
BookDao bookDao = ctx.getBean(BookDao.class);

当按类型获取 Bean 时,必须保证该类型的 Bean 在 Spring 容器中只有一个,否则会抛出 NoUniqueBeanDefinitionException 异常。

7.2.3、根据Bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);

八、一图流总结

8.1、Bean:

01

8.2、依赖注入:

02

4

评论 (0)

取消