Spring的深入浅出(2)

Spring框架以及基于XML的IOC配置(2)


Spring对bean的管理方式

        Spring实例化bean的方式

无参数构造函数实例化方式是Spring默认的bean的实例化方式。

Spring 默认 先用“无参构造器 new 出来”再填属性,所以类里必须有一个什么都不带的  public xxx() ,否则启动就报错。

想象 Spring 是一台“自动拼乐高机器人”:

  1. 你给它一张说明书(配置)。
  2. 机器人先找“最小块”(无参构造器),拼出空壳(new xxx())。
  3. 接着按说明书把其他积木(属性)插到空壳上。

为什么要拼出空壳呢?

        因为机器人得先有个“盒子”才能把东西往里装
        如果没有空壳(对象实例),后面属性、依赖都无处安放,连“插积木”这一步都进行不了

  无参构造实现实例化对象

类里只有无参构造器,Spring 能直接  new  出来并注入属性——这就是“默认无参构造实例化”。    

public class UserService {
    private UserDao userDao;

    // 1. 必须保留的无参构造器(默认就用它)
    public UserService() {
    }

    // 2. setter 供 Spring 注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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

        

在xml中配置

<bean id="userService" class="com.example.UserService">
    <property name="userDao" ref="userDao"/>
</bean>

启动时 Spring 做的事:
 new UserService()  → 调  setUserDao(...)  → Bean 就绪。

但是new对象会造成高耦合吧?

把“怎么造对象”的知识从业务代码里拿走,集中放到工厂方法或配置里


这样实现类名、构造细节变化时,调用方和 Spring 配置都不必改,耦合自然降低

那我们还是用工厂吧

我不想让 Spring 直接  new  对象,而是交给一个自己写的工厂方法去“定制生产”

工厂中普通方法实现实例化对象

XML

<!-- factory-method 指定静态方法 -->
<bean id="orderService"
      class="com.example.OrderFactory"
      factory-method="createOrderService"/>

在工厂类

public class OrderFactory {
    // 普通(静态)工厂方法
    public static OrderService createOrderService() {
        OrderService service = new OrderService();
        service.setPrefix("2025");   // 做一些额外加工
        return service;
    }
}

使用工厂

ApplicationContext ctx =
        new ClassPathXmlApplicationContext("beans.xml");
OrderService orderService = (OrderService) ctx.getBean("orderService");

工厂中的静态方法实例化对象

XML

<bean id="orderServiceFromFactory"
      class="com.example.OrderFactory"
      factory-method="createOrderService"/>

public class OrderFactory {
    // 静态工厂方法:Spring 唯一会调用的入口
    public static OrderService createOrderService() {
        return new OrderService();   // 直接 new,不做额外加工,更纯粹
    }
}

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

// 拿到的就是 OrderFactory.createOrderService() 返回的实例
OrderService orderService = ctx.getBean("orderServiceFromFactory", OrderService.class);

无参构造 自己 new → 硬编码;

普通工厂方法 → 先建工厂对象再调实例方法;

静态工厂方法 → 直接调静态方法,最简洁。

Bean的细节

        Bean的作用域

        默认情况下,Bean都是单例的

那我们要怎么修改bean对象的作用范围呢?

bean标签的scope属性:

作用:用于指定bean的作用范围

取值: 常用的就是单例的和多例的

singleton:单例的(默认值)

prototype:多例的

*request:作用于web应用的请求范围

*session:作用于web应用的会话范围

*global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是 session。

我们可以测试一下把Bean改为多例

Bean的生命周期

        单例对象

        单例对象的生命周期和容器的生命周期是一致的。当容器(IoC 容器,也就是  ApplicationContext)创建时,对象就实例化好了。当容器还在的时 候,对象也就一直存在。当容器销毁,对象也就消亡。

        多例对象

        出生:当我们使用对象时spring框架为我们创建

        活着:对象只要是在使用过程中就一直活着

