Rubin's Blog

  • 首页
  • 关于作者
  • 隐私政策
享受恬静与美好~~~
分享生活的点点滴滴~~~
  1. 首页
  2. Spring
  3. 正文

Spring之使用方式详解

2021年 5月 24日 1154点热度 0人点赞 0条评论

前言

随着SpringBoot的风靡,我们享受到了开箱即用带来的便利。但是,我们对于Spring的学习还是要从根本学起。要从最初的使用方式作为切入点,达到一通百通的目的。

本博文主要讲述了Spring框架的几种使用方式以及各种使用方式对应的注意事项。为之后的源码学习打下一个基础。也为之后的SpringBoot的学习做铺垫。

Spring的使用方式

BeanFactory与ApplicationContext区别

首先,我们来介绍一下BeanFactory和ApplicationContext的区别。

BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的。

通常,我们称BeanFactory为Spring IoC的基础容器,ApplicationContext是容器的⾼级接口,比BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等。它们的继承关系如下图:

启动 IoC 容器的方式

我们再来介绍一下IoC容器的启动方式。Spring为各个场景下都定制了特殊的容器类。每一种容器类都有它独到的启动方式。

java环境下启动IoC容器:

  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)。
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件。
  • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器。

web环境下启动IoC容器:

  • 从xml启动容器,配置方式如下:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置Spring IoC容器的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!--使用监听器启动Spring的IoC容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>
  • 从配置类启动容器,配置方式如下:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--告诉ContextloaderListener知道我们使用注解的方式启动IoC容器-->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <!--配置启动类的全限定类名-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.rubin.SpringConfig</param-value>
    </context-param>
    <!--使用监听器启动Spring的IoC容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

纯xml的方式使用Spring

我们最开始接触Spring,当是官方推荐的就是xml模式。我们先来说一下纯xml方式使用Spring。

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

创建bean的方式

此种方式下,初始化bean有三种方式:

  • 使用无参构造函数

在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建失败。

<!--配置service对象-->
<bean id="userService" class="com.rubin.service.impl.UserServiceImpl">
</bean>
  • 使用静态方法创建

在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个方法是static修饰的⽅法,即是此种情况。

例如,我们在做Jdbc操作时,会用到java.sql.Connection接口的实现类,如果是MySQL数据库,那么用的就是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection(),因为我们要注册驱动,还要提供URL和凭证信息,用DriverManager.getConnection方法来获取连接。

那么在实际开发中,尤其早期的项目没有使用Spring框架来管理对象的创建,但是在设计时使用了工厂模式解耦,那么当接入Spring之后,工厂类创建对象就具有和上述例子相同特征,即可采用此种方式配置。

<!--使用静态方法创建对象的配置方式-->
<bean id="userService" class="com.rubin.factory.BeanFactory" factory-method="getUserService">
</bean>
  • 使用实例化方法创建

此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是类中的⼀ 个普通方法。此种方式比静态方法创建的使用几率要⾼⼀些。

在早期开发的项目中,工厂类中的方法有可能是静态的,也有可能是非静态方法,当是非静态方法时,即可采用下面的配置⽅式:

<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory" class="com.rubin.factory.instancemethod.BeanFactory">
</bean>
<bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService">
</bean>

bean的作用范围与生命周期

作用范围

Spring框架管理的bean对象在创建时默认都是单例的。但是,我们可以通过配置的方式来改变bean的作用范围。bean的所有作用范围如下图所示:

在上图列举的作用范围中,我们常用的也就是singleton(单例)和prototype(原型)。配置方式如下:

<!--配置service对象-->
<bean id="transferService" class="com.rubin.service.impl.TransferServiceImpl" scope="singleton">
</bean>

生命周期

bean的不同的作用范围所对应的生命周期各不相同。常用的如下所示:

  • 单例模式:singleton

对象出生:当创建容器的时候,对象就被创建了。

对象活着:只要容器不销毁,对象就一直存活。

对象死亡:容器销毁,对象也一起销毁。

一句话总结:单例模式的bean的生命周期和容器相同。

  • 多例模式:prototype

对象出生:当使用对象时,创建新的对象实例。

