[记录学习]自学动力节点老杜Spring6笔记_02

首先感谢动力节点和杜老师的教学分享!Respect!

学习来源:B站
https://www.bilibili.com/video/BV1Ft4y1g7Fb/?spm_id_from=333.337.search-card.all.click&vd_source=07c8a1a7d89af39fe20c3a6894f5ff6a
资料来源:百度网盘
链接:https://pan.baidu.com/s/1yXl8Kg80utfqazkuaE_Q8g?pwd=dljd
提取码:dljd

文章目录

六、GoF之工厂模式

  • 设计模式:一种可以被重复利用的解决方案。

  • GoF(Gang of Four),中文名——四人组。

  • 《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),1995年由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著。这几位作者常被称为"四人组(Gang of Four)"。

  • 该书中描述了23种设计模式。我们平常所说的设计模式就是指这23种设计模式。

  • 不过除了GoF23种设计模式之外,还有其它的设计模式,比如:JavaEE的设计模式(DAO模式、MVC模式等)。

  • GoF23种设计模式可分为三大类:

    • 创建型(5个):解决对象创建问题。

      • 单例模式
      • 工厂方法模式
      • 抽象工厂模式
      • 建造者模式
      • 原型模式
    • 结构型(7个):一些类或对象组合在一起的经典结构。

      • 代理模式
      • 装饰模式
      • 适配器模式
      • 组合模式
      • 享元模式
      • 外观模式
      • 桥接模式
    • 行为型(11个):解决类或对象之间的交互问题。

      • 策略模式
      • 模板方法模式
      • 责任链模式
      • 观察者模式
      • 迭代子模式
      • 命令模式
      • 备忘录模式
      • 状态模式
      • 访问者模式
      • 中介者模式
      • 解释器模式
  • 工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式。这里为什么学习工厂模式呢?这是因为Spring框架底层使用了大量的工厂模式。

6.1 工厂模式的三种形态

  • 工厂模式通常有三种形态:
    • 第一种:简单工厂模式(Simple Factory):不属于23种设计模式之一。简单工厂模式又叫做:静态 工厂方法模式。简单工厂模式是工厂方法模式的一种特殊实现。
    • 第二种:工厂方法模式(Factory Method):是23种设计模式之一。
    • 第三种:抽象工厂模式(Abstract Factory):是23种设计模式之一。

6.2 简单工厂模式

  • 简单工厂模式的角色包括三个:

    • 抽象产品 角色
    • 具体产品 角色
    • 工厂类 角色
  • 简单工厂模式的代码如下:

    • 抽象产品角色:

      • com.powernode.spring6.factory.Weapon
      package com.powernode.spring6.factory;
      
      /**
       * 武器(抽象产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.Weapon
       * @date 2022/11/14
       * @since 1.0
       */
      public abstract class Weapon {
      
          /**
           * 所有的武器都有攻击行为
           */
          public abstract void attack();
      }
      
    • 具体产品角色

      • com.powernode.spring6.factory.Tank
      package com.powernode.spring6.factory;
      
      /**
       * 坦克(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.Tank
       * @date 2022/11/14
       * @since 1.0
       */
      public class Tank extends Weapon{
      
          @Override
          public void attack() {
              System.out.println("坦克开炮!");
          }
      }
      
      • com.powernode.spring6.factory.Fighter
      package com.powernode.spring6.factory;
      
      /**
       * 战斗机(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.Fighter
       * @date 2022/11/14
       * @since 1.0
       */
      public class Fighter extends Weapon{
          @Override
          public void attack() {
              System.out.println("战斗机投下原子弹!");
          }
      }
      
      • com.powernode.spring6.factory.Dagger
      package com.powernode.spring6.factory;
      
      /**
       * 匕首(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.Dagger
       * @date 2022/11/14
       * @since 1.0
       */
      public class Dagger extends Weapon{
          @Override
          public void attack() {
              System.out.println("突刺刺!");
          }
      }
      
    • 工厂类角色

      • com.powernode.spring6.factory.WeaponFactory
      package com.powernode.spring6.factory;
      
      /**
       * 工厂类角色
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.WeaponFactory
       * @date 2022/11/14
       * @since 1.0
       */
      public class WeaponFactory {
      
          /**
           * 根据不同的武器类型生产武器
           * @param weaponType 武器类型
           * @return  武器对象
           */
          public static Weapon get(String weaponType){
              if (weaponType == null || weaponType.trim().length() == 0) {
                  return null;
              }
              Weapon weapon = null;
              if ("Tank".equals(weaponType)) {
                  weapon = new Tank();
              }else if("Fighter".equals(weaponType)){
                  weapon = new Fighter();
              }else if("Dagger".equals(weaponType)){
                  weapon = new Dagger();
              }else{
                  throw new RuntimeException("不支持该武器");
              }
              return weapon;
          }
      }
      
    • 测试程序

      package com.powernode.spring6.factory;
      
      /**
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory.Client
       * @date 2022/11/14
       * @since 1.0
       */
      public class Client {
          public static void main(String[] args) {
              Weapon tank = WeaponFactory.get("Tank");
              tank.attack();
      
              Weapon fighter = WeaponFactory.get("Fighter");
              fighter.attack();
      
              Weapon dagger = WeaponFactory.get("Dagger");
              dagger.attack();
          }
      }
      
    • 测试结果

      在这里插入图片描述

  • 简单工厂模式的优点:

  • 客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”。生产和消费分离。

  • 简单工厂模式的缺点:

    • 缺点1:工厂类集中了所有产品的创造逻辑,形成一个无所不知的全能类,有人把它叫做上帝类。显然工厂类非常关键,不能出问题,一旦出问题,整个系统瘫痪。
    • 缺点2:不符合OCP开闭原则,在进行系统扩展时,需要修改工厂类。
  • Spring中的BeanFactory就使用了简单工厂模式。

6.3 工厂方法模式

  • 工厂方法模式既保留了简单工厂模式的优点,同时又解决了简单工厂模式的缺点。

  • 工厂方法模式的角色包括:

    • 抽象工厂角色
    • 具体工厂角色
    • 抽象产品角色
    • 具体产品角色
  • 代码如下:

    • 抽象产品角色

      • com.powernode.spring6.factory1.Weapon
      package com.powernode.spring6.factory1;
      
      /**
       * 武器类(抽象产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.Weapon
       * @date 2022/11/14
       * @since 1.0
       */
      public abstract class Weapon {
      
          /**
           * 所有武器都有攻击行为
           */
          public abstract void attack();
      }
      
    • 具体产品角色

      • com.powernode.spring6.factory1.Gun
      package com.powernode.spring6.factory1;
      
      
      /**
       * 枪(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.Gun
       * @date 2022/11/14
       * @since 1.0
       */
      public class Gun extends Weapon {
          @Override
          public void attack() {
              System.out.println("FBI! Open the door! Hands up!");
          }
      }
      
      • com.powernode.spring6.factory1.Fighter
      package com.powernode.spring6.factory1;
      
      
      /**
       * 战斗机(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.Fighter
       * @date 2022/11/14
       * @since 1.0
       */
      public class Fighter extends Weapon {
          @Override
          public void attack() {
              System.out.println("战斗只因实施战术核打击!");
          }
      }
      
    • 抽象工厂角色是

      • com.powernode.spring6.factory1.WeaponFactory
      package com.powernode.spring6.factory1;
      
      
      /**
       * 武器工厂接口(抽象工厂角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.WeaponFactory
       * @date 2022/11/14
       * @since 1.0
       */
      public interface WeaponFactory {
          Weapon get();
      }
      
    • 具体工厂角色

      • com.powernode.spring6.factory1.GunFactory
      package com.powernode.spring6.factory1;
      
      /**
       * 枪工厂(具体工厂角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.GunFactory
       * @date 2022/11/14
       * @since 1.0
       */
      public class GunFactory implements WeaponFactory{
          @Override
          public Weapon get() {
              return new Gun();
          }
      }
      
      • com.powernode.spring6.factory1.FighterFactory
      package com.powernode.spring6.factory1;
      
      /**
       * 战斗机工厂(具体工厂角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.FighterFactory
       * @date 2022/11/14
       * @since 1.0
       */
      public class FighterFactory implements WeaponFactory{
          @Override
          public Weapon get() {
              return new Fighter();
          }
      }
      
    • 客户端测试

    package com.powernode.spring6.factory1;
    
    /**
     * 客户端程序
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.factory1.Client
     * @date 2022/11/14
     * @since 1.0
     */
    public class Client {
        public static void main(String[] args) {
            WeaponFactory gunFactory = new GunFactory();
            Weapon gun = gunFactory.get();
            gun.attack();
    
            WeaponFactory fighterFactory = new FighterFactory();
            Weapon fighter = fighterFactory.get();
            fighter.attack();
        }
    }
    
    • 测试结果

    在这里插入图片描述

  • 如果想扩展一个新的产品,只要新增一个产品类,再新增一个该产品对应的工厂即可,例如

    • 新增:匕首

    • 具体产品角色

      • com.powernode.spring6.factory1.Dagger
      package com.powernode.spring6.factory1;
      
      /**
       * 匕首(具体产品角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.Dagger
       * @date 2022/11/14
       * @since 1.0
       */
      public class Dagger extends Weapon{
          @Override
          public void attack() {
              System.out.println("突刺刺!");
          }
      }
      
    • 具体工厂角色

      • com.powernode.spring6.factory1.DaggerFactory
      package com.powernode.spring6.factory1;
      
      /**
       * 匕首工厂(具体工厂角色)
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory1.DaggerFactory
       * @date 2022/11/14
       * @since 1.0
       */
      public class DaggerFactory implements WeaponFactory{
          @Override
          public Weapon get() {
              return new Dagger();
          }
      }
      
    • 客户端测试

    package com.powernode.spring6.factory1;
    
    /**
     * 客户端程序
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.factory1.Client
     * @date 2022/11/14
     * @since 1.0
     */
    public class Client {
        public static void main(String[] args) {
            WeaponFactory gunFactory = new GunFactory();
            Weapon gun = gunFactory.get();
            gun.attack();
    
            WeaponFactory fighterFactory = new FighterFactory();
            Weapon fighter = fighterFactory.get();
            fighter.attack();
    
            WeaponFactory daggerFactory = new DaggerFactory();
            Weapon dagger = daggerFactory.get();
            dagger.attack();
        }
    }
    
    • 测试结果

    在这里插入图片描述

  • 我们可以看到在进行功能扩展的时候,不需要修改之前的源代码,显然工厂方法模式符合OCP原则。

  • 工厂方法模式的优点:

    • 一个调用者想创建一个对象,只要知道其名称就可以了。
    • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
    • 屏蔽产品的具体实现,调用者只关心产品的接口。
  • 工厂方法模式的缺点:

    • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

