SpringBoot/Spring扩展点系列之FactoryBean让你不在懵逼 - 第435篇

导读

当Bean的创建过程比较复杂的话,通过FactoryBean来实现实现Bean的复杂创建过程,这个时候FactoryBean的话,可以让使用者使用起来很简单无需关心Bean的创建过程,听起来是不是很像工厂设计模型。

一、FactoryBean概述

1.1是什么?

FactoryBean就是一个工厂Bean,相当于将工厂类放到了Spring中管理、当获取此Bean的时候返回的是此工厂生成的Bean

FactoryBean就是 生产 Bean 的 Bean  它的目的就是创建一个对象

解读:

(1)FactoryBean本身是就是一个Spring Bean。

(2)FactoryBean的目的是用来创建对象的(可能返回的是不是同一个bean,可以理解为简单工厂)

1.2 BeanFactory与FactoryBean的区别

BeanFactory是个Factory,也就是IOC容器或对象工厂,而FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。

1.3为什么会有FactoryBean?

一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean。至于为什么会有FactoryBean?原因有两个:

(1)在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。

(2)由于第三方库不能直接注册到spring容器,于是可以实现org.springframework.bean.factory.FactoryBean接口,然后给出自己对象的实例化代码即可。

1.4FactoryBean接口方法

看下FactoryBean的接口方法:

共有3个方法需要实现:

(1)getObject():从工厂中获取bean

(2)getObjectType():获取Bean工厂创建的对象的类型,该方法返回的类型是在ioc容器中getbean所匹配的类型。

(3)isSingleton():Bean工厂创建的对象是否是单例模式。

1.5使用场景

(1)FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象:AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean。

所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。

在spring中,Spring容器内部许多地方了使用FactoryBean。下面是一些比较常见的FactoryBean实现:

-SqlMapClientFactoryBean

-ProxyFactoryBean

-TransactionProxyFactoryBean

(2)Mybatis中的SqlSessionFactoryBean。阿里开源的分布式服务框架 Dubbo中的Consumer 也使用到了FactoryBean

(3)Hibernate中的SessionFactoryBean。

二、FactoryBean的基本使用

接下来看个小栗子,来对于FactoryBean有一个基本的认知。

先看下基本的步骤:

(1)创建一个Spring Boot项目

(2)创建一个普通的实体类

(3)构建一个FactoryBean

(4)测试例子

2.1创建一个Spring Boot项目

使用idea构建一个Spring Boot项目,取名为spring-boot-factorybean-example,添加spring-web依赖:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.7.0</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.kfit</groupId>    <artifactId>spring-boot-factorybean-example</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>spring-boot-factorybean-example</name>    <description>Demo project for Spring Boot</description>    <properties>        <java.version>1.8</java.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

使用插件会自动生成上面的pom.xml文件。

2.2创建一个普通的实体类

构建一个普通的java类,无需添加任何的Spring注解:

package com.kfit.demo.bean;/** * Computer * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */public class Computer {    private String cpu;//cpu    private String disk; //硬盘    private String memory; //内存    private String display;// 显示器    private String mainboard;//主板    public Computer(){        System.out.println("Computer构造方法");    }    public String getCpu() {        return cpu;    }    public void setCpu(String cpu) {        this.cpu = cpu;    }    public String getDisk() {        return disk;    }    public void setDisk(String disk) {        this.disk = disk;    }    public String getMemory() {        return memory;    }    public void setMemory(String memory) {        this.memory = memory;    }    public String getDisplay() {        return display;    }    public void setDisplay(String display) {        this.display = display;    }    public String getMainboard() {        return mainboard;    }    public void setMainboard(String mainboard) {        this.mainboard = mainboard;    }    @Override    public String toString() {        return "Computer{" +                "cpu='" + cpu + '\'' +                ", disk='" + disk + '\'' +                ", memory='" + memory + '\'' +                ", display='" + display + '\'' +                ", mainboard='" + mainboard + '\'' +                '}';    }}

2.3构建一个FactoryBean

对于FactoryBean的使用很简单,只需要实现接口ComputerFactoryBean:

package com.kfit.config;import com.kfit.demo.bean.Computer;import org.springframework.beans.factory.FactoryBean;import org.springframework.stereotype.Component;/** * ComputerFactoryBean * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */@Component("computer")public class ComputerFactoryBean implements FactoryBean<Computer> {    @Override    public Computer getObject() throws Exception {        Computer computer = new Computer();        //通用的复杂的构建过程,可以放在定义,以下只是举例说明.        computer.setMainboard("X570主板 ");        computer.setCpu("2核4G");        computer.setDisk("1T");        return computer;    }    @Override    public Class<?> getObjectType() {        return Computer.class;    }    @Override    public boolean isSingleton() {        return true;    }}

2.4测试例子

最后来测试一下:

package com.kfit;import com.kfit.config.ComputerFactoryBean;import com.kfit.demo.bean.Computer;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplicationpublic class SpringBootFactorybeanExampleApplication {    public static void main(String[] args) {        SpringApplication springApplication = new SpringApplication(SpringBootFactorybeanExampleApplication.class);        ConfigurableApplicationContext ctx  = springApplication.run(args);        Computer computer = ctx.getBean(Computer.class);        System.out.println(computer);        computer = ctx.getBean(Computer.class);        System.out.println(computer);        ComputerFactoryBean computerFactoryBean = ctx.getBean("&computer",ComputerFactoryBean.class);        System.out.println(computerFactoryBean);    }}





看下执行结果:

说明:

(1)如果不加&返回的是实例,加了返回的是工厂bean本身。

(2)如果不执行获取实例的方法,也就是不执行ctx.getBean(Computer.class)获取实例方法,是不会构建Computer实例的,也就不会执行构造方法的。获取工厂Bean的话,也是不会实例的创建。

来看下通过beanName获取FactoryBean创建的对象:

(1)移除name变量的&前缀,用一个beanName变量接收(这次没有&前缀,name和beanName是一样的)然后从容器的缓存中取(FactoryBean本身),取到后走getObjectForBeanInstance,该方法需要将从容器中拿到的FactoryBean本身、原始参数name和beanName都要传过去。

(2)判断name是不是以&为前缀,不是的话,创建一个Object对象,用来接收FactoryBean创建出来的Bean对象。

(3)从工厂的factoryBeanObjectCached(FactoryBean创建Bean的缓存)缓存中取,有的话直接返回(第一次获取肯定没有),没有通过FactoryBean#getObject方法进行创建流程。

(4)判断该FactoryBean#isSingleton是单例还是原型对象。然后通过FactoryBean#getObject创建一个Bean出来,如果是单例对象,放入factoryBeanObjectCached缓存中,如果是原型,不存缓存中。最后直接返回。

三、FactoryBean的加密方式例子

最后来看一个小小的案例,需求是这样子的:一般算法框架都会有多重算法的实现,比如MD5、AES。

常规的做法就是两个算法,MD5algorithm和AESalgorithm。然后if else 根据类型来进行判断是调用哪一个algorithm的encrypt ()方法,这里我们使用FactoryBean 来实现一个。

接下来在上面的代码基础上进行往下编码。

3.1 定义算法服务的接口

定义MD5和AES算法的接口:

package com.kfit.demo.service;/** * 定义算法服务的方法 * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */public interface AlgorithmService {
  1. public void encrypt();
}

3.2 MD5和AES算法的具体实现

MD5的算法的具体实现:

package com.kfit.demo.service.impl;import com.kfit.demo.service.AlgorithmService;/** * MD5算法的实现 * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */public class MD5algorithmServiceImpl implements AlgorithmService {    @Override    public void encrypt() {        System.out.println("MD5algorithmServiceImpl.encrypt");    }}

AES算法的具体实现:

package com.kfit.demo.service.impl;import com.kfit.demo.service.AlgorithmService;/** * AES算法的具体实现 * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */public class AESalgorithmServiceImpl implements AlgorithmService {    @Override    public void encrypt() {        System.out.println("AESalgorithmServiceImpl.encrypt");    }}

3.3算法的FactoryBean

最后就是通过FactoryBean来注入具体的算法实现了:

package com.kfit.config;import com.kfit.demo.service.AlgorithmService;import com.kfit.demo.service.impl.AESalgorithmServiceImpl;import com.kfit.demo.service.impl.MD5algorithmServiceImpl;import org.springframework.beans.factory.FactoryBean;/** * PayFactoryBean * (默认是单例实例,所以不重写isSingleton()方法了) * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */public class AlgorithmFactoryBean implements FactoryBean<AlgorithmService>{    //算法类型:MD5 | AES    private String algorithmType;    @Override    public AlgorithmService getObject() throws Exception {        if("MD5".equals(algorithmType)){            return new  MD5algorithmServiceImpl();        }        if("AES".equals(algorithmType)){            return new AESalgorithmServiceImpl();        }        return null;    }    @Override    public Class<?> getObjectType() {        return AlgorithmService.class;    }    public String getAlgorithmType() {        return algorithmType;    }    public void setAlgorithmType(String algorithmType) {        this.algorithmType = algorithmType;    }}

3.4算法的FactoryBean的配置类

最后就是算法的FactoryBean的配置,也就是通过这个配置类来指定是使用的哪一个算法:

package com.kfit.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * 算法配置类 * * @author 悟纤「公众号SpringBoot」 * @date 2022-07-18 * @slogan 大道至简 悟在天成 */@Configurationpublic class AlgorithmConfig {    @Bean    public AlgorithmFactoryBean algorithmFactoryBean(){        AlgorithmFactoryBean algorithmFactoryBean = new AlgorithmFactoryBean();        algorithmFactoryBean.setAlgorithmType("MD5");        return algorithmFactoryBean;    }}

3.5算法测试

来测试代码:

AlgorithmService algorithmService = ctx.getBean(AlgorithmService.class);algorithmService.encrypt();

通过注解注入使用:

@Autowiredprivate AlgorithmService algorithmService;@RequestMapping("/test")public  void test(){    algorithmService.encrypt();}


  1. 购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287