对象活着:只要对象在使用中,对象就一直存活。

对象死亡:对象长时间不用,就会被GC回收。

一句话总结:多例模式的bean对象,Spring框架只负责创建,不负责销毁。

bean标签的属性

在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的一个对象。话句话说,如果一个对象想要让IoC容器管理,在xml的配置中都需要使用此标签配置。bean标签的属性如下:

  • id:用于给bean提供一个全局唯一个标识。
  • classs:用于指定创建bean对象的全限定类名。
  • name:用于给bean提供一个或多个名称。多个名称用空格分隔。
  • factory-bean:用于指定创建该对象的工厂bean的唯一标识。当指定了该属性之后,class属性将失效。
  • factory-method:用于指定创建改对象的工厂方法。若配合factory-bean使用,class属性将失效。若配合class使用,则该方法必须是静态的。
  • scope:用于指定bean对象的作用范围。通常情况下就是singleton。当用到其他模式是可以自定义指定。
  • init-method:用于指定bean对象的初始化方法,此方法会在bean对象装配后被调用。该方法必须是一个无参的方法。
  • destroy-method:用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只能为scope为singleton的bean生效。

依赖注入的配置

依赖注入按照注入的方式分为以下两类:

  • 构造器注入:利用有参构造器来给属性赋值。
  • set方法注入:利用set方法来给属性赋值。

按照注入的数据类型,分为以下两类:

  • 基本类型和String:注入的值为基本类型或者String。
  • 其他bean类型:注入的数据类型是对象类型。称为其他bean类型的原因是,注入的对象类型要求出现在容器中。
  • 复杂类型:注入的数据类型是Array、List、Set、Map、Properties中的一种类型。

构造器注入

顾名思义,就是利用构造函数实现对类成员的赋值。它的使用要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。

当使用有参构造器注入时,涉及的标签是constructor-arg,改标签有如下属性:

  • name:用于给构造函数中指定名称的参数赋值。
  • index:用于给构造函数中指定索引位置的参数赋值。
  • value:用于指定基本类型或者String类型的数据。
  • ref:用于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。

set方法注入

在使用set方法注入时,需要使用property标签,该标签属性如下:

  • name:指定注入时调⽤的set方法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)。
  • value:指定注入的数据。它⽀持基本类型和String类型。
  • ref:指定注入的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。

复杂类型注入

复杂类型不是一个特殊的注入类型,而是它配置起来有些复杂,所以我们单独说一下。复杂类型在构造器注入和set方法注入都可以配置。我们以set方式注入为示例来讲解。

set方法注入的示例如下图所示:

在List结构的集合数据注入时,array ,list , set这三个标签通用,另外注值的value标签内部可以直接写值,也可以使用bean标签配置⼀个对象,或者用ref标签引用⼀个已经配置的bean的唯⼀标识。

在Map结构的集合数据注入时, map标签使用entry子标签实现数据注入,entry标签可以使用key和value属性指定存⼊map中的数据。使用value-ref属性指定已经配置好的bean的引⽤。同时entry标签中也可以使⽤ref标签,但是不能使用bean标签。而property标签中不能使用ref或者bean标签引用对象。

xml和注解相结合模式使用Spring

在我们的实际开发中,单纯使用xml的方式来使用Spring已经很罕见了。而xml和注解相结合的方式才是当下最流行的使用方式。

这种结合的使用方式,可以很清晰的管理我们自己的bean和第三方的bean。我们在使用过程中,约定俗成的规则是:将第三方的bean(数据库连接池等等)配置在配置文件中,而将我们自己开发的bean使用注解来管理。

我们Spring的注解其实和xml中的配置项是一一对应的,下表列出了常用的注解和配置项的对应关系:

xml形式对应的注解形式
标签@Component("accountDao"),注解加在类上。bean的id属性内容直接配置在注解后面。如果不配置,默认定义个这个bean的id为类的类名⾸字母小写。
另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的用法完全⼀样,只是为了更清晰的区分而已。
标签的scope属性@Scope("prototype"),默认单例,注解加在类上。
标签的init-method属性@PostConstruct,注解加在方法上,该方法就是初始化后调用的方法。
标签的destory-method属性@PreDestory,注解加在方法上,该方法就是销毁前调用的方法。