6.4 抽象工厂模式(了解)

  • 抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类。

  • 抽象工厂模式特点:

    • 抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。
    • 抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。
    • 抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。
    • 它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。
    • 每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果。
  • 抽象工厂中包含4个角色:

    • 抽象工厂角色
    • 具体工厂角色
    • 抽象产品角色
    • 具体产品角色
  • 抽象工厂模式的类图如下:

    在这里插入图片描述

  • 抽象工厂模式代码如下:

    • 第一部分:武器产品族:

      • com.powernode.spring6.product.Weapon
      package com.powernode.spring6.product;
      
      /**
       * 武器产品族
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Weapon
       * @date 2022/11/15
       * @since 1.0
       */
      public abstract class Weapon {
          public abstract void attack();
      }
      
      • com.powernode.spring6.product.Gun
      package com.powernode.spring6.product;
      
      /**
       * 武器产品族中的产品等级1
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Gun
       * @date 2022/11/15
       * @since 1.0
       */
      public class Gun extends Weapon{
          @Override
          public void attack() {
              System.out.println("biabiabia!biabiabia!");
          }
      }
      
      • com.powernode.spring6.product.Dagger
      package com.powernode.spring6.product;
      
      /**
       * 武器产品族中的产品等级2
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Dagger
       * @date 2022/11/15
       * @since 1.0
       */
      public class Dagger extends Weapon{
          @Override
          public void attack() {
              System.out.println("突刺刺!");
          }
      }
      
    • 第二部分:水果产品族

      • com.powernode.spring6.product.Fruit
      package com.powernode.spring6.product;
      
      /**
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Fruit
       * @date 2022/11/15
       * @since 1.0
       */
      public abstract class Fruit {
          /**
           * 所有果实都有一个成熟周期。
           */
          public abstract void ripCycle();
      }
      
      • com.powernode.spring6.product.Orange
      package com.powernode.spring6.product;
      
      /**
       * 水果产品族中的产品等级1
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Orange
       * @date 2022/11/15
       * @since 1.0
       */
      public class Orange extends Fruit{
          @Override
          public void ripCycle() {
              System.out.println("橘子的成熟周期是十个月。");
          }
      }
      
      • com.powernode.spring6.product.Apple
      package com.powernode.spring6.product;
      
      /**
       * 水果产品族中的产品等级2
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.product.Apple
       * @date 2022/11/15
       * @since 1.0
       */
      public class Apple extends Fruit{
          @Override
          public void ripCycle() {
              System.out.println("苹果的成熟周期是八个月。");
          }
      }
      
    • 第三部分:抽象工厂类

      • com.powernode.spring6.factory2.AbstractFactory
      package com.powernode.spring6.factory2;
      
      import com.powernode.spring6.product.Fruit;
      import com.powernode.spring6.product.Weapon;
      
      /**
       * 抽象工厂
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory2.AbstractFactory
       * @date 2022/11/15
       * @since 1.0
       */
      public abstract class AbstractFactory {
          public abstract Weapon getWeapon(String type);
          public abstract Fruit getFruit(String type);
      }
      
    • 第四部分:具体工厂

      • com.powernode.spring6.factory2.WeaponFactory
      package com.powernode.spring6.factory2;
      
      import com.powernode.spring6.product.Dagger;
      import com.powernode.spring6.product.Fruit;
      import com.powernode.spring6.product.Gun;
      import com.powernode.spring6.product.Weapon;
      
      /**
       * 武器族工厂
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory2.WeaponFactory
       * @date 2022/11/15
       * @since 1.0
       */
      public class WeaponFactory extends AbstractFactory{
          @Override
          public Weapon getWeapon(String type) {
              if (type == null || type.trim().length() == 0) {
                  return null;
              }else if("Gun".equals(type)){
                  return new Gun();
              } else if ("Dagger".equals(type)) {
                  return new Dagger();
              }else{
                  throw new RuntimeException("无法生产该武器!");
              }
          }
      
          @Override
          public Fruit getFruit(String type) {
              return null;
          }
      }
      
      • com.powernode.spring6.factory2.FruitFactory
      package com.powernode.spring6.factory2;
      
      import com.powernode.spring6.product.Apple;
      import com.powernode.spring6.product.Fruit;
      import com.powernode.spring6.product.Orange;
      import com.powernode.spring6.product.Weapon;
      
      /**
       * 水果族工厂
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.factory2.FruitFactory
       * @date 2022/11/15
       * @since 1.0
       */
      public class FruitFactory extends AbstractFactory{
          @Override
          public Weapon getWeapon(String type) {
              return null;
          }
      
          @Override
          public Fruit getFruit(String type) {
              if (type == null || type.trim().length() == 0) {
                  return null;
              } else if ("Orange".equals(type)) {
                  return new Orange();
              } else if ("Apple".equals(type)) {
                  return new Apple();
              }else{
                  throw new RuntimeException("我家果园没有这种水果!");
              }
          }
      }
      
    • 第五部分:客户端测试程序

      • com.powernode.spring6.client.client
      package com.powernode.spring6.client;
      
      import com.powernode.spring6.factory2.AbstractFactory;
      import com.powernode.spring6.factory2.FruitFactory;
      import com.powernode.spring6.factory2.WeaponFactory;
      import com.powernode.spring6.product.Fruit;
      import com.powernode.spring6.product.Weapon;
      
      /**
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.client.client
       * @date 2022/11/15
       * @since 1.0
       */
      public class client {
          public static void main(String[] args) {
              // 客户端调用方法时只面向AbstractFactory调用方法
              AbstractFactory weaponFactory = new WeaponFactory();
              // 注意:这里的new WeaponFactory();可以采用 简单工厂模式 进行隐藏
              Weapon gun = weaponFactory.getWeapon("Gun");
              gun.attack();
              Weapon dagger = weaponFactory.getWeapon("Dagger");
              dagger.attack();
      
              AbstractFactory fruitFactory = new FruitFactory();
              // 注意:这里的new WeaponFactory();可以采用 简单工厂模式 进行隐藏
              Fruit orange = fruitFactory.getFruit("Orange");
              orange.ripCycle();
              Fruit apple = fruitFactory.getFruit("Apple");
              apple.ripCycle();
          }
      }
      
    • 测试结果

    在这里插入图片描述

  • 抽象工厂模式的优缺点:

    • 优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
    • 缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在AbstractFactory里加代码,又要在具体的里面加代码。

七、Bean的实例化方式

  • Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
    • 第一种:通过构造方法实例化
    • 第二种:通过简单工厂模式实例化
    • 第三种:通过factory-bean实例化
    • 第四种:通过FactoryBean接口实例化

7.1 通过构造方法实例化

我们之前一直使用的就是这种方式。默认情况下,会调用Bean的无参数构造方法。

  • com.powernode.spring6.bean.User
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.User
 * @date 2022/11/19
 * @since 1.0
 */
public class User {
    public User() {
        System.out.println("User类的无参数构造方法执行!");
    }
}
  • 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.powernode.spring6.bean.User" />
</beans>
  • 测试程序
package com.powernode.spring6.test;

import com.powernode.spring6.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.test.SpringInstantiationTest
 * @date 2022/11/19
 * @since 1.0
 */
public class SpringInstantiationTest {

    @Test
    public void testConstructor(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }
}
  • 测试结果

在这里插入图片描述

7.2 通过简单工厂模式实例化

  • 第一步:定义一个Bean

    • com.powernode.spring6.bean.Vip
    package com.powernode.spring6.bean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.Vip
     * @date 2022/11/19
     * @since 1.0
     */
    public class Vip {
    }
    
  • 第二步:编写简单工厂模式当中的工厂类

    • com.powernode.spring6.bean.VipFactory
    package com.powernode.spring6.bean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.VipFactory
     * @date 2022/11/19
     * @since 1.0
     */
    public class VipFactory {
        public static Vip get(){
            return new Vip();
        }
    }
    
  • 第三步:在Spring配置文件中指定创建该Bean的方法(使用factory-method属性指定)

<bean id="vip" class="com.powernode.spring6.bean.VipFactory" factory-method="get"/>
  • 测试程序
@Test
public void testSimpleFactory(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Vip vip = applicationContext.getBean("vip", Vip.class);
    System.out.println(vip);
}
  • 测试结果

在这里插入图片描述

7.3 通过factory-bean实例化

这种方式本质上是:通过工厂方法模式进行实例化。

  • 第一步:定义一个Bean

    • com.powernode.spring6.bean.Order

      package com.powernode.spring6.bean;
      
      /**
       * @author shanglinsong
       * @version 1.0
       * @className com.powernode.spring6.bean.Order
       * @date 2022/11/19
       * @since 1.0
       */
      public class Order {
      }
      
  • 第二步:定义具体工厂类,工厂类中定义实例方法

    • com.powernode.spring6.bean.OrderFactory
    package com.powernode.spring6.bean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.OrderFactory
     * @date 2022/11/19
     * @since 1.0
     */
    public class OrderFactory {
        public Order get(){
            return new Order();
        }
    }
    
  • 第三步:在Spring配置文件中指定factory-bean以及factory-method

<bean id="orderFactory" class="com.powernode.spring6.bean.OrderFactory"/>
<bean id="order" factory-bean="orderFactory" factory-method="get"/>
  • 测试程序
@Test
public void testSelfFactoryBean(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Order order = applicationContext.getBean("order", Order.class);
    System.out.println(order);
}
  • 测试结果

在这里插入图片描述

7.4 通过FactoryBean接口实例化

以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。

在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。

factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。

  • 第一步:定义一个Bean

    • com.powernode.spring6.bean.Person
    package com.powernode.spring6.bean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.Person
     * @date 2022/11/19
     * @since 1.0
     */
    public class Person {
    }
    
  • 第二步:编写一个类实现FactoryBean接口

    • com.powernode.spring6.bean.PersonFactoryBean
    package com.powernode.spring6.bean;
    
    import org.springframework.beans.factory.FactoryBean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.PersonFactoryBean
     * @date 2022/11/19
     * @since 1.0
     */
    public class PersonFactoryBean implements FactoryBean<Person> {
        @Override
        public Person getObject() throws Exception {
            return new Person();
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    
        @Override
        public boolean isSingleton() {
            return FactoryBean.super.isSingleton();
        }
    }
    
  • 第三步:在Spring配置文件中配置FactoryBean

<bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean"/>
  • 测试程序
@Test
public void testFactoryBean(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Person person = applicationContext.getBean("person", Person.class);
    System.out.println(person);

    Person person1 = applicationContext.getBean("person", Person.class);
    System.out.println(person1);
}
  • 测试结果

在这里插入图片描述

FactoryBean在Spring中是一个接口。被称为“工厂Bean”。“工厂Bean”是一种特殊的Bean。所有的“工厂Bean”都是用来协助Spring框架来创建其他Bean对象的。

7.5 BeanFactory和FactoryBean的区别

7.5.1 BeanFactory
  • Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”
  • 在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
  • BeanFactory是工厂。
7.5.2 FactoryBean
  • FactoryBean:

    • 它是一个Bean,
    • 是一个能够辅助Spring实例化其它Bean对象的一个Bean。
  • 在Spring中,Bean可以分为两类:

    • 第一类:普通Bean
    • 第二类:工厂Bean
  • 记住:

    • 工厂Bean也是一种Bean,
    • 只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。

7.6 注入自定义Date

我们前面说过,java.util.Date在Spring中被当做简单类型,简单类型在注入的时候可以直接使用value属性或value标签来完成。但我们之前已经测试过了,对于Date类型来说,采用value属性或value标签赋值的时候,对日期字符串的格式要求非常严格,必须是这种格式的:Mon Oct 10 14:30:26 CST 2022。其他格式是不会被识别的。如以下代码:

  • com.powernode.spring6.bean.Student
package com.powernode.spring6.bean;

import java.util.Date;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Student
 * @date 2022/11/19
 * @since 1.0
 */
public class Student {
    private Date birth;

    @Override
    public String toString() {
        return "Student{" +
                "birth=" + birth +
                '}';
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}
  • spring.xml
<bean id="student" class="com.powernode.spring6.bean.Student">
    <property name="birth" value="Mon Oct 10 14:30:26 CST 2002"/>
</bean>
  • 测试程序
@Test
public void testDate(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Student student= applicationContext.getBean("student", Student.class);
    System.out.println(student);
}
  • 测试结果

在这里插入图片描述

  • 如果把日期格式修改一下:
<bean id="student" class="com.powernode.spring6.bean.Student">
    <property name="birth" value="2022-11-19"/>
</bean>
  • 测试结果

在这里插入图片描述

  • 这种情况下,我们就可以使用FactoryBean来完成这个骚操作。

    • 编写DateFactoryBean实现FactoryBean接口:com.powernode.spring6.bean.DateFactoryBean
    package com.powernode.spring6.bean;
    
    import org.springframework.beans.factory.FactoryBean;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.DateFactoryBean
     * @date 2022/11/19
     * @since 1.0
     */
    public class DateFactoryBean implements FactoryBean<Date> {
    
        // 定义属性接收日期字符串
        private String date;
    
        // 通过构造方法给日期字符串属性赋值
        public DateFactoryBean(String date) {
            this.date = date;
        }
    
        @Override
        public Date getObject() throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            return sdf.parse(this.date);
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    }
    
    • spring.xml
    <bean id="date" class="com.powernode.spring6.bean.DateFactoryBean">
        <constructor-arg name="date" value="2022-11-19 14:45:11 111"/>
    </bean>
    <bean id="student" class="com.powernode.spring6.bean.Student">
        <property name="birth" ref="date"/>
        <!--<property name="birth" value="2022-11-19 14:45:11 111"/>-->
        <!--<property name="birth" value="Mon Oct 10 14:30:26 CST 2002"/>-->
    </bean>
    
    • 测试结果

    在这里插入图片描述

八、Bean的生命周期

8.1 什么是Bean的生命周期

  • Spring其实就是一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。
  • 所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。
  • 什么时候创建Bean对象?
  • 创建Bean对象的前后会调用什么方法?
  • Bean对象什么时候销毁?
  • Bean对象的销毁前后调用什么方法?

8.2 为什么要知道Bean的生命周期

  • 其实生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。
  • 我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。
  • 只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写到哪。
  • 我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。

8.3 Bean的生命周期之5步

Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。

Bean生命周期可以粗略的划分为五大步:

  • 第一步:实例化Bean
  • 第二步:Bean属性赋值
  • 第三步:初始化Bean
  • 第四步:使用Bean
  • 第五步:销毁Bean

在这里插入图片描述

  • 编写测试程序:

    • 定义一个Bean:com.powernode.spring6.bean.User
    package com.powernode.spring6.bean;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.User
     * @date 2022/11/19
     * @since 1.0
     */
    public class User {
    
        private String name;
    
        public User() {
            System.out.println("1. 实例化Bean");
        }
    
        public void setName(String name) {
            this.name = name;
            System.out.println("2. Bean属性赋值");
        }
    
        public void initBean(){
            System.out.println("3. 初始化Bean");
        }
    
        public void destroyBean(){
            System.out.println("5. 销毁Bean");
        }
    }
    
    • 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--
            init-method: 指定初始化方法
            destroy-method: 指定销毁方法
        -->
        <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
            <property name="name" value="zhangsan"/>
        </bean>
    </beans>
    
    • 测试程序
    package com.powernode.spring6.test;
    
    import com.powernode.spring6.bean.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.test.BeanLifeCycleTest
     * @date 2022/11/19
     * @since 1.0
     */
    public class BeanLifeCycleTest {
    
        @Test
        public void testFiveLifeCycle(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            User user = applicationContext.getBean("user", User.class);
            System.out.println("4. 使用Bean");
            // 只有正常关闭spring容器才会执行销毁方法
            ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
            context.close();
        }
    
    }
    
    • 测试结果

    在这里插入图片描述

  • 需要注意的:

    • 第一:只有正常关闭spring容器,bean的销毁方法才会被调用。
    • 第二:ClassPathXmlApplicationContext类才有close()方法。
    • 第三:配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。

8.4 Bean生命周期之7步

在以上的5步中,第3步是初始化Bean,如果你还想在初始化前初始化后添加代码,可以加入“Bean后处理器”。

  • 编写一个类实现BeanPostProcessor类,并且重写before和after方法:
package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.LogBeanPostProcessor
 * @date 2022/11/19
 * @since 1.0
 */
public class LogBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的before方法执行,即将开始初始化");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的after方法执行,已完成初始化");
        return bean;
    }
}
  • 在spring.xml文件中配置“Bean后处理器”:
<!--配置Bean后处理器,这个后处理器作用于当前配置文件中所有的bean-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>

一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。

  • 执行测试程序:

在这里插入图片描述

  • 如果加上Bean后处理器的话,Bean的生命周期就是7步了:

在这里插入图片描述

8.5 Bean生命周期之10步

如果根据源码跟踪,可以划分更细粒度的步骤,10步:

在这里插入图片描述

  • 上图中检查Bean是否实现了Aware的相关接口是什么意思?

  • Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware

    • 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
    • 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
    • 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
  • 测试以上10步,可以让User类实现5个接口,并实现所有方法:

    • BeanNameAware
    • BeanClassLoaderAware
    • BeanFactoryAware
    • InitializingBean
    • DisposableBean
  • 代码如下:

    • com.powernode.spring6.bean.User
    package com.powernode.spring6.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.*;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.User
     * @date 2022/11/19
     * @since 1.0
     */
    public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    
        private String name;
    
        public User() {
            System.out.println("1. 实例化Bean");
        }
    
        public void setName(String name) {
            this.name = name;
            System.out.println("2. Bean属性赋值");
        }
    
        public void initBean(){
            System.out.println("6. 初始化Bean");
        }
    
        public void destroyBean(){
            System.out.println("10. 销毁Bean");
        }
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            System.out.println("3. 类加载器" + classLoader);
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("3. Bean工厂" + beanFactory);
        }
    
        @Override
        public void setBeanName(String name) {
            System.out.println("3. Bean名字" + name);
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("9. DisposableBean destroy执行");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("5. afterPropertiesSet执行");
        }
    }
    
    • com.powernode.spring6.bean.LogBeanPostProcessor
    package com.powernode.spring6.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    /**
     * @author shanglinsong
     * @version 1.0
     * @className com.powernode.spring6.bean.LogBeanPostProcessor
     * @date 2022/11/19
     * @since 1.0
     */
    public class LogBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("4. Bean后处理器的before方法执行,即将开始初始化");
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("7. Bean后处理器的after方法执行,已完成初始化");
            return bean;
        }
    }
    
    • 测试程序
    @Test
    public void testFiveLifeCycle(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("8. 使用Bean");
        // 只有正常关闭spring容器才会执行销毁方法
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        context.close();
    }
    
    • 测试结果

    在这里插入图片描述

  • 通过测试可以看出来:

    • InitializingBean的方法早于init-method的执行。
    • DisposableBean的方法早于destroy-method的执行。
  • 对于SpringBean的生命周期,掌握之前的7步即可。够用。

8.6 Bean的作用域不同,管理方式不同

Spring 根据Bean的作用域来选择管理方式。

  • 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁
  • 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

我们把之前User类的spring.xml文件中的配置scope设置为prototype:

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

    <!--
        init-method: 指定初始化方法
        destroy-method: 指定销毁方法
    -->
    <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean" scope="prototype">
        <property name="name" value="zhangsan"/>
    </bean>

    <!--配置Bean后处理器,这个后处理器作用于当前配置文件中所有的bean-->
    <bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
</beans>
  • 测试结果

在这里插入图片描述

  • 通过测试一目了然。只执行了前8步,第9和10都没有执行。

8.7 自己new的对象如何让Spring管理

  • 有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,怎么实现?
  • com.powernode.spring6.bean.Customer
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Customer
 * @date 2022/11/19
 * @since 1.0
 */
public class Customer {
}
  • 将自己new的对象纳入spring容器管理
@Test
public void testBeanRegister(){
    // 自己new对象
    Customer customer = new Customer();
    System.out.println(customer);
    // 创建 默认可列表的BeanFactory 对象
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // 注册Bean
    factory.registerSingleton("customer", customer);
    // 从spring容器中获取Bean
    Customer customerBean = factory.getBean("customer", Customer.class);
    System.out.println(customerBean);
}
  • 测试结果

