Spring的那些开发小技巧(上)
创始人
2024-06-02 19:44:37
0

FactoryBean

提起FactoryBean,就有一道“著名”的面试题“说一说FactoryBean和BeanFactory的区别”。其实这两者除了名字有点像,没有半毛钱关系。。

BeanFactory是Bean的工厂,可以帮我们生成想要的Bean,而FactoryBean就是一种Bean的类型。当往容器中注入class类型为FactoryBean的类型的时候,最终生成的Bean是用过FactoryBean的getObject获取的。

来个FactoryBean的Demo

定义一个UserFactoryBean,实现FactoryBean接口,getObject方法返回一个User对象

public class UserFactoryBean implements FactoryBean {@Overridepublic User getObject() throws Exception {User user = new User();System.out.println("调用 UserFactoryBean 的 getObject 方法生成 Bean:" + user);return user;}@Overridepublic Class getObjectType() {// 这个 FactoryBean 返回的Bean的类型return User.class;}}

测试类:

public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//将 UserFactoryBean 注册到容器中applicationContext.register(UserFactoryBean.class);applicationContext.refresh();System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));}}

结果:

调用 UserFactoryBean 的 getObject 方法生成 Bean:com.sanyou.spring.extension.User@396e2f39
获取到的Bean为com.sanyou.spring.extension.User@396e2f39

从结果可以看出,明明注册到Spring容器的是UserFactoryBean,但是却能从容器中获取到User类型的Bean,User这个Bean就是通过UserFactoryBean的getObject方法返回的。

FactoryBean在开源框架中的使用

1、 在Mybatis中的使用

Mybatis在整合Spring的时候,就是通过FactoryBean来实现的,这也就是为什么在Spring的Bean中可以注入Mybatis的Mapper接口的动态代理对象的原因。

代码如下,省略了不重要的代码。

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {// mapper的接口类型private Class mapperInterface;@Overridepublic T getObject() throws Exception {// 通过SqlSession获取接口的动态搭理对象return getSqlSession().getMapper(this.mapperInterface);}@Overridepublic Class getObjectType() {return this.mapperInterface;}}

getObject方法的实现就是返回通过SqlSession获取到的Mapper接口的动态代理对象。

而@MapperScan注解的作用就是将每个接口对应的MapperFactoryBean注册到Spring容器的。

2、在OpenFeign中的使用

FeignClient接口的动态代理也是通过FactoryBean注入到Spring中的。

class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware {// FeignClient接口类型private Class type;@Overridepublic Object getObject() throws Exception {return getTarget();}@Overridepublic Class getObjectType() {return type;}
}

getObject方法是调用getTarget方法来返回的动态代理。

@EnableFeignClients注解的作用就是将每个接口对应的FeignClientFactoryBean注入到Spring容器的。

一般来说,FactoryBean 比较适合那种复杂Bean的构建,在其他框架整合Spring的时候用的比较多。

@Import注解

@Import注解导入的配置类可以分为三种情况:

第一种:配置类实现了 ImportSelector 接口

public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);@Nullabledefault Predicate getExclusionFilter() {return null;}}

当配置类实现了 ImportSelector 接口的时候,就会调用 selectImports 方法的实现,获取一批类的全限定名,最终这些类就会被注册到Spring容器中。

UserImportSelector实现了ImportSelector,selectImports方法返回User的全限定名,代表吧User这个类注册容器中

public class UserImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println("调用 UserImportSelector 的 selectImports 方法获取一批类限定名");return new String[]{"com.sanyou.spring.extension.User"};}}

测试:

// @Import 注解导入 UserImportSelector
@Import(UserImportSelector.class)
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//将 Application 注册到容器中applicationContext.register(Application.class);applicationContext.refresh();System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));}}

结果:

调用 UserImportSelector 的 selectImports 方法获取一批类限定名
获取到的Bean为com.sanyou.spring.extension.User@282003e1

所以可以看出,的确成功往容器中注入了User这个Bean

第二种:配置类实现了 ImportBeanDefinitionRegistrar 接口

public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}

当配置类实现了 ImportBeanDefinitionRegistrar 接口,你就可以自定义往容器中注册想注入的Bean。这个接口相比与 ImportSelector 接口的主要区别就是,ImportSelector接口是返回一个类,你不能对这个类进行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加属性之类的。

来个demo:

实现ImportBeanDefinitionRegistrar接口