依赖注入

@Autowire注入

@Autowire是Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired。此中注入方式是我们比较推荐使用的一种。

@Autowire默认的注入策略是按照类型注入。示例代码如下:

public class TransferServiceImpl {
    @Autowired
    private AccountDao accountDao;
}

如上代码所示,这样装配会去Spring容器中找到类型为AccountDao的类,然后将其注入进来。这样会产生⼀个问题,当⼀个类型有多个bean值的时候,会造成无法选择具体注入哪⼀个的情况,这个时候我们需要配合着@Qualifier使用。

public class TransferServiceImpl {
    @Autowired
    @Qualifier(name="jdbcAccountDaoImpl")
    private AccountDao accountDao;
}

@Resource注入

@Resource 注解由 J2EE 提供,需要导入包 javax.annotation.Resource。@Resource 默认按照 ByName 自动注入。

public class TransferService {
    @Resource
    private AccountDao accountDao;
    @Resource(name="studentDao")
    private StudentDao studentDao;
    @Resource(type="TeacherDao")
    private TeacherDao teacherDao;
    @Resource(name="manDao",type="ManDao")
    private ManDao manDao;
}

它的注入规则如下:

  • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配。找不到则抛出异常。
  • 如果指定了name,则从Spring上下文中找到id为name的bean进行装配。找不到则抛出异常。
  • 如果指定了type,则从Spring上下文中找到类型和type匹配的bean进行装配。如果找到多个或找不到都会抛出异常。
  • 如果既没有指定name,也没有指定type,则按照byName的方式自动装配。

注意:

@Resource 在 Jdk 11中已经移除,如果要使用,需要单独引入jar包:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

纯注解方式使用Spring

顾名思义,也就是说我们可以完全使用注解的方式来使用Spring,而舍弃掉xml配置文件。常用的注解如下所示:

  • @Configuration注解,表名当前类是⼀个配置类。
  • @ComponentScan注解,替代context:component-scan。
  • @PropertySource,引入外部属性配置⽂件。
  • @Import引入其他配置类。
  • @Value对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息。
  • @Bean将⽅法返回对象加入Spring IoC容器。

Spring IoC高级特性

lazy-init延迟加载

ApplicationContext 容器的默认行为是在启动服务器时将所有singleton bean提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext实例会创建并配置所有的singleton bean。

比如:

<bean id="testBean" class="com.rubin.LazyBean"/>
该bean默认的设置为:
<bean id="testBean" calss="com.rubin.LazyBean" lazy-init="false"/>

lazy-init="false"就表示立即加载,也就是在容器启动的时候就实例化。

如果我们不想让一个singleton bean在容器启动的时候就实例化,而是想着什么时候用到,什么时候再实例化的话,我们可以设置如下:

<bean id="testBean" calss="com.rubin.LazyBean" lazy-init="true"/>

如果一个设置了立即加载的bean1引用了一个延迟加载的bean2,那么bean1在容器启动时实例化的过程中会把bean2也实例化好,这也符合延迟加载在第一次使用时被加载的规则。

我们也可以在容器层次中通过在元素上设置default-lazy-init属性来控制全局的bean是否默认延迟加载,如下所示:

<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果一个bean的scope设置为了scope="pototype"时,即使设置了lazy-init="false",容器启动的时候也不会实例化该bean。而是等到调用getBean()方法的时候实例化。

延迟加载的应用场景是可以将一些加载耗时并且不常用的bean设置成延迟加载。这样的话会提高系统的启动速度,也可以在不使用该bean的时候不加载,节省了资源。

FactoryBean和BeanFactory

BeanFactory接口是容器的顶级接口,定义了容器的一些基本行为是负责生产和管理bean的一个工厂。我们是用的是它下面具体的子接口类型,例如ApplicationContext。

Spring中bean有两种类型:一种是普通的bean,一种是FactoryBean。FactoryBean可以生成一个具体的bean实例返回给容器,也就是说,我们可以借助它来自定义实现bean的创建过程。

