【深入浅出Spring6】第三期——作用域和工厂模式
创始人
2024-01-22 03:20:07

一、Bean 的作用域

  • 作用域以下用 scope 代替,在不同的应用情景下可以使用的参数值下是不同的
  • 我们以普通 Java 工程为例:可选的参数值有两个 singleton、prototype

$ singleton

  • 默认情况下,scope 的属性值就为 singleton,当然我们也可以显式的定义为 singleton
  • 需求:我们想测试以前之前创建的bean对象是单例还是多例的

编写我们待测试的BeanSpringBean.java

package com.powernode.spring6.bean;/*** @author Bonbons* @version 1.0*/
public class SpringBean {public SpringBean(){System.out.println("调用了无参构造方法");}
}

我们之前就知道,Spring创建对象是在初始化上下文的时候进行的,而且调用了无参构造方法,此处我们直接给出无参构造方法,方便更好的说明问题

编写我们的 spring 配置文件 spring-scope.xml




编写测试方法,利用getBean方法多次获取我们的Bean对象

@Testpublic void testBeanScope(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");SpringBean sb = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb);SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb2);SpringBean sb3 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb3);/*** 测试结果,输出的三个bean是一个对象* 原因:*    在我们解析XML配置文件的时候,对象就已经调用无参构造方法创建结束了*    在我们调用getBean的时候得到的就是已经创建好的对象,默认情况下是单例模式的*/}

在这里插入图片描述

  • 通过测试结果我们可以发现,无参构造方法只被调用一次,而且通过getBean方法获取到的对象都是同一个实例
  • 得出结论:默认情况下我们创建的Bean对象的单例的
  • Bean对象的创建是在初始化Spring上下文的时候就完成的。
  • 那么如果我们想通过getBean获得不同的对象,我们应该怎么做呢?

$ prototype

  • 单词的含义为原型、多例,我们可以将scope的属性值定义为 prototype 来解决上面的问题
  • 我们修改一下配置文件的那条bean定义语句,并再次运行测试文件

在这里插入图片描述

  • 我们可以发现,调用了三次无参构造方法,我们获得的是三个不同的Bean的对象

  • 我们在使用默认的作用域时,无论是否调用getBean方法,都会调用我们Bean类的无参构造方法,但是当我们将作用域定义为 prototype 的时候,如果没有调用getBean方法就不会去调用无参构造方法

$ 其他 scope

  • 其实我们Bean 的作用域并不是只有两个,但是有些作用域只有在特定的情景下才会有效

  • 那么Bean的作用域都包括哪些呢?
    在这里插入图片描述

  • 尽管自定义的 scope 很少使用,但是我们还是给出如果来自定义 scope

  • 需求:我们先测试一下,不同线程创建的Bean是单例的还是多列的?

还是利用我们上面的文件,只是在测试方法处创建新的线程进而再获得实例 【scope采用默认值】

 @Testpublic void testThreadScope(){// 获得一个SpringBean的实例ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb1);SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb2);// 创建一个新的线程new Thread(new Runnable() {@Overridepublic void run() {SpringBean sb3 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb3);}}).start();}

经过前面在 singleton 部分的测试我们知道,sb1和sb2一定是同一个对象,那么到底不同线程的bean是单例的还是多例的呢?

在这里插入图片描述

  • 很显然,不同线程获得的Bean仍然是单例的
  • 我们想自定义一个scope,实现不同线程获得的是不同的Bean的实例

(1)我们自定义一个Scope类,实现Scope接口

  • spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可以直接用

(2)将我们自定义的Scope注册到Spring容器中 【此处我们直接就定义到了上面的 spring-scope.xml中】


(3)想要使用我们的这个scope只需在声明bean的时候将scope属性的值设置为我们自定义的scope即可



(4)再次测试我们上面那个不同线程获得的bean对象是否为同一个

@Testpublic void testThreadScope(){// 获得一个SpringBean的实例ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb1);SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb2);// 创建一个新的线程new Thread(new Runnable() {@Overridepublic void run() {SpringBean sb3 = applicationContext.getBean("sb", SpringBean.class);System.out.println(sb3);}}).start();}

在这里插入图片描述
通过结果我们可以看到,已经实现了不同线程通过getBean方法获得的bean的对象不是同一个


二、GoF 工厂模式

  • GoF 的中文含义代表四人组(指的是编写设计模式的四个作者)

  • 设计模式:可以重复利用的一种解决问题的方案,该书中包含 23 种设计模式,当然也存在其他的设计模式

  • GoF 23种设计模式可以分为三大类:

    • 创建型:解决对象创建问题
      • 单例模式
      • 工厂方法模式
      • 抽象工厂模式
      • 建造者模式
      • 原型模式
    • 结构型:一些类或对象组合在一起的经典结构
      • 代理模式
      • 装饰模式
      • 适配器模式
      • 组合模式
      • 享元模式
      • 外观模式
      • 桥接模式
    • 行为型:解决类或对象之间的交互问题
      • 策略模式
      • 模板方法模式
      • 责任链模式
      • 观察者模式
      • 迭代子模式
      • 命名模式
      • 备忘录模式
      • 状态模式
      • 访问者模式
      • 中介者模式
      • 解释器模式
  • 因为Spring容器使用了大量的工厂模式,所以接下来我们对工厂模式展开论述

$ 工厂模式的三种形态

  • 简单工厂模式(静态工厂模式):是工厂方法模式的一种特殊形式
  • 工厂方法模式:GoF 设计模式的一种
  • 抽象工厂模式:GoF 设计模式的一种

$ 简单工厂模式

  • 简单工厂模式的角色有三个:
    • 抽象产品角色
    • 具体产品角色
    • 工厂类角色
  • 需求:我们通过编写对应角色的文件,来演示简单工厂模式究竟是怎样的?

编写抽象产品角色,抽象类 + 抽象方法 Weapon.java

package com.powernode.simple.factory;/*** 抽象产品角色* @author Bonbons* @version 1.0*/
public abstract class Weapon {// 定义一个抽象的攻击方法,具体内容由其子类去实现public abstract void attack();
}

编写具体产品角色,此处我们就给两个 Tank.java、Dagger.java 要继承我们的抽象产品角色并实现抽象方法

package com.powernode.simple.factory;/*** 具体产品角色* @author Bonbons* @version 1.0*/
public class Tank extends Weapon{@Overridepublic void attack() {System.out.println("向我开炮!!!");}
}
package com.powernode.simple.factory;/*** 具体产品角色* @author Bonbons* @version 1.0*/
public class Dagger extends Weapon{@Overridepublic void attack() {System.out.println("偷袭!!!");}
}

编写工厂类角色,WeaponFactory 类提供一个静态方法,根据我们客户端传递的参数,来决定生成哪个具体的产品角色对象

package com.powernode.simple.factory;/*** 工厂类角色* @author Bonbons* @version 1.0*/
public class WeaponFactory {/*** 简单工厂模式我们又称之为静态工厂模式:* 通过一个静态方法,根据我们的参数生成对应的具体产品角色的对象*/public static Weapon get(String weapon){// 通过if条件判断去匹配对应的角色if("TANK".equals(weapon)){return new Tank();}else if("FIGHTER".equals(weapon)){return new Fighter();}else if("DAGGER".equals(weapon)){return new Dagger();}else{throw new RuntimeException("无法识别该类型!!!");}}
}

编写Test测试方法:

package com.powernode.simple.factory;/*** @author Bonbons* @version 1.0*/
public class Test {public static void main(String[] args) {// 如果我们需要坦克Weapon tank = WeaponFactory.get("TANK");tank.attack();// 如果我们需要偷袭Weapon dagger = WeaponFactory.get("DAGGER");dagger.attack();}
}

在这里插入图片描述

  • Spring 中的BeanFactory就采用了简单工厂模式

  • 简单工厂模式的优点:

    • 实现了责任的分离,工厂负责生产,客户端负责消费
    • 客户端程序不必关系对象创建的细节,想要创建哪个对象就传入对应的参数即可
  • 简单工厂模式的缺点:

    • 需要扩展新产品时,需要修改WeaponFactory类,违背了OCP开闭原则
    • 工厂类负责生产所有的产品,一旦出现问题就会导致全盘崩溃

$ 工厂方法模式

  • 工厂方法模式的角色对象有四个:【改变就是让一个具体的工厂去生产一种具体的产品】
    • 抽象产品角色
    • 具体产品角色
    • 抽象工厂角色
    • 具体工厂角色
  • 需求:我们创建一个新的模块还是通过实践演示工厂方法模式的原理是怎样的

创建一个新模块 factory-method 并不需要添加依赖什么的,因为这部分的内容用不到

编写我们的抽象产品角色 >> Weapon

package com.powernode.factory.method;/*** 抽象产品角色* @author Bonbons* @version 1.0*/
abstract public class Weapon {public abstract void attack();
}

编写我们的具体产品角色 >> Gun、Dagger

package com.powernode.factory.method;/*** 具体产品角色* @author Bonbons* @version 1.0*/
public class Gun extends Weapon{@Overridepublic void attack() {System.out.println("开枪射击!!!");}
}
package com.powernode.factory.method;/*** 具体产品角色* @author Bonbons* @version 1.0*/
public class Dagger extends Weapon{@Overridepublic void attack() {System.out.println("给你一刀!!!");}
}

编写我们的抽象工厂 >> WeaponFactory

package com.powernode.factory.method;/*** 抽象工厂角色* @author Bonbons* @version 1.0*/
abstract public class WeaponFactory {// 给出获得工厂对象的get方法,具体获得哪个工厂的对象由实现它的工厂决定public abstract Weapon get();
}

编写我们的具体工厂角色 >> GunFactory、DaggerFactory

package com.powernode.factory.method;/*** 具体工厂角色* @author Bonbons* @version 1.0*/
public class GunFactory extends WeaponFactory{@Overridepublic Weapon get() {return new Gun();}
}
package com.powernode.factory.method;/*** 具体工厂角色* @author Bonbons* @version 1.0*/
public class DaggerFactory extends WeaponFactory{@Overridepublic Weapon get() {return new Dagger();}
}

编写我们的测试方法

package com.powernode.factory.method;/*** @author Bonbons* @version 1.0*/
public class Test {public static void main(String[] args) {// 我们先获得想要的工厂WeaponFactory daggerFactory = new DaggerFactory();// 调用工厂的get方法,就可以获得对应工厂的产品Weapon dagger = daggerFactory.get();// 然后我们调用我们产品的方法dagger.attack();WeaponFactory gunFactory = new GunFactory();Weapon gun = gunFactory.get();gun.attack();}
}

在这里插入图片描述

  • 工厂方法模式的优点:

    • 如果想扩展一个新的产品,只要新增一个产品类,再新增一个该产品对应的工厂即可,解决了简单工厂模式的OCP问题
    • 扩展性高,为用户提供接口,屏蔽了产品的具体实现
  • 工厂方法模式的缺点:

    • 我们添加一个新产品就要添加具体的产品角色和具体的工厂角色,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

$ 抽象工厂模式

  • 如何理解抽象工厂模式?

    • 工厂方法模式对应一个系列,抽象工厂模式就对应多个系列
    • 工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果
    • 抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象
  • 抽象工厂模式的组成:

    • 抽象工厂角色
    • 具体工厂角色
    • 抽象产品角色
    • 具体产品角色
  • 需求:我们写一个案例,包含两个系列——武器系列和水果系列

编写武器产品系列: 一个抽象产品角色、两个具体产品角色

package com.powernode.product;/*** 武器产品族* @author 动力节点* @version 1.0* @className Weapon* @since 1.0**/
public abstract class Weapon {public abstract void attack();
}
package com.powernode.product;/*** 武器产品族中的产品等级1* @author 动力节点* @version 1.0* @className Gun* @since 1.0**/
public class Gun extends Weapon{@Overridepublic void attack() {System.out.println("开枪射击!");}
}
package com.powernode.product;/*** 武器产品族中的产品等级2* @author 动力节点* @version 1.0* @className Dagger* @since 1.0**/
public class Dagger extends Weapon{@Overridepublic void attack() {System.out.println("砍丫的!");}
}

编写水果产品系列: 一个抽象产品角色、两个具体产品角色

package com.powernode.product;/*** 水果产品族* @author 动力节点* @version 1.0* @className Fruit* @since 1.0**/
public abstract class Fruit {/*** 所有果实都有一个成熟周期。*/public abstract void ripeCycle();
}
package com.powernode.product;/*** 水果产品族中的产品等级1* @author 动力节点* @version 1.0* @className Orange* @since 1.0**/
public class Orange extends Fruit{@Overridepublic void ripeCycle() {System.out.println("橘子的成熟周期是10个月");}
}
package com.powernode.product;/*** 水果产品族中的产品等级2* @author 动力节点* @version 1.0* @className Apple* @since 1.0**/
public class Apple extends Fruit{@Overridepublic void ripeCycle() {System.out.println("苹果的成熟周期是8个月");}
}

编写我们的抽象工厂角色

package com.powernode.factory;import com.powernode.product.Fruit;
import com.powernode.product.Weapon;/*** 抽象工厂* @author 动力节点* @version 1.0* @className AbstractFactory* @since 1.0**/
public abstract class AbstractFactory {public abstract Weapon getWeapon(String type);public abstract Fruit getFruit(String type);
}

编写我们的具体工厂角色 >> 采用简单工厂的方式实现的

package com.powernode.factory;import com.powernode.product.Dagger;
import com.powernode.product.Fruit;
import com.powernode.product.Gun;
import com.powernode.product.Weapon;/*** 武器族工厂* @author 动力节点* @version 1.0* @className WeaponFactory* @since 1.0**/
public class WeaponFactory extends AbstractFactory{public Weapon getWeapon(String type){if (type == null || type.trim().length() == 0) {return null;}if ("Gun".equals(type)) {return new Gun();} else if ("Dagger".equals(type)) {return new Dagger();} else {throw new RuntimeException("无法生产该武器");}}@Overridepublic Fruit getFruit(String type) {return null;}
}
package com.powernode.factory;import com.powernode.product.*;/*** 水果族工厂* @author 动力节点* @version 1.0* @className FruitFactory* @since 1.0**/
public class FruitFactory extends AbstractFactory{@Overridepublic Weapon getWeapon(String type) {return null;}public Fruit getFruit(String type){if (type == null || type.trim().length() == 0) {return null;}if ("Orange".equals(type)) {return new Orange();} else if ("Apple".equals(type)) {return new Apple();} else {throw new RuntimeException("我家果园不产这种水果");}}
}

编写我们的客户端测试程序:

package com.powernode.client;import com.powernode.factory.AbstractFactory;
import com.powernode.factory.FruitFactory;
import com.powernode.factory.WeaponFactory;
import com.powernode.product.Fruit;
import com.powernode.product.Weapon;/*** @author 动力节点* @version 1.0* @className Client* @since 1.0**/
public class Client {public static void main(String[] args) {// 客户端调用方法时只面向AbstractFactory调用方法。AbstractFactory factory = new WeaponFactory(); // 注意:这里的new WeaponFactory()可以采用 简单工厂模式 进行隐藏。Weapon gun = factory.getWeapon("Gun");Weapon dagger = factory.getWeapon("Dagger");gun.attack();dagger.attack();AbstractFactory factory1 = new FruitFactory(); // 注意:这里的new FruitFactory()可以采用 简单工厂模式 进行隐藏。Fruit orange = factory1.getFruit("Orange");Fruit apple = factory1.getFruit("Apple");orange.ripeCycle();apple.ripeCycle();}
}

抽象工厂模式的优缺点:

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

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...