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;
@SpringBootApplication
public 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 {
-
- 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 大道至简 悟在天成
*/
@Configuration
public 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();
通过注解注入使用:
@Autowired
private AlgorithmService algorithmService;
@RequestMapping("/test")
public void test(){
algorithmService.encrypt();
}