public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {//构建一个 BeanDefinition , Bean的类型为 UserAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)// 设置 User 这个Bean的属性username的值为三友的java日记.addPropertyValue("username", "三友的java日记").getBeanDefinition();System.out.println("往Spring容器中注入User");//把 User 这个Bean的定义注册到容器中registry.registerBeanDefinition("user", beanDefinition);}}

测试:

// 导入 UserImportBeanDefinitionRegistrar
@Import(UserImportBeanDefinitionRegistrar.class)
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//将 Application 注册到容器中applicationContext.register(Application.class);applicationContext.refresh();User user = applicationContext.getBean(User.class);System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());}}

结果:

往Spring容器中注入User
获取到的Bean为com.sanyou.spring.extension.User@6385cb26,属性username值为:三友的java日记

第三种:配置类什么接口都没实现

这种就不演示了,就是一个普普通通的类。

总结

其实不论是什么样的配置类,主要的作用就是往Spring容器中注册Bean,只不过注入的方式不同罢了。

这种方式有什么好处呢?

ImportSelector和ImportBeanDefinitionRegistrar的方法是有入参的,也就是注解的一些属性的封装,所以就可以根据注解的属性的配置,来决定应该返回样的配置类或者是应该往容器中注入什么样的类型的Bean,可以看一下 @EnableAsync 的实现,看看是如何根据@EnableAsync注解的属性来决定往容器中注入什么样的Bean。

@Import的核心作用就是导入配置类,并且还可以根据配合(比如@EnableXXX)使用的注解的属性来决定应该往Spring中注入什么样的Bean。

Bean的生命周期

第一节讲的FactoryBean是一种特殊的Bean的类型,@Import注解是往Spring容器中注册Bean。其实不论是@Import注解,还是@Component、@Bean等注解,又或是xml配置,甚至是demo中的register方法,其实主要都是做了一件事,那就是往Spring容器去注册Bean。

为什么需要去注册Bean?

当然是为了让Spring知道要为我们生成Bean,并且需要按照我的要求来生成Bean,比如说,我要@Autowired一个对象,那么你在创建Bean的过程中,就得给我@Autowired一个对象,这就是一个IOC的过程。所以这就涉及了Bean的创建,销毁的过程,也就是面试常问的Bean的生命周期。

本节来着重看一下,一个Bean在创建的过程中,有哪些常见的操作Spring在Bean的创建过程中给我们完成,并且操作的顺序是什么样的。

话不多说,直接测试,基于结果来分析。

Bean生命周期的回调

先来测试

创建LifeCycle类

创建了一个LifeCycle,实现了 InitializingBean、ApplicationContextAware、DisposableBean接口,加了@PostConstruct、@PreDestroy注解,注入了一个User对象。

public class LifeCycle implements InitializingBean, ApplicationContextAware, DisposableBean {@Autowiredprivate User user;public LifeCycle() {System.out.println("LifeCycle对象被创建了");}/*** 实现的 Aware 回调接口** @param applicationContext* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("Aware接口起作用,setApplicationContext被调用了,此时user=" + user);}@PostConstructpublic void postConstruct() {System.out.println("@PostConstruct注解起作用,postConstruct方法被调用了");}/*** 实现 InitializingBean 接口** @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean接口起作用,afterPropertiesSet方法被调用了");}/*** 通过 {@link Bean#initMethod()}来指定** @throws Exception*/public void initMethod() throws Exception {System.out.println("@Bean#initMethod()起作用,initMethod方法被调用了");}@PreDestroypublic void preDestroy() throws Exception {System.out.println("@PreDestroy注解起作用,preDestroy方法被调用了");}/*** 通过 {@link Bean#destroyMethod()}来指定** @throws Exception*/public void destroyMethod() throws Exception {System.out.println("@Bean#destroyMethod()起作用,destroyMethod方法被调用了");}/*** 实现 DisposableBean 注解** @throws Exception*/@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean接口起作用,destroy方法被调用了");}}
声明LifeCycle

通过@Bean声明了LifeCycle,并且initMethod和destroyMethod属性分别指定到了LifeCycle类的initMethod方法和destroyMethod方法

@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public LifeCycle lifeCycle() {return new LifeCycle();
}
测试
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//将 LifeCycle 注册到容器中applicationContext.register(Application.class);applicationContext.refresh();// 关闭上下文,触发销毁操作applicationContext.close();}@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")public LifeCycle lifeCycle() {return new LifeCycle();}@Beanpublic User user() {return new User();}}

执行结果:

LifeCycle对象被创建了
Aware接口起作用,setApplicationContext被调用了,此时user=com.sanyou.spring.extension.User@57d5872c
@PostConstruct注解起作用,postConstruct方法被调用了
InitializingBean接口起作用,afterPropertiesSet方法被调用了
@Bean#initMethod()起作用,initMethod方法被调用了
@PreDestroy注解起作用,preDestroy方法被调用了
DisposableBean接口起作用,destroy方法被调用了
@Bean#destroyMethod()起作用,destroyMethod方法被调用了

分析结果

通过测试的结果可以看出,Bean在创建和销毁的过程当我们实现了某些接口或者加了某些注解,Spring就会回调我们实现的接口或者执行的方法。

同时,在执行setApplicationContext的时候,能打印出User对象,说明User已经被注入了,说明注入发生在setApplicationContext之前。

这里画张图总结一下Bean创建和销毁过程中调用的顺序。

红色部分发生在Bean的创建过程,灰色部分发生在Bean销毁的过程中,在容器关闭的时候,就会销毁Bean。

这里说一下图中的Aware接口指的是什么。其余的其实没什么好说的,就是按照这种方式配置,Spring会调用对应的方法而已。

Aware接口是指以Aware结尾的一些Spring提供的接口,当你的Bean实现了这些接口的话,在创建过程中会回调对应的set方法,并传入响应的对象。

这里列举几个Aware接口以及它们的作用

有了这些回调,比如说我的Bean想拿到ApplicationContext,不仅可以通过@Autowired注入,还可以通过实现ApplicationContextAware接口拿到。

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...