        死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收

        

总结:

单例对象随容器生而生、随容器死而死;

多例对象每次 getBean() 都当场生,生后自生自灭,容器不保管。

DI的概念和作用

        依赖注入(DI  Dependency Injection)

概念

IoC 是“把对象的创建权交给容器”

DI 是“容器把对象塞给需要它的地方”(依赖关系的维护)

DI 是 IoC 的实现方式,让代码只管用、不管造

DI使用实例

依赖注入能注入的数据类型:

  1. 基本类型和String
  2. 其他bean类型(在配置文件中或者注解配置过的bean)
  3. 复杂类型/集合类型

注入的方式

  1. 使用构造函数提供
  2. 使用set方法提供
  3. 使用注解提供(后面介绍)

构造函数注入

        构造函数注入是依赖注入(DI)的一种方式,通过在配置文件中使用 constructor-arg 标签,为Bean的构造函数提供参数值,从而完成依赖的注入

        使用的标签: constructor-arg 
                标签位置:位于 bean 标签内部
                标签属性:
                         name :指定构造函数中参数的名称(最常用)
                         value :用于提供基本类型和 String 类型的数据
                         ref :用于引用其他Bean类型的数据(该Bean已在Spring的IoC核心容器中定义)

        在使用构造函数注入时,需要确保POJO类提供相应的构造函数,以便Spring容器在创建Bean实例时能够正确地注入依赖。

        

配置文件

<bean id="user" class="com.xq.pojo.User">
    <constructor-arg name="username" value="eric"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" value="2022-10-10"></constructor-arg>
</bean>

然后创建测试类

@Test
 public void test07(){
    ApplicationContext context = new 
    ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User)context.getBean("user");
    System.out.println(user);
}
 

但是发现报错了,因为Spring 不能自动把字符串转成  Date  类型,所以会报错。

Date  类型不像基本数据类型(如  int )那样有明确的转换规则,需要显式地进行类型转换

解决方案:

记得创建一个date的实例bean

测试成功

总结

对于构造函数注入
        优势:注入数据是必须的,如果没有提供相应的参数,对象就无法创建成功。因为类没有默认的无参构造函数,Spring 必须通过构造函数注入参数来创建对象
        弊端:这种方式改变了 Bean 对象的实例化方式。即使某些参数在创建对象时并不需要,也必须提供这些参数。这可能会导致一些不必要的数据在创建对象时被强制注入。
        总而言之,构造函数注入要求必须提供所有构造函数所需的参数,这保证了对象在创建时的完整性,但也可能在某些情况下引入不必要的数据依赖

set方法注入(默认)

        
        使用的标签:property
                标签位置:bean标签的内部
                标签属性:
                            name:用于指定注入时所调用的set方法名称(如:setName --> name)
                            value:用于提供基本类型和String类型的数据
                            ref:用于引用其他Bean类型的数据(该Bean已在Spring的IoC核心容器中定义)

创建实体类,并提供set方法(与构造函数不同,它采用set了)

还是一样的date文件,记得配置

<bean id="user" class="com.xq.pojo.User">
    <property name="username" value="eric"></property>
    <property name="age" value="38"></property>
    <property name="birthday" ref="date"></property>
</bean>


<bean id="date" class="java.util.Date"></bean>

总结

与构造函数恰恰相反


        优点:灵活性高:可以在Bean对象创建后动态地改变其属性值。这在一些需要灵活配置属性的场景下非常有用,例如根据不同的业务逻辑动态设置对象的属性。
                   易于理解:对于熟悉面向对象编程中setter方法的人来说,很容易理解和使用set注入。代码的可读性相对较好,因为属性的设置是通过直观的方法调用来完成的。
        缺点:不能保证对象的完整性:因为可以在对象创建后随时改变其属性,可能会导致对象处于不完整的状态。例如,一个对象在创建后未设置必要的属性就投入使用,可能会引发运行时错误。

复杂类型的数据注入