在这里插入图片描述

九、Bean的循环依赖问题

9.1 什么是Bean的循环依赖

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。

比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。

image.png

  • com.powernode.spring6.bean.Husband
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Husband
 * @date 2022/11/19
 * @since 1.0
 */
public class Husband {
    private String name;
    private Wife wife;

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public void setWife(Wife wife) {
        this.wife = wife;
    }
}
  • com.powernode.spring6.bean.Wife
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Wife
 * @date 2022/11/19
 * @since 1.0
 */
public class Wife {
    private String name;
    private Husband husband;

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public void setHusband(Husband husband) {
        this.husband = husband;
    }
}
  • 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="wife" class="com.powernode.spring6.bean.Wife">
        <property name="name" value="小娴"/>
        <property name="husband" ref="husband"/>
    </bean>
    <bean id="husband" class="com.powernode.spring6.bean.Husband">
        <property name="name" value="小松"/>
        <property name="wife" ref="wife"/>
    </bean>
</beans>
  • 测试程序
package com.powernode.spring6.test;

import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.test.RecycleDependencyTest
 * @date 2022/11/19
 * @since 1.0
 */
public class RecycleDependencyTest {

    @Test
    public void testSingletonAndSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Wife wife = applicationContext.getBean("wife", Wife.class);
        System.out.println(wife);
        Husband husband = applicationContext.getBean("husband", Husband.class);
        System.out.println(husband);
    }
}
  • 测试结果

在这里插入图片描述

  • 通过测试得知:在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。

9.3 prototype下的set注入产生的循环依赖

我们再来测试一下:prototype+set注入的方式下,循环依赖会不会出现问题?

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

    <bean id="wife" class="com.powernode.spring6.bean.Wife" scope="prototype">
        <property name="name" value="小娴"/>
        <property name="husband" ref="husband"/>
    </bean>
    <bean id="husband" class="com.powernode.spring6.bean.Husband" scope="prototype">
        <property name="name" value="小松"/>
        <property name="wife" ref="wife"/>
    </bean>
</beans>
  • 测试结果

在这里插入图片描述

翻译为:请求的bean当前正在创建中:是否存在无法解析的循环引用?

通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。

  • 大家可以测试一下,以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的。

    在这里插入图片描述

  • 为什么两个Bean都是prototype时会出错呢?

    image.png

9.4 singleton下的构造注入产生的循环依赖

我们再来测试一下singleton + 构造注入的方式下,spring是否能够解决这种循环依赖。

  • com.powernode.spring6.bean.Husband
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Husband
 * @date 2022/11/19
 * @since 1.0
 */
public class Husband {
    private String name;
    private Wife wife;

    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public void setWife(Wife wife) {
        this.wife = wife;
    }*/
}
  • com.powernode.spring6.bean.Wife
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Wife
 * @date 2022/11/19
 * @since 1.0
 */
public class Wife {
    private String name;
    private Husband husband;

    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public void setHusband(Husband husband) {
        this.husband = husband;
    }*/
}
  • 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="wife" class="com.powernode.spring6.bean.Wife">
        <constructor-arg name="name" value="小娴"/>
        <constructor-arg name="husband" ref="husband"/>
        <!--<property name="name" value="小娴"/>
        <property name="husband" ref="husband"/>-->
    </bean>
    <bean id="husband" class="com.powernode.spring6.bean.Husband">
        <constructor-arg name="name" value="小松"/>
        <constructor-arg name="wife" ref="wife"/>
        <!--<property name="name" value="小松"/>
        <property name="wife" ref="wife"/>-->
    </bean>
</beans>
  • 测试结果

在这里插入图片描述

和上一个测试结果相同,都是提示产生了循环依赖,并且Spring是无法解决这种循环依赖的。

为什么呢?

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。

9.5 Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?

  • 根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。

  • 实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。

  • 给Bean属性赋值的时候:调用setter方法来完成。

  • 两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。

也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

那么在Spring框架底层源码级别上是如何实现的呢?请看:

image.png

在以上类中包含三个重要的属性:

Cache of singleton objects: bean name to bean instance.单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】

Cache of early singleton objects: bean name to bean instance.早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】

Cache of singleton factories: bean name to ObjectFactory.单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】

这三个缓存其实本质上是三个Map集合。

我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。

image.png

再分析下面的源码:

image.png

从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

总结:

Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题

十、回顾反射机制

10.1 分析方法四要素

我们先来看一下,不使用反射机制调用一个方法需要几个要素的参与。

有一个这样的类:

  • com.powernode.reflect.SystemService
package com.powernode.reflect;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.reflect.SystemService
 * @date 2022/11/20
 * @since 1.0
 */
public class SystemService {

    public void logout(){
        System.out.println("退出系统");
    }

    public boolean login(String username, String password){
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }
}
  • 测试程序
package com.powernode.reflect.test;

import com.powernode.reflect.SystemService;
import org.junit.Test;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.reflect.test.ReflectTest
 * @date 2022/11/20
 * @since 1.0
 */
public class ReflectTest {

    @Test
    public void test01(){
        // 创建对象
        SystemService systemService = new SystemService();
        // 调用方法并接收方法的返回值
        boolean success = systemService.login("admin", "123");
        System.out.println(success? "登陆成功": "登陆失败");
    }
}
  • 测试结果

在这里插入图片描述

  • 通过以上第20行代码可以看出,调用一个方法,一般涉及到4个要素:
    • 调用哪个对象的(systemService)
    • 哪个方法(login)
    • 传什么参数(“admin”, “123”)
    • 返回什么值(success)

10.2 获取Method

要使用反射机制调用一个方法,首先你要获取到这个方法。

在反射机制中Method实例代表的是一个方法。那么怎么获取Method实例呢?

有这样一个类:

  • com.powernode.reflect.SystemService
package com.powernode.reflect;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.reflect.SystemService
 * @date 2022/11/20
 * @since 1.0
 */
public class SystemService {

    private int age;

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

    public void logout(){
        System.out.println("退出系统");
    }

    public boolean login(String username, String password){
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }

    public boolean login(String password){
        if ("110".equals(password)) {
            return true;
        }
        return false;
    }
}

我们如何获取到 logout()、login(String,String)、login(String) 这三个方法呢?

要获取方法Method,首先你需要获取这个类Class。

Class clazz = Class.forName("com.powernode.reflect.SystemService");

当拿到Class之后,调用getDeclaredMethod()方法可以获取到方法。

假如你要获取这个方法:login(String username, String password)

Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
  • 测试程序
@Test
public void testGetMethod() throws Exception {
    // 获取类Class的代码
    Class<?> clazz = Class.forName("com.powernode.reflect.SystemService");
    // 获取login(String username, String password)方法
    Method loginMethodUP = clazz.getDeclaredMethod("login", String.class, String.class);
    StringBuffer sb = new StringBuffer();
    sb.append(Modifier.toString(loginMethodUP.getModifiers()));
    sb.append(" ");
    sb.append(loginMethodUP.getReturnType());
    sb.append(" ");
    sb.append(loginMethodUP.getName());
    sb.append("(");
    for (int i = 0; i < loginMethodUP.getParameterTypes().length; i++) {
        sb.append(loginMethodUP.getParameterTypes()[i].getSimpleName() + " arg"+ (i));
        if(i < loginMethodUP.getParameterTypes().length-1){
            sb.append(", ");
        }
    }
    sb.append("){}");
    System.out.println(sb);
}
  • 测试结果

在这里插入图片描述

假如你要获取到这个方法:login(String password)

Method loginMethod = clazz.getDeclaredMethod("login", String.class);

获取一个方法,需要告诉Java程序,你要获取的方法的名字是什么,这个方法上每个形参的类型是什么。这样Java程序才能给你拿到对应的方法。

这样的设计也非常合理,因为在同一个类当中,方法是支持重载的,也就是说方法名可以一样,但参数列表一定是不一样的,所以获取一个方法需要提供方法名以及每个形参的类型。

假设有这样一个方法:

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

你要获取这个方法的话,代码应该这样写:

Method setAgeMethod = clazz.getDeclaredMethod("setAge", int.class);

其中setAge是方法名,int.class是形参的类型。

如果要获取上面的logout方法,代码应该这样写:

Method logoutMethod = clazz.getDeclaredMethod("logout");

因为这个方法形式参数的个数是0个。所以只需要提供方法名就行了。

10.3 调用Method

要让一个方法调用的话,就关联到四要素了:

  • 调用哪个对象的
  • 哪个方法
  • 传什么参数
  • 返回什么值

开整:

  • com.powernode.reflect.SystemService
package com.powernode.reflect;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.reflect.SystemService
 * @date 2022/11/20
 * @since 1.0
 */
public class SystemService {

    private int age;

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

    public void logout(){
        System.out.println("退出系统");
    }

    public boolean login(String username, String password){
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }

    public boolean login(String password){
        if ("110".equals(password)) {
            return true;
        }
        return false;
    }
}

假如我们要调用的方法是:login(String, String)

第一步:创建对象(四要素之首:调用哪个对象的)

Class clazz = Class.forName("com.powernode.reflect.SystemService");
Object obj = clazz.newInstance();

第二步:获取方法login(String,String)(四要素之一:哪个方法)

Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);

第三步:调用方法

Object retValue = loginMethod.invoke(obj, "admin", "admin123");

解说四要素:

  • 哪个对象:obj
  • 哪个方法:loginMethod
  • 传什么参数:“admin”, “123”
  • 返回什么值:retValue

测试程序

@Test
public void test02() throws Exception {
    Class<?> aClass = Class.forName("com.powernode.reflect.SystemService");
    Constructor<?> constructor = aClass.getConstructor();
    Object obj = constructor.newInstance();
    //        Object obj = aClass.newInstance();
    Method loginMethodUP = aClass.getDeclaredMethod("login", String.class, String.class);
    Object reValue = loginMethodUP.invoke(obj, "admin", "123");
    System.out.println(reValue);
}

测试结果

在这里插入图片描述

  • 修改newInstance()方法的原因:

在这里插入图片描述

  • 那如果调用既没有参数,又没有返回值的logout方法,应该怎么做?

    • 测试程序
    @Test
    public void test03() throws Exception{
        Class<?> aClass = Class.forName("com.powernode.reflect.SystemService");
        Constructor<?> constructor = aClass.getConstructor();
        Object obj = constructor.newInstance();
        Method logoutMethod = aClass.getDeclaredMethod("logout");
        logoutMethod.invoke(obj);
    }
    
    • 测试结果

    在这里插入图片描述

10.4 假设你知道属性名

假设有这样一个类:

  • com.powernode.reflect.User
package com.powernode.reflect;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.reflect.User
 * @date 2022/11/20
 * @since 1.0
 */
public class User {

    private String name;
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

你知道以下这几条信息:

  • 类名是:com.powernode.reflect.User
  • 该类中有String类型的name属性和int类型的age属性。
  • 另外你也知道该类的设计符合javabean规范。(也就是说属性私有化,对外提供setter和getter方法)

你如何通过反射机制给User对象的name属性赋值zhangsan,给age属性赋值20岁。

  • 测试程序(仅赋值age)
@Test
public void testUser() throws Exception {
    // 已知类名
    String className = "com.powernode.reflect.User";
    // 已知属性名
    String propertyName = "age";
    // 通过反射机制给User对象的age属性赋值20岁
    Class<?> aClass = Class.forName(className);
    Constructor<?> constructor = aClass.getConstructor();
    Object obj = constructor.newInstance();
    // 根据属性名获取setter方法
    String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
    //        System.out.println(setMethodName);
    // 通过属性名获取属性类型
    // System.out.println(aClass.getDeclaredField(propertyName).getType());
    // 获取Method
    Method setAgeMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(propertyName).getType());
    // 调用Method
    setAgeMethod.invoke(obj, 20);
    System.out.println(obj);
}
  • 测试结果

在这里插入图片描述

  • 测试程序(通过反射机制给User对象的name属性赋值zhangsan,给age属性赋值20岁。)