bean创建的三种方式中的静态方法和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架⼀些组件中会使用,还有其他框架和Spring框架整合时使用。

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
    @Nullable
    // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
    T getObject() throws Exception;
    @Nullable
    // 返回FactoryBean创建的Bean类型
    Class<?> getObjectType();
    // 返回作⽤域是否单例
    default boolean isSingleton() {
        return true;
    }
}

例如我们有一个自定义的类User:

@Data
public class User {
    
    private Long id;

    private String name;

    private Integer age;
 
} 

我们来自定义过一个FactoryBean来自定义构件我们的User对象:

public class UserFactoryBean implements FactoryBean<User> {
    private String userInfo;
    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }
    @Override
    public Company getObject() throws Exception {
        // 模拟创建复杂对象User
        User user = new User();
        String[] strings = userInfo.split(",");
        user.setId(Long.valueOf(strings[0]));
        user.setName(strings[1]);
        user.setAge(Integer.valueOf(strings[2]));
        return user;
    }
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

将其配置到我们的容器中:

<bean id="userBean" class="com.rubin.factory.UserFactoryBean">
    <property name="userInfo" value="1,小明,18"/>
</bean>

测试我们的配置结果:

Object userBean = applicationContext.getBean("userBean");
System.out.println("bean:" + userBean);
// 结果如下
bean:User{id=1, name='小明', age=18}

获取FactoryBean,我们需要在id前面加一个"&":

Object userBean = applicationContext.getBean("&userBean");
System.out.println("bean:" + userBean);
// 结果如下
bean:com.rubin.factory.UserFactoryBean@53f6fd09

后置处理器

BeanPostProcessor

BeanPostProcessor是针对bean级别的处理。可以针对具体的某个bean。定义如下图所示:

该接口提供了两个方法,分别在bean的初始化方法前和初始化方法后执行。具体这个初始化方法是什么方法,类似于我们定义的init-method方法。

定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发生在Spring容器的实例化和依赖注⼊之后。

BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个bean的工厂进行处理,典型应用:PropertyPlaceholderConfigurer。

此接口只提供了一个方法,方法参数为ConfigrableListableBeanFactory。该参数类型定义了一些方法:

其中,有一个getBeanDefinition(String)的方法。我们可以根据此方法,找到我们定义bean的BeanDefinition对象。然后我们对定义的属性进行修改,以下是BeanDefinition中的方法:

方法名字类似我们bean标签的属性,setBeanClassName(String)对应bean标签中的class属性,所以当我们拿
到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。

BeanDefinition对象:我们在xml或者配置类里面配置的类或者标有@Component注解以及子注解的类,都会被Spring容器解析成一个描述bean对象的JavaBean。这个JavaBean就是我们的BeanDefinition。
注意:容器调用BeanFactoryPostProcessor的方法时,bean还没有实例化,只是全部被解析成了BeanDefinition对象。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: Spring
最后更新:2022年 6月 9日

RubinChu

一个快乐的小逗比~~~

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • 前言
  • Spring的使用方式
    • BeanFactory与ApplicationContext区别
    • 启动 IoC 容器的方式
    • 纯xml的方式使用Spring
      • 创建bean的方式
      • bean的作用范围与生命周期
      • bean标签的属性
      • 依赖注入的配置
    • xml和注解相结合模式使用Spring
      • 依赖注入
    • 纯注解方式使用Spring
  • Spring IoC高级特性
    • lazy-init延迟加载
    • FactoryBean和BeanFactory
    • 后置处理器
      • BeanPostProcessor
      • BeanFactoryPostProcessor
最新 热点 随机
最新 热点 随机
问题记录之Chrome设置屏蔽Https禁止调用Http行为 问题记录之Mac设置软链接 问题记录之JDK8连接MySQL数据库失败 面试系列之自我介绍 面试总结 算法思维
SpringBoot之内嵌Tomcat Docker之安装 Neo4j之数据管理和优化 MySQL之索引原理 MyBatis之延迟加载 手写简易的SpringMVC

COPYRIGHT © 2021 rubinchu.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

京ICP备19039146号-1