本身变化不多,多是处理<list> 、 <set> 、 <map> 、 <props> 等标签指定集合元素。

        注入方式:
set注入:
        其他Bean:用 <property> 标签的 ref 属性指定Bean的id。
        集合类型:在 <property> 标签内用 <list> 、 <set> 、 <map> 、 <props> 等标签指定集合元素。
构造函数注入:
        其他Bean:用 <constructor-arg> 标签的 ref 属性指定Bean的id。
        集合类型:在 <constructor-arg> 标签内用 <list> 、 <set> 、 <map> 、 <props> 等标签指定集合元素。

复杂类型注入的好处:

  • 增强协作:便于不同Bean协作,构建复杂应用功能。
  • 提升可维护性和可扩展性:方便修改集合内容,无需修改代码和重新编译。
  • 体现依赖注入思想:组件依赖由Spring容器管理,降低耦合度,利于复用和测试。
  • 支持复杂场景:满足如控制器注入路径变量列表、缓存管理器注入映射配置等复杂场景需求。

基于XML配置搭建IOC案例

流程:

  1. 在数据库创建数据表
  2. 新建工程,导入依赖
  3. 编写实体类
  4. 编写业务层接口 AccountService
  5. 编写持久层接口 AccountDao
  6. 编写业务层实现类
  7. 编写持久层实现类
  8. 编写Spring的配置文件
  9. 编写测试文件

在数据库创建数据表

create table account(
 id int primary key auto_increment,
 name varchar(40),
 money double
 )character set utf8 collate utf8_general_ci;
 insert into account(name,money) values(eric,1000);
 insert into account(name,money) values('james',1000);
 insert into account(name,money) values('curry',1000);

新建一个普通的maven工程,导入以下依赖

<dependencies>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>5.0.2.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>commons-dbutils</groupId>
 <artifactId>commons-dbutils</artifactId>
 <version>1.4</version>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.6</version>
 </dependency>
 <dependency>
 <groupId>c3p0</groupId>
 <artifactId>c3p0</artifactId>
 <version>0.9.1.2</version>
 </dependency>
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.12</version>
 </dependency>
 </dependencies>

编写实体类

public class Account {
    private Integer id;
    private String name;
    private Double money;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

编写业务层接口 AccountService

public interface AccountService {
    /**
     * 查询所有
     *
     * @return
     */
    List<Account> findAllAccount();
    /**
     * 查询一个
     *
     * @return
     */
    Account findAccountById(Integer accountId);
  /**
     * 保存账户
     *
     * @param account
     */
    void saveAccount(Account account);
    /**
     * 更新账户
     *
     * @param account
     */
    void updateAccount(Account account);
    /**
     * 删除账户
     *
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}

编写持久层接口AccountDao

public interface AccountDao {
    /**
     * 查询所有
     *
     * @return
     */
    List<Account> findAllAccount();
    /**
     * 查询一个
     *
     * @return
     */
    Account findAccountById(Integer accountId);
    /**
     * 保存账户
     *
     * @param account
     */
    void saveAccount(Account account);
    /**
     * 更新账户
     *
     * @param account
     */
    void updateAccount(Account account);
    /**
     * 删除账户
     *
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}

编写业务层实现类

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    /**
     * 使用set方法注入AccountDao
     * @param accountDao
     */
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }
    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }
    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }
}

编写持久层实现类