@Test
public void testUserSetValue() throws Exception{
    // 获取类
    Class<?> aClass = Class.forName("com.powernode.reflect.User");
    // 实例化对象
    Constructor<?> constructor = aClass.getConstructor();
    Object obj = constructor.newInstance();
    // 获取属性名
    Arrays.stream(aClass.getDeclaredFields()).forEach(property -> {
        try {
            // 配置set方法名
            String setMethodName = "set" + property.getName().toUpperCase().charAt(0) + property.getName().substring(1);
            // System.out.println(setMethodName);
            // 获取属性数据类型
            Class<?> type = property.getType();
            // 获取set方法
            Method setMethod = aClass.getDeclaredMethod(setMethodName, type);
            // 调用set方法赋值
            if ("name".equals(property.getName())) {
                setMethod.invoke(obj, "zhangsan");
            }else if("age".equals(property.getName())){
                setMethod.invoke(obj, 20);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    System.out.println(obj);
}
  • 测试结果

在这里插入图片描述

十一、手写Spring框架

Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制。

我们给自己的框架起名为:myspring(我的春天)

第一步:创建模块myspring

采用Maven方式新建Module:myspring

在这里插入图片描述

打包方式采用jar,并且引入dom4j和jaxen的依赖,因为要使用它解析XML文件,还有junit依赖。

  • 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>

    <groupId>org.myspringframework</groupId>
    <artifactId>myspring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

第二步:准备好我们要管理的Bean

准备好我们要管理的Bean(这些Bean在将来开发完框架之后是要删除的

注意包名,不要用org.myspringframework包,因为这些Bean不是框架内置的。是将来使用我们框架的程序员提供的。

  • com.powernode.myspring.bean.Address
package com.powernode.myspring.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.bean.Address
 * @date 2022/11/20
 * @since 1.0
 */
public class Address {

    private String city;
    private String street;
    private String zipcode;

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                ", zipcode='" + zipcode + '\'' +
                '}';
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    public Address() {
    }
}
  • com.powernode.myspring.bean.User
package com.powernode.myspring.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.bean.User
 * @date 2022/11/20
 * @since 1.0
 */
public class User {

    private String name;
    private int age;
    private Address address;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public User() {
    }
}

第三步:准备myspring.xml配置文件

将来在框架开发完毕之后,这个文件也是要删除的。因为这个配置文件的提供者应该是使用这个框架的程序员。

文件名随意,我们这里叫做:myspring.xml

文件放在类路径当中即可,我们这里把文件放到类的根路径下。

  • myspring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="user" class="com.powernode.myspring.bean.User">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="address" ref="address"/>
    </bean>
    <bean id="address" class="com.powernode.myspring.bean.Address">
        <property name="city" value="北京"/>
        <property name="street" value="大兴区"/>
        <property name="zipcode" value="100001"/>
    </bean>
</beans>

使用value给简单属性赋值。使用ref给非简单属性赋值。

第四步:编写ApplicationContext接口

ApplicationContext接口中提供一个getBean()方法,通过该方法可以获取Bean对象。

注意包名:这个接口就是myspring框架中的一员了。

  • org.myspringframework.core.ApplicationContext
package org.myspringframework.core;

/**
 * @author shanglinsong
 * @version 1.0
 * @className org.myspringframework.core.ApplicationContext
 * @date 2022/11/20
 * @since 1.0
 */
public interface ApplicationContext {

    /**
     * 根据bean的id获取bean实例
     * @param beanId bean的id
     * @return bean的实例
     */
    Object getBean(String beanId);
}

第五步:编写ClassPathXmlApplicationContext

ClassPathXmlApplicationContext是ApplicationContext接口的实现类。该类从类路径当中加载myspring.xml配置文件。

  • org.myspringframework.core.ClassPathXmlApplicationContext
package org.myspringframework.core;

/**
 * @author shanglinsong
 * @version 1.0
 * @className org.myspringframework.core.ClassPathXmlApplicationContext
 * @date 2022/11/20
 * @since 1.0
 */
public class ClassPathXmlApplicationContext implements ApplicationContext{

    @Override
    public Object getBean(String beanId) {
        return null;
    }
}

第六步:确定采用Map集合存储Bean

确定采用Map集合存储Bean实例。Map集合的key存储beanId,value存储Bean实例。

  • Map<String,Object>

  • 在ClassPathXmlApplicationContext类中添加Map<String,Object>属性。

并且在ClassPathXmlApplicationContext类中添加构造方法,

  • 该构造方法的参数接收myspring.xml文件。

同时实现getBean方法。

  • org.myspringframework.core.ClassPathXmlApplicationContext
package org.myspringframework.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author shanglinsong
 * @version 1.0
 * @className org.myspringframework.core.ClassPathXmlApplicationContext
 * @date 2022/11/20
 * @since 1.0
 */
public class ClassPathXmlApplicationContext implements ApplicationContext{

    /**
     * 存储Bean的Map集合
     */
    private Map<String, Object> beanMap = new HashMap<>();

    /**
     * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。
     * @param resource 配置文件路径(要求在类路径当中)
     */
    public ClassPathXmlApplicationContext(String resource) {
    }

    @Override
    public Object getBean(String beanId) {
        return beanMap.get(beanId);
    }
}

第七步:解析配置文件实例化所有Bean

在ClassPathXmlApplicationContext的构造方法中解析配置文件,获取所有bean的类名,通过反射机制调用无参数构造方法创建Bean。并且将Bean对象存放到Map集合中。

  • org.myspringframework.core.ClassPathXmlApplicationContext
package org.myspringframework.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author shanglinsong
 * @version 1.0
 * @className org.myspringframework.core.ClassPathXmlApplicationContext
 * @date 2022/11/20
 * @since 1.0
 */
public class ClassPathXmlApplicationContext implements ApplicationContext{

    /**
     * 存储Bean的Map集合
     */
    private Map<String, Object> beanMap = new HashMap<>();

    /**
     * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。
     * @param resource 配置文件路径(要求在类路径当中)
     */
    public ClassPathXmlApplicationContext(String resource) {
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));
            // 获取所有的bean标签
            List<Node> beanNodes = document.selectNodes("//bean");
            // 遍历集合
            beanNodes.forEach(beanNode -> {
                Element beanElt = (Element) beanNode;
                // 获取id
                String id = beanElt.attributeValue("id");
//                System.out.println(id);
                // 获取className
                String className = beanElt.attributeValue("class");
//                System.out.println(className);
                try {
                    // 通过反射机制创建对象
                    Class<?> aClass = Class.forName(className);
                    Constructor<?> constructor = aClass.getDeclaredConstructor();
                    Object bean = constructor.newInstance();
//                    System.out.println(bean);
                    // 存储到Map集合
                    beanMap.put(id, bean);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanId) {
        return beanMap.get(beanId);
    }
}

第八步:测试能否获取到Bean

  • 测试程序
package com.powernode.myspring.test;

import org.junit.Test;
import org.myspringframework.core.ApplicationContext;
import org.myspringframework.core.ClassPathXmlApplicationContext;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.test.MySpringTest
 * @date 2022/11/20
 * @since 1.0
 */
public class MySpringTest {

    @Test
    public void testMySpring(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
        Object user = applicationContext.getBean("user");
        System.out.println(user);
        Object address = applicationContext.getBean("address");
        System.out.println(address);
    }
}
  • 测试结果

在这里插入图片描述

通过测试Bean已经实例化成功了,属性的值是null,这是我们能够想到的,毕竟我们调用的是无参数构造方法,所以属性都是默认值。

下一步就是我们应该如何给Bean的属性赋值呢?

第九步:给Bean的属性赋值

通过反射机制调用set方法,给Bean的属性赋值。

继续在ClassPathXmlApplicationContext构造方法中编写代码。

  • org.myspringframework.core.ClassPathXmlApplicationContext
package org.myspringframework.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author shanglinsong
 * @version 1.0
 * @className org.myspringframework.core.ClassPathXmlApplicationContext
 * @date 2022/11/20
 * @since 1.0
 */
public class ClassPathXmlApplicationContext implements ApplicationContext{

    /**
     * 存储Bean的Map集合
     */
    private Map<String, Object> beanMap = new HashMap<>();

    /**
     * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。
     * @param resource 配置文件路径(要求在类路径当中)
     */
    public ClassPathXmlApplicationContext(String resource) {
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));
            // 获取所有的bean标签
            List<Node> beanNodes = document.selectNodes("//bean");
            // 遍历集合
            beanNodes.forEach(beanNode -> {
                Element beanElt = (Element) beanNode;
                // 获取id
                String id = beanElt.attributeValue("id");
//                System.out.println(id);
                // 获取className
                String className = beanElt.attributeValue("class");
//                System.out.println(className);
                try {
                    // 通过反射机制创建对象
                    Class<?> aClass = Class.forName(className);
                    Constructor<?> constructor = aClass.getDeclaredConstructor();
                    Object bean = constructor.newInstance();
//                    System.out.println(bean);
                    // 存储到Map集合
                    beanMap.put(id, bean);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            // 再重新遍历集合,这次遍历是为了给Bean的所有属性赋值
            // 思考:为什么不在上面的循环中给Bean的属性赋值,而在这里再重新遍历一次呢?
            // 通过这里你是否能够想到Spring是如何解决循环以来的:实例化和属性赋值分开。
            beanNodes.forEach(beanNode -> {
                Element beanElt = (Element) beanNode;
                // 获取id
                String id = beanElt.attributeValue("id");
                // 获取所有的property标签
                List<Element> propertyElts = beanElt.elements("property");
                // 遍历所有属性
                propertyElts.forEach(propertyElt -> {
                    try {
                        // 获取属性名
                        String propertyName = propertyElt.attributeValue("name");
                        // 获取属性类型
                        Class<?> propertyType = beanMap.get(id).getClass().getDeclaredField(propertyName).getType();
                        // 获取set方法名
                        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                        // 获取set方法
                        Method setMethod = beanMap.get(id).getClass().getDeclaredMethod(setMethodName, propertyType);
                        // 获取属性的值,可能是value,也可能是ref
                        // 获取value
                        String propertyValue = propertyElt.attributeValue("value");
                        // 获取ref
                        String propertyRef = propertyElt.attributeValue("ref");
                        Object propertyVal = null;
                        if (propertyValue != null) {
                            // 该属性是简单属性
                            String propertyTypeSimpleName = propertyType.getSimpleName();
                            switch (propertyTypeSimpleName){
                                case "byte": case "Byte":
                                    propertyVal = Byte.valueOf(propertyValue);
                                    break;
                                case "short": case "Short":
                                    propertyVal = Short.valueOf(propertyValue);
                                    break;
                                case "int": case "Integer":
                                    propertyVal = Integer.valueOf(propertyValue);
                                    break;
                                case "long": case "Long":
                                    propertyVal = Long.valueOf(propertyValue);
                                    break;
                                case "float": case "Float":
                                    propertyVal = Float.valueOf(propertyValue);
                                    break;
                                case "double": case "Double":
                                    propertyVal = Double.valueOf(propertyValue);
                                    break;
                                case "boolean": case "Boolean":
                                    propertyVal = Boolean.valueOf(propertyValue);
                                    break;
                                case "char": case "Character":
                                    propertyVal = propertyValue.charAt(0);
                                    break;
                                case "String":
                                    propertyVal = propertyValue;
                                    break;
                            }
                            setMethod.invoke(beanMap.get(id), propertyVal);
                        }
                        if (propertyRef != null) {
                            // 该属性不是简单类型
                            setMethod.invoke(beanMap.get(id), beanMap.get(propertyRef));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanId) {
        return beanMap.get(beanId);
    }
}

重点处理:当property标签中是value怎么办?是ref怎么办?

  • 测试结果

在这里插入图片描述

第十步:打包发布

将多余的类以及配置文件删除,使用maven打包发布。

在这里插入图片描述

在这里插入图片描述

第十一步:站在程序员角度使用myspring框架

新建模块:myspring-test

在这里插入图片描述

引入myspring框架的依赖:

  • 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>

    <groupId>com.powernode</groupId>
    <artifactId>myspring-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.myspringframework</groupId>
            <artifactId>myspring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

编写Bean

  • com.powernode.myspring.bean.UserDao
package com.powernode.myspring.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.bean.UserDao
 * @date 2022/11/20
 * @since 1.0
 */
public class UserDao {
    public void insert(){
        System.out.println("UserDao正在插入数据");
    }
}
  • com.powernode.myspring.bean.UserService
package com.powernode.myspring.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.bean.UserService
 * @date 2022/11/20
 * @since 1.0
 */
public class UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        System.out.println("UserService开始执行save操作");
        userDao.insert();
        System.out.println("UserService执行save操作结束");
    }
}

编写myspring.xml文件

  • myspring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="userService" class="com.powernode.myspring.bean.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="userDao" class="com.powernode.myspring.bean.UserDao"/>
</beans>
  • 测试程序
package com.powernode.myspring.test;

import com.powernode.myspring.bean.UserService;
import org.junit.Test;
import org.myspringframework.core.ApplicationContext;
import org.myspringframework.core.ClassPathXmlApplicationContext;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.myspring.test.MySpringTest
 * @date 2022/11/20
 * @since 1.0
 */
public class MySpringTest {
    
    @Test
    public void testMySpring(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.save();
    }
}
  • 测试结果

在这里插入图片描述

十二、Spring IoC注解式开发

12.1 回顾注解

注解的存在主要是为了简化XML的配置。Spring6倡导全注解开发

我们来回顾一下:

  • 第一:注解怎么定义,注解中的属性怎么定义?
  • 第二:注解怎么使用?
  • 第三:通过反射机制怎么读取注解?

注解怎么定义,注解中的属性怎么定义?

  • com.powernode.annotation.Compnent
package com.powernode.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
}

以上是自定义了一个注解:Component

该注解上面修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解。

Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上。

Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。

String value(); 是Component注解中的一个属性。该属性类型String,属性名是value。

注解怎么使用?

  • com.powernode.spring6.bean.User
package com.powernode.spring6.bean;

import com.powernode.annotation.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.User
 * @date 2022/11/20
 * @since 1.0
 */
@Component(value = "user")
public class User {
}

用法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值…)

userBean为什么使用双引号括起来,因为value属性是String类型,字符串。

另外如果属性名是value,则在使用的时候可以省略属性名,例如:

  • com.powernode.spring6.bean.User
package com.powernode.spring6.bean;

import com.powernode.annotation.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.User
 * @date 2022/11/20
 * @since 1.0
 */
//@Component(value = "user")
@Component("user")
public class User {
}

通过反射机制怎么读取注解?

接下来,我们来写一段程序,当Bean类上有Component注解时,则实例化Bean对象,如果没有,则不实例化对象。

我们准备两个Bean,一个上面有注解,一个上面没有注解。

  • com.powernode.spring6.bean.User(代码同上)
  • com.powernode.spring6.bean.Vip
package com.powernode.spring6.bean;

import com.powernode.annotation.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Vip
 * @date 2022/11/20
 * @since 1.0
 */
public class Vip {
}

假设我们现在只知道包名:com.powernode.spring6.bean。至于这个包下有多少个Bean我们不知道。哪些Bean上有注解,哪些Bean上没有注解,这些我们都不知道,如何通过程序全自动化判断。

  • 测试程序
package com.powernode.spring6.test;

import com.powernode.annotation.Component;
import org.junit.Test;

import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.test.AnnotationTest
 * @date 2022/11/20
 * @since 1.0
 */
public class AnnotationTest {
    @Test
    public void test(){
        // 存放Bean的Map集合,key存储beanId,value存储bean
        Map<String, Object> beanMap = new HashMap<>();

        String packageName = "com.powernode.spring6.bean";
        String path = packageName.replaceAll("\\.", "/");
        URL url = ClassLoader.getSystemClassLoader().getResource(path);
        File file = new File(url.getPath());
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
            String className = packageName + "." + f.getName().split("\\.")[0];
            try {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(Component.class)) {
                    Component component = aClass.getAnnotation(Component.class);
                    String beanId = component.value();
                    Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
                    Object obj = declaredConstructor.newInstance();
                    beanMap.put(beanId, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println(beanMap);
    }
}
  • 执行结果

在这里插入图片描述

12.2 声明Bean的注解

负责声明Bean的注解,常见的包括四个:

  • @Component
  • @Controller
  • @Service
  • @Repository

源码如下:

  • @Component注解
package com.powernode.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
}
  • @Controller注解
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
  • @Service注解
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
  • @Repository注解
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名。

也就是说:这四个注解的功能都一样。用哪个都可以。

只是为了增强程序的可读性,建议:

  • 控制器类上使用:Controller
  • service类上使用:Service
  • dao类上使用:Repository

他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。

在这里插入图片描述

12.3 Spring注解的使用

如何使用以上的注解呢?

  • 第一步:加入aop的依赖
  • 第二步:在配置文件中添加context命名空间
  • 第三步:在配置文件中指定扫描的包
  • 第四步:在Bean类上使用注解

第一步:加入aop的依赖

我们可以看到当加入spring-context依赖之后,会关联加入aop的依赖。所以这一步不用做。

在这里插入图片描述

第二步:在配置文件中添加context命名空间

  • 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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

第三步:在配置文件中指定要扫描的包

  • 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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.powernode.spring6.bean"/>
</beans>

第四步:在Bean类上使用注解

  • com.powernode.spring6.bean.User
package com.powernode.spring6.bean;


import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.User
 * @date 2022/11/20
 * @since 1.0
 */
@Component(value = "user")
public class User {
}

如果注解的属性名是value,那么value是可以省略的。

  • com.powernode.spring6.bean.Vip
package com.powernode.spring6.bean;

import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.Vip
 * @date 2022/11/20
 * @since 1.0
 */
//@Component(value = "vip")
@Component("vip")
public class Vip {
}

如果把value属性彻底去掉,spring会被Bean自动取名吗?会的。并且默认名字的规律是:Bean类名首字母小写即可。

  • com.powernode.spring6.bean.BankDao
package com.powernode.spring6.bean;

import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.BankDao
 * @date 2022/11/21
 * @since 1.0
 */
@Component
public class BankDao {
}

也就是说,这个BankDao的bean的名字为:bankDao

我们将Component注解换成其它三个注解,看看是否可以用:

  • com.powernode.spring6.bean.BankDao
package com.powernode.spring6.bean;

import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.BankDao
 * @date 2022/11/21
 * @since 1.0
 */
@Repository
public class BankDao {
}

剩下的两个注解大家可以测试一下。

如果是多个包怎么办?有两种解决方案:

  • 第一种:在配置文件中指定多个包,用逗号隔开。
  • 第二种:指定多个包的共同父包。

先来测试一下逗号(英文)的方式:

创建一个新的包:bean2,定义一个Bean类。

  • com.powernode.spring6.bean2.Order
package com.powernode.spring6.bean2;

import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean2.Order
 * @date 2022/11/21
 * @since 1.0
 */
@Service
public class Order {
}

配置文件修改:

  • 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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.bean2"/>
</beans>

我们再来看看,指定共同的父包行不行:

  • 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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">

    <!--<context:component-scan base-package="com.powernode.spring6.bean, com.powernode.spring6.bean2"/>-->
    <context:component-scan base-package="com.powernode.spring6"/>
</beans>
  • 测试程序
@Test
public void testBean(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user);
    Vip vip = applicationContext.getBean("vip", Vip.class);
    System.out.println(vip);
    BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
    System.out.println(bankDao);
    Order order = applicationContext.getBean("order", Order.class);
    System.out.println(order);
}
  • 测试结果

在这里插入图片描述

12.4 选择性实例化Bean

假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?

  • 这里为了方便,将这几个类都定义到同一个java源文件中了: com.powernode.spring6.bean3.A
package com.powernode.spring6.bean3;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean3.A
 * @date 2022/11/21
 * @since 1.0
 */
@Component
public class A {
    public A() {
        System.out.println("A的无参数构造方法执行!");
    }
}

@Controller
class B{
    public B() {
        System.out.println("B的无参数构造方法执行!");
    }
}

@Service
class C{
    public C() {
        System.out.println("C的无参数构造方法执行!");
    }
}

@Repository
class D{
    public D() {
        System.out.println("D的无参数构造方法执行!");
    }
}

@Controller
class E{
    public E() {
        System.out.println("E的无参数构造方法执行!");
    }
}

@Controller
class F{
    public F() {
        System.out.println("F的无参数构造方法执行!");
    }
}

我只想实例化bean3包下的Controller。配置文件这样写:

  • spring.xml
<context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

use-default-filters=“true” 表示:使用spring默认的规则,只要有Component、Controller、Service、Repository中的任意一个注解标注,则进行实例化

use-default-filters=“false” 表示:不再spring默认实例化规则,即使有Component、Controller、Service、Repository这些注解标注,也不再实例化

<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 表示只有Controller进行实例化。

  • 测试程序
@Test
public void testChoose(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
}
  • 测试结果

在这里插入图片描述

也可以将use-default-filters设置为true(不写就是true),并且采用exclude-filter方式排出哪些注解标注的Bean不参与实例化:

  • spring.xml
<context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • 测试结果

在这里插入图片描述

12.5 负责注入的注解

@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。接下来我们看一下,如何给Bean的属性赋值。给Bean属性赋值需要用到这些注解:

  • @Value
  • @Autowired
  • @Qualifier
  • @Resource
12.5.1 @Value

当属性的类型是简单类型时,可以使用@Value注解进行注入。

  • com.powernode.spring6.bean4.User
package com.powernode.spring6.bean4;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean4.User
 * @date 2022/11/21
 * @since 1.0
 */
@Component
public class User {
    @Value(value = "zhangsan")
    private String name;
    @Value("20")
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

开启包扫描:

  • spring-injection.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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.powernode.spring6.bean4"/>
</beans>
  • 测试程序
@Test
public void testValue(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
    com.powernode.spring6.bean4.User user = applicationContext.getBean("user", com.powernode.spring6.bean4.User.class);
    System.out.println(user);
}
  • 测试结果

在这里插入图片描述

通过以上代码可以发现,我们并没有给属性提供setter方法,但仍然可以完成属性赋值。

如果提供setter方法,并且在setter方法上添加@Value注解,可以完成注入吗?尝试一下:

  • com.powernode.spring6.bean4.User
package com.powernode.spring6.bean4;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean4.User
 * @date 2022/11/21
 * @since 1.0
 */
@Component
public class User {
//    @Value(value = "zhangsan")
    private String name;
//    @Value("20")
    private int age;

    @Value("李四")
    public void setName(String name) {
        this.name = name;
    }

    @Value("30")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 测试结果

在这里插入图片描述

通过测试可以得知,@Value注解可以直接使用在属性上,也可以使用在setter方法上。都是可以的。都可以完成属性的赋值。

为了简化代码,以后我们一般不提供setter方法,直接在属性上使用@Value注解完成属性赋值。

出于好奇,我们再来测试一下,是否能够通过构造方法完成注入:

  • com.powernode.spring6.bean4.User
package com.powernode.spring6.bean4;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean4.User
 * @date 2022/11/21
 * @since 1.0
 */
@Component
public class User {
//    @Value(value = "zhangsan")
    private String name;
//    @Value("20")
    private int age;

    public User(@Value("隔壁老王") String name, @Value("33") int age) {
        this.name = name;
        this.age = age;
    }

    /*@Value("李四")
    public void setName(String name) {
        this.name = name;
    }

    @Value("30")
    public void setAge(int age) {
        this.age = age;
    }*/

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 测试结果

在这里插入图片描述

通过测试得知:@Value注解可以出现在属性上、setter方法上、以及构造方法的形参上。可见Spring给我们提供了多样化的注入。太灵活了。

12.5.2 @Autowired与@Qualifier

@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。

单独使用@Autowired注解,默认根据类型装配。【默认是byType】

看一下它的源码:

  • @Autowired源码
package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
	boolean required() default true;
}

源码中有两处需要注意:

  • 第一处:该注解可以标注在哪里?

    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
  • 第二处:该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。

我们先在属性上使用@Autowired注解:

  • com.powernode.spring6.dao.UserDao
package com.powernode.spring6.dao;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDao
 * @date 2022/11/21
 * @since 1.0
 */
public interface UserDao {
    void insert();
}
  • 实现类com.powernode.spring6.dao.UserDaoForMySQL
package com.powernode.spring6.dao;

import com.powernode.spring6.bean4.User;
import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDaoForMySQL
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Repository
public class UserDaoForMySQL implements UserDao {
    @Override
    public void insert() {
        System.out.println("正在向mysql数据库插入User数据");
    }
}
  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    @Autowired
    private UserDao userDao;

    // 没有提供构造方法和setter方法

    public void save(){
        userDao.insert();
    }
}
  • spring-autowired.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"
       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 http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.powernode.spring6.dao, com.powernode.spring6.service"/>
</beans>
  • 测试程序
@Test
public void testAutowired(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowired.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.save();
}
  • 测试结果

在这里插入图片描述

以上构造方法和setter方法都没有提供,经过测试,仍然可以注入成功。

接下来,再来测试一下@Autowired注解出现在setter方法上:

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

我们再来看看能不能出现在构造方法上:

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    /*@Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

再来看看,这个注解能不能只标注在构造方法的形参上:

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    // @Autowired
    public UserService(@Autowired UserDao userDao) {
        this.userDao = userDao;
    }

    /*@Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

还有更劲爆的,当有参数的构造方法只有一个时,@Autowired注解可以省略。

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    // @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    /*@Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

当然,如果有多个构造方法,@Autowired肯定是不能省略的。

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    public UserService() {
    }

    // @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    /*@Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

到此为止,我们已经清楚@Autowired注解可以出现在哪些位置了。

@Autowired注解默认是byType进行注入的,也就是说根据类型注入的,如果以上程序中,UserDao接口还有另外一个实现类,会出现问题吗?

  • com.powernode.spring6.dao.UserDaoForOrcle
package com.powernode.spring6.dao;

import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDaoForOrcle
 * @date 2022/11/21
 * @since 1.0
 */
@Repository
public class UserDaoForOrcle implements UserDao{
    @Override
    public void insert() {
        System.out.println("正在向Oracle数据库插入User数据");
    }
}

当我写完这个新的实现类之后,IDEA工具并没有提示以下错误信息

在这里插入图片描述

  • 测试结果

在这里插入图片描述

错误信息中说:不能装配,UserDao这个Bean的数量大于1.

怎么解决这个问题呢?当然要byName,根据名称进行装配了。

@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。

  • @Qualifier注解源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
    String value() default "";
}

由此可以看出:@Qualifier不能使用在构造方法上

  • com.powernode.spring6.dao.UserDaoForOrcle
package com.powernode.spring6.dao;

import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDaoForOrcle
 * @date 2022/11/21
 * @since 1.0
 */
// 这里没有给Bean起名,默认的名字是: userDaoForOrcle
@Repository
public class UserDaoForOrcle implements UserDao{
    @Override
    public void insert() {
        System.out.println("正在向Oracle数据库插入User数据");
    }
}
  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    private UserDao userDao;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

    @Autowired
    // 这个是Bean的名字
    @Qualifier("userDaoForOrcle")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
  • 执行结果

在这里插入图片描述

总结:

  • @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。
  • 当带参数的构造方法只有一个,@Autowired注解可以省略。
  • @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。
12.5.3 @Resource

@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别?

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。

  • 如果你是Spring6+版本请使用这个依赖
<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <version>2.1.1</version>
</dependency>

一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax.* 包名统一修改为 jakarta.*包名了。)

  • 如果你是spring5-版本请使用这个依赖
<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.2</version>
</dependency>

@Resource注解的源码如下:

在这里插入图片描述

测试一下:

  • 给这个UserDaoForOracle起名xyz: com.powernode.spring6.dao.UserDaoForOrcle
package com.powernode.spring6.dao;

import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDaoForOrcle
 * @date 2022/11/21
 * @since 1.0
 */
// 这里没有给Bean起名,默认的名字是: userDaoForOrcle
@Repository("xyz")
public class UserDaoForOrcle implements UserDao{
    @Override
    public void insert() {
        System.out.println("正在向Oracle数据库插入User数据");
    }
}
  • 在UserService中使用Resource注解根据name注入
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    @Resource(name = "xyz")
    private UserDao userDao;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

    /*@Autowired
    // 这个是Bean的名字
    @Qualifier("userDaoForOrcle")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

我们把UserDaoForOracle的名字xyz修改为userDao,让这个Bean的名字和UserService类中的UserDao属性名一致:

  • com.powernode.spring6.dao.UserDaoForOrcle
package com.powernode.spring6.dao;

import org.springframework.stereotype.Repository;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.dao.UserDaoForOrcle
 * @date 2022/11/21
 * @since 1.0
 */
// 这里没有给Bean起名,默认的名字是: userDaoForOrcle
@Repository("userDao")
public class UserDaoForOrcle implements UserDao{
    @Override
    public void insert() {
        System.out.println("正在向Oracle数据库插入User数据");
    }
}
  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    @Resource
    private UserDao userDao;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

    /*@Autowired
    // 这个是Bean的名字
    @Qualifier("userDaoForOrcle")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao.insert();
    }
}
  • 测试结果

在这里插入图片描述

通过测试得知,当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。

接下来把UserService类中的属性名修改一下:

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    @Resource
    private UserDao userDao2;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

    /*@Autowired
    // 这个是Bean的名字
    @Qualifier("userDaoForOrcle")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    public void save(){
        userDao2.insert();
    }
}
  • 测试结果

在这里插入图片描述

根据异常信息得知:显然当通过name找不到的时候,自然会启动byType进行注入。以上的错误是因为UserDao接口下有两个实现类导致的。所以根据类型注入就会报错。

我们再来看@Resource注解使用在setter方法上可以吗?

  • UserService添加setter方法并使用注解标注
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    // @Resource
    private UserDao userDao;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

//    @Autowired
//    // 这个是Bean的名字
//    @Qualifier("userDaoForOrcle")
    @Resource
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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

注意这个setter方法的方法名,setUserDao去掉set之后,将首字母变小写userDao,userDao就是name

  • 测试结果

在这里插入图片描述

当然,也可以指定name:

  • com.powernode.spring6.service.UserService
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.service.UserService
 * @date 2022/11/21
 * @since 1.0
 */
// 纳入Bean管理
@Service
public class UserService {

    // 在属性上注入
    // @Autowired
    // @Resource
    private UserDao userDao;

    /*public UserService() {
    }*/

    // @Autowired
    /*public UserService(UserDao userDao) {
        this.userDao = userDao;
    }*/

//    @Autowired
//    // 这个是Bean的名字
//    @Qualifier("userDaoForOrcle")
//    @Resource
    @Resource(name = "userDaoForMySQL")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}
  • 执行结果

在这里插入图片描述

一句话总结@Resource注解:

默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个。

12.6 全注解式开发

所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。

  • 配置类代替spring配置文件: com.powernode.spring6.config.Spring6Configuration
package com.powernode.spring6.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.config.Spring6Configuration
 * @date 2022/11/21
 * @since 1.0
 */
@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}

编写测试程序:不再new ClassPathXmlApplicationContext()对象了。

  • 测试程序
@Test
public void testNoXml(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.save();
}
  • 测试结果

在这里插入图片描述

十三、JdbcTemplate

JdbcTemplate是Spring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码。

当然,你也可以不用,可以让Spring集成其它的ORM框架,例如:MyBatis、Hibernate等。

接下来我们简单来学习一下,使用JdbcTemplate完成增删改查。

13.1 环境准备

数据库表:t_user

在这里插入图片描述

IDEA中新建模块:spring6-011-jdbc

在这里插入图片描述

引入相关依赖:

  • 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>

    <groupId>com.powernode</groupId>
    <artifactId>spring6-011-jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring MileStone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--新增的依赖:mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--新增的依赖:spring jdbc,这个依赖中有JdbcTemplate-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

准备实体类:表t_user对应的实体类User。

  • com.powernode.spring6.bean.User
package com.powernode.spring6.bean;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.bean.User
 * @date 2022/11/21
 * @since 1.0
 */
public class User {

    private Integer id;
    private String realName;
    private Integer age;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", realName='" + realName + '\'' +
                ", age=" + age +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public Integer getAge() {
        return age;
    }

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

    public User(Integer id, String realName, Integer age) {
        this.id = id;
        this.realName = realName;
        this.age = age;
    }

    public User() {
    }
}

编写Spring配置文件:

JdbcTemplate是Spring提供好的类,这类的完整类名是:org.springframework.jdbc.core.JdbcTemplate

我们怎么使用这个类呢?new对象就可以了。怎么new对象,Spring最在行了。直接将这个类配置到Spring配置文件中,纳入Bean管理即可。

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

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"></bean>
</beans>

我们来看一下这个JdbcTemplate源码:

在这里插入图片描述

在这里插入图片描述

可以看到JdbcTemplate中有一个DataSource属性,这个属性是数据源,我们都知道连接数据库需要Connection对象,而生成Connection对象是数据源负责的。所以我们需要给JdbcTemplate设置数据源属性。

所有的数据源都是要实现javax.sql.DataSource接口的。这个数据源可以自己写一个,也可以用写好的,比如:阿里巴巴的德鲁伊连接池,c3p0,dbcp等。我们这里自己先手写一个数据源。

  • 自己写的数据源: com.powernode.spring6.jdbc.MyDataSource
package com.powernode.spring6.jdbc;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.jdbc.MyDataSource
 * @date 2022/11/21
 * @since 1.0
 */
public class MyDataSource implements DataSource {

    // 添加4个属性
    private String driver;
    private String url;
    private String username;
    private String password;

    // 提供4个setter方法


    public void setDriver(String driver) {
        this.driver = driver;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    // 重点写怎么获取Connection对象就行,其他方法不用管
    @Override
    public Connection getConnection() throws SQLException {
        try {
            Class.forName(driver);
            Connection conn = DriverManager.getConnection(url, username, password);
            return conn;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

写完数据源,我们需要把这个数据源传递给JdbcTemplate。因为JdbcTemplate中有一个DataSource属性:

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


    <bean id="myDataSource" class="com.powernode.spring6.jdbc.MyDataSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="000000"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="myDataSource"/>
    </bean>
</beans>

到这里环境就准备好了。

13.2 新增

  • 测试程序
package com.powernode.spring6.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author shanglinsong
 * @version 1.0
 * @className com.powernode.spring6.test.JdbcTest
 * @date 2022/11/21
 * @since 1.0
 */
public class JdbcTest {

    @Test
    public void testInsert(){
        // 获取JdbcTemplate对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        // 执行插入操作
        // 注意:insert delete update 的sql语句,都是执行update方法
        String sql = "insert into t_user(id, real_name, age) values(?, ?, ?)";
        int count = jdbcTemplate.update(sql, null, "张三", 30);
        System.out.println("插入记录条数:" + count);
    }
}
  • update方法有两个参数:

    • 第一个参数:要执行的SQL语句。(SQL语句中可能会有占位符 ? )
    • 第二个参数:可变长参数,参数的个数可以是0个,也可以是多个。一般是SQL语句中有几个问号,则对应几个参数。
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.3 修改

  • 测试程序
@Test
public void testUpdate(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行更新操作
    String sql = "update t_user set real_name=?, age=? where id=?";
    int count = jdbcTemplate.update(sql, "张三丰", 55, 1);
    System.out.println("更新记录条数:" + count);
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.4 删除

  • 测试程序
@Test
public void testDelete(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行删除操作
    String sql = "delete from t_user where id=?";
    int count = jdbcTemplate.update(sql, 1);
    System.out.println("删除记录条数:" + count);
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.5 查询一个对象

先insert一条新的数据

在这里插入图片描述

  • 测试程序
@Test
public void testSelectOne(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行查询操作
    String sql = "select id, real_name, age from t_user where id=?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
    System.out.println(user);
}
  • 测试结果

在这里插入图片描述

  • queryForObject方法三个参数:
    • 第一个参数:sql语句
    • 第二个参数:Bean属性值和数据库记录行的映射对象。在构造方法中指定映射的对象类型。
    • 第三个参数:可变长参数,给sql语句的占位符问号传值。

13.6 查询多个对象

  • 测试程序
@Test
public void testSelectAll(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行查询操作
    //        String sql = "select id, real_name, age from t_user";
    String sql = "select * from t_user";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    System.out.println(users);
}
  • 测试结果

在这里插入图片描述

13.7 查询一个值

  • 测试程序
@Test
public void testSelectOneValue(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行查询操作
    String sql = "select count(*) from t_user";
    Integer count = jdbcTemplate.queryForObject(sql, int.class);
    System.out.println("总记录条数:" + count);
}
  • 测试结果

在这里插入图片描述

13.8 批量添加

  • 测试程序
@Test
public void testBatchInsert(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行批量插入操作
    String sql = "insert into t_user values(?, ?, ?)";

    Object[] obj1 = {null, "小花", 20};
    Object[] obj2 = {null, "小明", 21};
    Object[] obj3 = {null, "小刚", 22};
    List<Object[]> list = new ArrayList<>();
    list.add(obj1);
    list.add(obj2);
    list.add(obj3);

    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.9 批量修改

  • 测试程序
@Test
public void testBatchUpdate(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行批量更新操作
    String sql = "update t_user set real_name=?, age=? where id=?";
    Object[] obj1 = {"小花11", 10, 4};
    Object[] obj2 = {"小明22", 12, 5};
    Object[] obj3 = {"小刚33", 9, 6};
    List<Object[]> list = new ArrayList<>();
    list.add(obj1);
    list.add(obj2);
    list.add(obj3);

    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.10 批量删除

  • 测试程序
@Test
public void testBatchDelete(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行批量删除操作
    String sql = "delete from t_user where id=?";
    Object[] obj1 = {4};
    Object[] obj2 = {5};
    Object[] obj3 = {6};
    List<Object[]> list = new ArrayList<>();
    list.add(obj1);
    list.add(obj2);
    list.add(obj3);

    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

13.11 使用回调函数

使用回调函数,可以参与的更加细节:

  • 测试程序
@Test
public void testCallback(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    String sql = "select * from t_user where id=?";

    User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
        @Override
        public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
            User user = null;
            ps.setInt(1, 2);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setRealName(rs.getString("real_name"));
                user.setAge(rs.getInt("age"));
            }
            return user;
        }
    });
    System.out.println(user);
  • 测试结果

在这里插入图片描述

13.12 使用德鲁伊连接池

之前数据源是用我们自己写的。也可以使用别人写好的。例如比较牛的德鲁伊连接池。

第一步:引入德鲁伊连接池的依赖。(毕竟是别人写的)

  • 德鲁伊依赖 pom.xml
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.8</version>
</dependency>
  • spring-druid.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="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
</beans>
  • 测试程序
@Test
public void testDruidDataSource(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-druid.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    String sql = "select * from t_user where id=?";

    User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
        @Override
        public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
            User user = null;
            ps.setInt(1, 2);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setRealName(rs.getString("real_name"));
                user.setAge(rs.getInt("age"));
            }
            return user;
        }
    });
    System.out.println(user);
}
  • 测试结果

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值