在上一篇文章《走进Spring框架系列—IOC应用》中,我们介绍了Spring IOC的基础应用,经常使用Spring框架的读者应该对上一章的内容能够驾轻就熟,本章将会讲解IOC的进阶应用,本章涉及到的应用可能作为一个普通开发者永远都用不到,因为它们几乎大量被应用在框架开发中,但我可以给出两个理由为什么要学习本章节内容:
其实我一直觉得问“FactoryBean与BeanFactory的区别”这个问题的人是在玩文字游戏,FactoryBean和BeanFactory除了名字上单词颠倒了,容易让不熟悉它们的人有点迷惑,但实际上它们真没多大的关系。
从字面上,其实就可以看出,FactoryBean是一个Bean,而BeanFactory是一个Factory。前面的文章已经讲到过BeanFactory是一个生产Bean的工厂类,它是Spring框架中完成IOC功能的核心类之一,当然,这是从广义上讲,要从狭义上讲,BeanFactory是一个接口,而Spring框架中的默认实现类叫DefaultListableBeanFactory:
/*
*它的顶级接口就是BeanFactory
*/
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable
由于还没讲到源码,所以这里没法讲到这个类究竟能做什么,这里只需要知道它保存了Bean的元数据并且能生成Bean,是一个Bean工厂就可以了。
那么,什么是FactoryBean呢,它也是Spring框架提供的一个接口:
public interface FactoryBean {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class getObjectType();
default boolean isSingleton() {
return true;
}
}
它能干什么,我们直接上代码演示,先写一个普通类:
/**
* @Author: Richard Lv
* @Date: 2021/2/23 17:59
* @Description:
*/
public class Richard {
public void selfIntroduction(){
System.out.println("my name is richard");
}
}
注意,这个类我没有加上@Component注解,讲道理,它不会被Spring容器管理。
接着我们写一个类实现FactoryBean接口:
/**
* @Author: Richard Lv
* @Date: 2021/2/23 17:57
* @Description: 自定义的FactoryBean
*/
@Component
public class MyFactoryBean implements FactoryBean {
/**
* @return
* 返回一个对象实例
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
return new Richard();
}
/**
*
* @return
* 返回对象实例的Class类型
*/
@Override
public Class getObjectType() {
return Richard.class;
}
/**
* 细心看过FactoryBean接口的读者就知道,这个方法可以不实现
* 默认值就是true
* @return
* 定义该实例在Spring容器中是否单例
*/
@Override
public boolean isSingleton() {
return true;
}
}
编辑主类,首先,Richard类我没有加@Component注解,肯定从容器中是拿不到的,这里还是证实一下:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext beanFactory=new AnnotationConfigApplicationContext(SpringConfig.class);
Richard richard= (Richard ) beanFactory.getBean("richard");
}
}
运行报错,容器中没有叫“richard”的bean:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'richard' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:816)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1288)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1109)
at com.richard.Test.main(Test.java:19)
修改主类,这次我们拿自定义的MyFactoryBean:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext beanFactory=new AnnotationConfigApplicationContext(SpringConfig.class);
MyFactoryBean myFactoryBean = (MyFactoryBean) beanFactory.getBean("myFactoryBean");
}
}
运行发现,又报错了:
Exception in thread "main" java.lang.ClassCastException: com.richard.user.Richard cannot be cast to com.richard.user.MyFactoryBean
at com.richard.Test.main(Test.java:18)
这个类型转换错误相信Java程序员都不会陌生,它说无法把Richard转换成MyFactoryBean,可我取的就是MyFactoryBean啊,相信仔细读了我的MyFactoryBean类里面的代码的读者已经猜到答案了,也推想出FactoryBean是干什么的了。
没错,FactoryBean是一个特殊的Bean,当我取一个FactoryBean时,它返回的不是FactoryBean本身,而是返回了我重写的getObject()方法中返回的对象,这就是FactoryBean的作用。
知道它的作用还不够,你难道就没有疑问吗,是不是内心在想:“这个类神经病啊,我需要一个类,直接加个@Component交给Spring容器就行,为什么要搞个FactoryBean这么麻烦”。有这个疑问说明你在思考了,那么接下来就来介绍这个类用的比较多的两个地方让大家体会它的作用:
长期做CRUD,用过mybatis-spring的程序员肯定对这个类不陌生,如果你没用过也没关系,只要用过myBatis或者做过CRUD就能看懂。我们做数据库操作时都需要打开一个与数据库的会话连接,称作session,在使用myBatis时,我们需要得到一个SqlSessionFactory,这是一个接口,默认实现类是DefaultSqlSessionFactory:
public class DefaultSqlSessionFactory implements org.apache.ibatis.session.SqlSessionFactory{
private final org.apache.ibatis.session.Configuration configuration;
public DefaultSqlSessionFactory(org.apache.ibatis.session.Configuration configuration)
public org.apache.ibatis.session.SqlSession openSession()
......
}
调用它的openSession()方法,该方法就会返回一个SqlSession对象,使用这个对象就能进行数据库的select、insert、update、commit等等操作,那么在我们的Spring项目中我们需要把DefaultSqlSessionFactory这个对象交给Spring容器来管理方便我们使用,熟悉Spring IOC的或者看过我前面文章的读者应该知道该怎么做了,它是一个第三方类,我们不能直接在别人源码上加@Component,因此我们可以在XML中配置一个
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan("com.richard")
public class SpringConfig {
@Bean
public DefaultSqlSessionFactory sqlSessionFactory(){
return new DefaultSqlSessionFactory();
}
}
这就完了?当然不行,上面展示的DefaultSqlSessionFactory源码片段也可以看到,DefaultSqlSessionFactory依赖了一个Configuration类,否则没法正常工作,怎么办呢,那还不简单,再配置一个Configuration的bean给它不就行了:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan("com.richard")
public class SpringConfig {
@Bean
public org.apache.ibatis.session.Configuration configuration(){
return new org.apache.ibatis.session.Configuration();
}
@Autowired
@Bean
public DefaultSqlSessionFactory sqlSessionFactory(org.apache.ibatis.session.Configuration configuration){
return new DefaultSqlSessionFactory(configuration);
}
}
这就完了?no way boy,我们点开这个Configuration类就知道了:
public class Configuration {
protected org.apache.ibatis.mapping.Environment environment;
protected java.lang.Class logImpl;
protected java.lang.Class vfsImpl;
protected org.apache.ibatis.session.LocalCacheScope localCacheScope;
protected org.apache.ibatis.type.JdbcType jdbcTypeForNull;
protected java.util.Set lazyLoadTriggerMethods;
protected org.apache.ibatis.session.ExecutorType defaultExecutorType;
protected org.apache.ibatis.session.AutoMappingBehavior autoMappingBehavior;
protected org.apache.ibatis.session.AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected java.util.Properties variables;
protected org.apache.ibatis.reflection.ReflectorFactory reflectorFactory;
protected org.apache.ibatis.reflection.factory.ObjectFactory objectFactory;
protected org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory objectWrapperFactory;
protected org.apache.ibatis.executor.loader.ProxyFactory proxyFactory;
protected java.lang.Class configurationFactory;
protected final org.apache.ibatis.binding.MapperRegistry mapperRegistry;
protected final org.apache.ibatis.plugin.InterceptorChain interceptorChain;
protected final org.apache.ibatis.type.TypeHandlerRegistry typeHandlerRegistry;
protected final org.apache.ibatis.type.TypeAliasRegistry typeAliasRegistry;
protected final org.apache.ibatis.scripting.LanguageDriverRegistry languageRegistry;
public Configuration(org.apache.ibatis.mapping.Environment environment) {...}
......
}
上面的Configuration类是我去掉一些基本类型和集合成员变量后截取的部分代码,现在,你是不是已经意识到了问题的严重性,我们为了拿到一个DefaultSqlSessionFactory究竟还要配置多少东西,当然,如果你有耐心,你可以去把这些依赖一一配置清楚,但这样的做法,实在太反人类了,myBatis的开发人员们当然也知道这种操作是程序员无法接受的,所以,在myBatis和Spring整合框架myBatis-spring中,他们为我们提供了这样一个类,就是上面提到的主角:SqlSessionFactoryBean,我们来看看它的部分源码:
public class SqlSessionFactoryBean
implements FactoryBean, InitializingBean, ApplicationListener {
......
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* {@inheritDoc}
*/
@Override
public Class getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
......
}
有没有一种亲切感?这个SqlSessionFactoryBean就是一个实现了FactoryBean接口的类,并在getObject()方法中返回了一个组装好的SqlSessionFactory,其中getObject()方法里的afterPropertiesSet()方法就是在为sqlSessionFactory配置各种依赖和初始值,感兴趣的读者可以自行去github找到源码阅读它的一系列初始化过程,这里就不多作展示了,于是,在真实开发中我们要在Spring项目中使用SqlSessionFactory一般是这样写的:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan("com.richard")
public class SpringConfig {
@Bean
public DruidDataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true");
ds.setUsername("root");
ds.setPassword("123456");
ds.setInitialSize(5);
return ds;
}
@Bean
@Autowired
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean;
}
}
它也是Spring框架提供的一个接口,同时也是Spring框架对外提供的扩展点之一,什么是扩展点?我们平时用Spring框架时,几乎就是把它当成一个黑匣子来用,对于里面整个工厂如何构建,如何扫描类,如何实例化bean,我们都不关心也不参与,但事实上Spring框架为我们提供了很多扩展点,你也可以狭义理解为就是一个个接口,我们实现这些接口重写其中的方法就可以参与到bean的注入,实例化甚至工厂构建等等,首先来看看ImportSelector接口,它是众多扩展点里我认为最简单的一个:
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate getExclusionFilter() {
return null;
}
}
它只有两个方法,第二个有默认实现的getExclusionFilter()方法一般用的比较少,看注释和方法名其实也知道它是用来过滤哪些类不需要被扫描到Spring容器中,暂且不去管它。第一个方法则是动态将我们给定的类添加到Spring容器中,老样子,先上代码再细讲,新增一个为了演示功能的空类:
/**
* @Author: Richard Lv
* @Date: 2021/2/25 16:10
* @Description:
*/
public class Dance { }
写一个实现ImportSelector的类:
/**
* @Author: Richard Lv
* @Date: 2021/2/25 16:14
* @Description:
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{Dance.class.getName()};
}
}
在配置类中import我自定义的ImportSelector类:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan(value = "com.richard")
@Import({MyImportSelector.class})
public class SpringConfig {
}
运行发现Dance类被放入了Spring容器:
Dance类被放入了Spring容器
此时你可能又会想,我不能直接在Dance类上加@Component吗,搞这么麻烦做什么,如果像我Demo中这样写,当然是画蛇添足,可如果改成下面这样呢?
/**
* @Author: Richard Lv
* @Date: 2021/2/25 16:14
* @Description:
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (你的条件) return new String[]{Dance.class.getName()};
else return new String[0];
}
}
是不是一下就get到了"动态"两个字的含义,请注意,这个方法还有一个参数我没用到,annotationMetadata可以获取注解元数据,所以,ImportSelector的常规用法是这样的,先定义一个注解,只有一个值value,默认ture:
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableDance {
boolean value() default true;
}
在配置类上加上这个注解:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@EnableDance
@ComponentScan(value = "com.richard")
@Import({MyImportSelector.class})
public class SpringConfig {
}
修改自定义的ImportSelector类:
/**
* @Author: Richard Lv
* @Date: 2021/2/25 16:14
* @Description:
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//检查@Import注解的类上有没有@EnableDance注解
if(annotationMetadata.hasAnnotation(EnableDance.class.getName())){
//取出@EnableDance所有值
Map attributes = annotationMetadata.getAnnotationAttributes(EnableDance.class.getName());
//取出value属性的值
assert attributes != null;
boolean isImport = (Boolean) attributes.get("value");
if (isImport) return new String[]{Dance.class.getName()};
}
return new String[0];
}
}
运行发现Dance类加入了Spring容器,把注解值改为false或者不写这个Enable注解就不会被加入,有没有感觉到一丝丝熟悉?如果没有,我们还可以再改改,我们知道,注解是可以嵌套的,所以,一般我们会把@Import放入@EnableXXX里包装起来,就像这样:
@Retention(RetentionPolicy.RUNTIME)
@Import({MyImportSelector.class})
public @interface EnableDance {
boolean value() default true;
}
这样,配置类里就只有一个Enable注解了:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@EnableDance
@ComponentScan(value = "com.richard")
public class SpringConfig {
}
这下是不是明白了,我们在Spring项目中通过一个注解开启/关闭某个功能就是这么做的,用过springboot的读者肯定会倍感亲切,因为里面有大量的@EnableXXX的注解,比如开启自动配置功能的@EnableAutoConfiguration注解,不信我们看它的源码:
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
@java.lang.annotation.Inherited
@org.springframework.boot.autoconfigure.AutoConfigurationPackage
@org.springframework.context.annotation.Import({org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
java.lang.String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
java.lang.Class[] exclude() default {};
java.lang.String[] excludeName() default {};
}
再看Import里的AutoConfigurationImportSelector类源码:
//截取部分,它实现的接口太多了
public class AutoConfigurationImportSelector implements org.springframework.context.annotation.DeferredImportSelector
再接着看DeferredImportSelector源码:
public interface DeferredImportSelector extends org.springframework.context.annotation.ImportSelector {
......
}
有没有一种豁然开朗的感觉,其实springboot本身就是对spring框架的二次封装,把一些东西藏得更深了,然后再整合了Web和其他常用的一些功能,学习本系列spring文章后,你就会感觉springboot也不再神秘。
BeanFactoryPostProcessor和BeanPostProcessor都是spring框架提供的另外两个扩展点,前者参与了beanFactory的构建,后者参与了bean的实例化过程,前者一般来说用的比较少,我们很少去干涉beanFactory的构建,而后者就用的比较多了,BeanFactoryPostProcessor会放到源码讲解中进行细讲,本章只讲BeanPostProcessor的简单应用,老规矩,先看源码定义:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
该接口有两个方法,看方法名大概能猜到其意,postProcessBeforeInitialization()方法是在bean初始化之前调用,
postProcessAfterInitialization()方法是在bean初始化之后调用。
在Spring容器内部就有很多内置的BeanPostProcessor,其中一个就是处理@Autowired注解的AutowiredAnnotationBeanPostProcessor。
有一点需要注意,在spring容器中,实例化和初始化是两个过程,我们常说的实例化是通过构造方法实例出真实对象,可以是new也可以是反射Class.newInstance(),而初始化是属性填充的过程,也就是注入,它必须是在实例化之后,这个很容易理解,实例对象都没有,怎么填充属性呢。
另外,还有两个方法也会在初始化过程中调用,一个是配置一个init-method,另一个是实现InitializingBean接口重写的afterPropertiesSet()方法,这两个方法我个人认为是用的很少的,但鉴于很多人喜欢把它们放在一起比较,这里我也把它们加入一起讲好了。
改造我的Richard类:
/**
* @Author: Richard Lv
* @Date: 2021/2/23 17:59
* @Description:
*/
public class Richard implements InitializingBean {
//构造方法
public Richard(){
System.out.println("richard 已经实例化");
}
//这个是xml中配置的init-method
public void init(){
System.out.println("richard 自定义初始化方法被调用");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("richard afterPropertiesSet 方法被调用");
}
}
因为init-method只能在xml的
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan(value = "com.richard")
@ImportResource("classpath:spring.xml")
public class SpringConfig { }
修改xml:
新增一个MyBeanPostProcessor类实现BeanPostProcessor接口,这里补充一句,所有的bean在初始化过程中都会被调用BeanPostProcessor的方法,所以我这里加入了筛选条件,只让Richard类打印对应内容:
/**
* @Author: Richard Lv
* @Date: 2021/2/26 10:10
* @Description: spring容器中有很多BeanPostProcessor,并放入一个集合中
* 所有bean的初始化过程中,spring容器都会遍历这个集合,
* 然后调用集合中所有的BeanPostProcessor的这两个方法
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//加一个筛选条件只让我的Richard类打印这段内容,如果不加这个条件,所有类都会打印这段内容
if (beanName.equals("richard")) System.out.println(beanName+"postProcessBeforeInitialization方法被调用");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("richard")) System.out.println(beanName+"postProcessAfterInitialization方法被调用");
return bean;
}
}
运行主类结果:
richard 已经实例化
richardpostProcessBeforeInitialization方法被调用
richard afterPropertiesSet 方法被调用
richard 自定义初始化方法被调用
richardpostProcessAfterInitialization方法被调用
为了证明它们的调用顺序,这里贴出部分源码,首先,在bean的创建过程中会调用一个doCreateBean()方法,删除了大量源码只留下部分与此处讲解有关的代码:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
......
if (instanceWrapper == null) {
//这个方法就是bean的实例化
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//从包装类中获得实例化后的bean,此时的bean所有属性都还是空的
Object bean = instanceWrapper.getWrappedInstance();
......
// Initialize the bean instance.
Object exposedObject = bean;
try {
//这个方法就完成了bean的初始化,包括所有的属性填充,也就是注入过程。
//这里面会执行一些内置的特殊BeanPostProcessor,比如上面提到的处理@Autowired注解的AutowiredAnnotationBeanPostProcessor
populateBean(beanName, mbd, instanceWrapper);
//这个方法里会执行普通的BeanPostProcessor和上面那几个方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
......
}
.....
return exposedObject;
}
接着来看initializeBean()方法:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
//看方法名就知道,这里面执行的就是BeanPostProcessors的postProcessBeforeInitialization()方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//这里面就是init-method和afterPropertiesSet()的调用
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
......
}
if (mbd == null || !mbd.isSynthetic()) {
//看方法名就知道,这里面执行的就是BeanPostProcessors的postProcessAfterInitialization()方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
最后看invokeInitMethods()方法,这个方法没什么多余的东西,所有源码都在,关键地方我写了注释:
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
//判断我的bean是不是实现了InitializingBean接口
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
//如果是,就会进这个大的if条件
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction
通过源码,我们可以看到它们的执行顺序,spring框架搞这么多初始化方法接口其实也是为了我们在开发中应对不同的应用和项目环境。
好了,讲了这么多,还没有讲到BeanPostProcessor的实际应用场景,其实看方法标签也很容易想到它的应用场景,不管是postProcessBeforeInitialization()还是postProcessAfterInitialization()方法,都提供了beanName和bean对象,那么我们很容易去拦截一个想要的bean,然后对它进行任何改造,这里简单举两个例子好了,第一个就是在上一篇文章中提到的@PostConstruct注解,关于它的使用这里就不再演示了,忘了的读者朋友可以去我上一篇文章中看一下,这个注解的原理就是一个实现了BeanPostProcessor接口的类来处理的。第二个就是AOP,没错,AOP的原理之一就是用到了BeanPostProcessor,这个也不能猜吧,熟悉AOP的读者应该知道它的工作机制就是动态代理,不管是JDK动态代理还是CGLib,总之被AOP切入逻辑的类已经不是原来的类了,而是一个代理类,那么它是在哪里把我们原来的类给替换的呢,就是BeanPostProcessor的postProcessAfterInitialization()方法了,这里大致给个伪代码表示一下过程:
/**
* @Author: Richard Lv
* @Date: 2021/2/26 10:10
*/
@Component
public class AOPBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//假如Richard类中的方法需要AOP织入逻辑
if (beanName.equals("richard")) {
Object beanProxy=createAOPProxy(bean);
//spring容器在执行这个方法回调时,就会拿到一个代理类,原来的bean已经彻底被代替了
return beanProxy;
}
return bean;
}
}
关于AOP更详细的实现过程,我会在后面的综合实战来讲。
ImportBeanDefinitionRegistrar同样是spring框架的扩展点之一,它与前面讲过的ImportSelector功能其实很类似,都是可以动态向spring容器中添加类,但功能比ImportSelector更强大,还是先看源码的接口定义再接着细讲:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
可以看到该接口有两个重载的方法,第一个方法比第二个多了一个BeanNameGenerator类型的参数,默认实现是直接调用第二个方法,并没有把BeanNameGenerator传过去,可见重点的参数是前两个,BeanNameGenerator是一个名字生成器,确实不重要,这里也不讲它。
第一个参数类型是AnnotationMetadata,这个相信大家不陌生了吧,在ImportSelector里就出现过,它可以获取import它的类的注解信息;第二个参数BeanDefinitionRegistry就很强大了,它是整个spring容器的bean注册器,也就是说任何bean要放到spring容器中,都要通过它来注册,这也就可以看出ImportBeanDefinitionRegistrar与ImportSelector不一样的地方了,ImportSelector只能动态添加一个已经存在的类,而ImportBeanDefinitionRegistrar有BeanDefinitionRegistry这个东西,就可以添加一个运行期间动态生成的类,ImportBeanDefinitionRegistrar有一个非常经典的应用场景:@MapperScan
用过mybatis-spring的肯定不会陌生吧,这里大致给没用过的读者大致描述一下,在spring中使用mybatis可以在配置类中开启mapper扫描功能:
/**
* @Author: Richard Lv
* @Date: 2021/2/2 18:27
* @Description: spring 配置类
*/
@Configuration
@ComponentScan("com.richard")
//扫描com.richard.mapper包下所有的mapper接口
@MapperScan("com.richard.mapper")
public class SpringConfig {
}
在com.richard.mapper包下写一个mapper作为dao层:
/**
* @Author Richard Lv
* @Date 2021/2/28 20:29
* @Description 用户查询mapper
*/
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
注意,这是一个接口,没有实现类,就可以在service中直接注入使用:
/**
* @Author Richard Lv
* @Date 2021/2/28 20:29
* @Description 简单的用户CRUD逻辑处理层
*/
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User getUser(String userId) {
return userMapper.getUser(userId);
}
}
controller层和运行结果就不演示了,这就是mybatis在spring整合版中的基础使用,不管你是否用过,有没有觉得它很神奇,它居然能把一个没有实现类的接口注入进来直接使用,其实稍微有经验的就知道,它注入进来的肯定不可能是接口,必然是一个真实类,而这个真实类就是运行期间才对UserMapper接口生成的代理类,然后使用ImportBeanDefinitionRegistrar放入spring容器的。
用源码验证,很明显,就是@MapperScan注解起的作用,点开它:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan
立马就看到熟悉的东西,@Import({MapperScannerRegistrar.class}),所以,点开MapperScannerRegistrar这个类:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
可以看到,@MapperScan底层确实是使用ImportBeanDefinitionRegistrar来实现的,具体实现手段会在后续实战演练中进行模拟。
本章节主要介绍了Spring框架中几个比较进阶的应用,其中FactoryBean是一个特殊Bean,把它交给Spring容器管理后,当我们获取它时它会返回getObject方法中定义的对象,而ImportSelector、BeanPostProcessor、ImportBeanDefinitionRegistrar都是Spring框架提供的扩展接口,我们可以通过实现它们从而参与整个Spring容器中的bean的构建。
如果你已经对这些接口熟悉了,下一章笔者将会带大家使用上面介绍的这些内容进行实战,更具体得模拟实现spring aop和@MapperScan功能。
《走进Spring框架系列—初识IOC》
《走进Spring框架系列—IOC应用》
《走进Spring框架系列—IOC应用进阶篇》
《走进Spring框架系列—IOC应用进阶实战》
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
,咱们下期见!