public class AccountDaoImpl implements AccountDao {
    private QueryRunner runner;
    /**
     * 使用set方法注入QueryRunner
     * @param runner
     */
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }
    public List<Account> findAllAccount() {
        try{
            return runner.query("select * from account",new 
BeanListHandler<Account>(Account.class));
   }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public Account findAccountById(Integer accountId) {
        try{
            return runner.query("select * from account where id = ? ",new 
BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public void saveAccount(Account account) {
        try{
            runner.update("insert into 
account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public void updateAccount(Account account) {
        try{
            runner.update("update account set name=?,money=? where 
id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

但是我们现在还无法运行,因为我们还没有配置ioc,还没有注入依赖

编写Spring的配置文件

        <?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">
    <!--    配置Service对象-->
    <bean id="accountService" class="com.xq.service.impl.AccountServiceImpl">
        <!--注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--    配置Dao对象-->
    <bean id="accountDao" class="com.xq.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner对象-->
        <property name="runner" ref="runner"></property>
    </bean>
    <!--    
        配置QueryRunner对象
        对于数据源对象创建多例的,因为可能是有多个用户都在用数据源,可能存在一个用户在用数据源
的时候,另外一个用户正在用的问题。
        
    -->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://192.168.10.137:3306/ssm"></property>
        <property name="user" value="root"></property>
        <property name="password" value="Admin123!"></property>
    </bean>
</beans>

测试文件编写

        public class TestAccount {
    @Test
    public void test(){
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到业务层对象
        AccountService as = ac.getBean("accountService", AccountService.class);
        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

可以测试了,检查测试结果。

最后的工程目录如下

一些复习的笔记(浏览即可):

<!--    对bean的依赖注入  给bean的属性赋值的过程 就叫做依赖注入
        1.默认使用set方法进行注入,使用set方法进行注入的前提是 被管理的bean所属的类必须提供set方法
            property 标签: 完成bean的属性的注入  完成属性的赋值
            name  描述bean的属性名称
            value  描述bean的属性名称(基本数据类型和字符串的值)
            ref: 注入的是引用数据类型的值 引用的是另一个bean的id  记住定义一个配置去描述这个bean
-->
<!--
spring的依赖注入的第二种方式: 使用构造函数实现依赖注入
constructor-arg: 完成依赖注入 使用的是带参数的构造函数完成
        name : 属性名称
        value: 属性名称所属的值  必须是基本数据类型和字符串类型的值
        ref: 引用数据类型的值
-->
1.spring 入门环境的搭建
    第一步:导入spring的核心依赖
        spring-context
    第二步: 定义dao service的接口以及实现类
    第三步: 创建xml的配置文件,通过bean标签来管理bean
            标准格式: <bean id="" class=""></bean>
    第四步: 获取bean
            首先要初始化bean工厂(IOC容器)
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            调用工厂提供的getBean方法来获取bean。
2.Bean工厂的一些细节
  1.BeanFactory和ApplicationContext之间的区别
        BeanFactory:Bean工厂(IOC容器)的顶级接口,里面定义了Bean工厂的约束和规范
        getBean() 从容器中获取bean的方法
    ApplicationContext:间接的继承了BeanFactory。对BeanFactory的方法进行了拓展。
    BeanFactory和ApplicationContext之间的区别:
        最大的区别是 bean的加载时机不一样。
            ApplicationContext:立即加载所有的bean。当bean工厂一般初始化完成,工厂需要管理所有的bean也随之初始化完成。
            BeanFactory:懒加载所需要的bean。bean的创建不会随着bean工厂的创建而创建,而是调用bean的时候,(getBean方法)才会初始化bean。
  2.关于ApplicationContext的实现类:
        ClassPathXmlApplicationContext: 加载类路径下面的spring配置文件。(最常用)
        FileSystemXmlApplicationContext: 加载磁盘绝对路径下面的配置文件。(很少用)
        AnnotationConfigApplicationContext: 用于注解环境,创建的容器(后面再解释)
bean的一些细节:
    1.bean的管理方式
        无参数的构造函数管理(默认)
        静态实例化方法管理bean
        实例化工厂管理bean
    2.bean的作用域
        scope属性描述bean的作用域
            singleton 单例bean   默认就是单例的bean
            prototype 多例bean
    3.bean的生命周期
        init-method:描述初始化bean的时候需要执行的方法
        destroy-method:描述bean销毁的时候需要执行的方法

        至此,Spring框架的概述以及Spring基于 XMl的IOC配置这一章结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值