手写Mybatis整合Spring示例,原来Spring的扩展能力这么强大

Spring之所以能打败其他所有同类型Java开发框架屹立不倒的重要原因之一就是提供很多扩展点,让其他组件和框架很容易就整合到Spring框架里,所以也就诞生很多基于Spring的二次开发项目,接下来我们一起聊聊Spring提供哪些扩展点,这篇文章只是简单说明扩展点但不深入,有兴趣的伙伴可以后续一起学习交流,本篇最后我们再进行一个Mybatis和Spring整合工程简易开发示例

Spring加载上下文方式

Spring加载容器上下文主要提供以下三大类方式,第一种基于配置类为常用的使用方式,

AnnotationConfigApplicationContext传入参数是一个Class数组也即是支持多个配置类

publicclassApplicationDemo{publicstaticvoidmain(String[] args){//基于配置类加载Spring上下文AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(MyConfig.class);        Student student = applicationContext.getBean(Student.class);        System.out.println(student);//基于项目路径xml加载Spring上下文ClassPathXmlApplicationContext applicationContextXml =newClassPathXmlApplicationContext("spring.xml");        student = applicationContextXml.getBean(Student.class);        System.out.println(student);//基于文件系统绝对路径xml加载Spring上下文FileSystemXmlApplicationContext applicationContextFileXml =newFileSystemXmlApplicationContext("E://spring.xml");        student = applicationContextXml.getBean(Student.class);        System.out.println(student);    }}@Configuration@ComponentScan({"com.itxs.pojo","com.itxs.extend"})publicclassMyConfig{}

publicAnnotationConfigApplicationContext(Class<?>... componentClasses){this();register(componentClasses);    refresh();}

扩展点

ApplicationContextInitializer

这个是Spring Boot提供的扩展点,在整个 spring 容器在刷新之前初始化

ConfigurableApplicationContext 的回调接口,简单来说,就是在容器刷新之前调用此类的 initialize 方法。这个点允许用户自己扩展。用户可以在整个 spring 容器还没被初始化之前做过一些事情。可以想到的场景可能为,在最开始激活一些配置,或者利用这时候 class 还没被类加载器加载的时机,进行动态字节码注入等操作

publicclassMyApplicationContextInitializerimplementsApplicationContextInitializer{@Overridepublicvoidinitialize(ConfigurableApplicationContext configurableApplicationContext){        System.out.println("-----------------------MyApplicationContextInitializer initialize");    }}

@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){//SpringApplication Main函数添加初始化器方式SpringApplication springApplication =newSpringApplication(MyApplication.class);        springApplication.addInitializers(newMyApplicationContextInitializer());        springApplication.run(args);    }}

第二种配置文件中的配置

context:initializer:classes:com.itxs.extend.MyApplicationContextInitializer

第三种SpringBoot的SPI扩展

---META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=com.itxs.extend.MyApplicationContextInitializer

BeanDefinitionRegistryPostProcessor-bean定义注册后置处理器

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口,BeanFactoryPostProcessor的作用是在bean的定义信息已经加载但还没有进行初始化的时候执行postProcessBeanFactory()的方法,BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor的前面执行,在bean定义之后提供的扩展点,比如可以在这里动态注册自己的 beanDefinition,加载 classpath 之外的 bean信息。

@ComponentpublicclassMyBeanDefinitionRegistryPostProcessorimplementsBeanDefinitionRegistryPostProcessor{@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)throwsBeansException{        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanDefinitionRegistry");        System.out.println("BeanDefinitionCount:"+beanDefinitionRegistry.getBeanDefinitionCount());        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();for(String beanDefinitionName : beanDefinitionNames) {            System.out.println("beanDefinitionName:"+beanDefinitionName);        }    }@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)throwsBeansException{        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanFactory");    }}

BeanFactoryPostProcessor-Bean工厂后置处理器

BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 的扩展接口,在 Spring 在读取 beanDefinition 信息之后实例化 bean 之前读取 bean 定义并可以修改它。在这个时机,用户可以通过实现这个扩展接口来自行处理一些东西,比如修改已经注册的 beanDefinition 的元信息。下面将student bean类型修改为teacher bean类型

@ComponentpublicclassMyBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)throwsBeansException{        System.out.println("BeanFactoryPostProcessor-postProcessBeanFactory");        BeanDefinition student = configurableListableBeanFactory.getBeanDefinition("student");        student.setBeanClassName(Teacher.class.getName());    }}

BeanPostProcessor-Bean后置处理器

该接口也可称为后置处理器,而我们Spring提供很多种bean后置处理器(比如在Spring源码中九大后置处理器九次调用后置处理器地方的说法),其作用是在Bean对象实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的业务逻辑;注意是Bean实例化完毕后及依赖注入完成后触发的;在这个扩展点我们可以修改bean的属性,可以给bean生成一个动态代理实例等等。Spring AOP的底层处理主要也是通过实现BeanPostProcessor来执行代理包装逻辑。方法中输入是一个个的bean,返回值则是bean修改的对象,默认为null则是不修改;bean后置处理器可以有多个,可以通过实现Ordered接口或者标记@Order注解来决定其处理顺序。

·

postProcessBeforeInitialization:初始化 bean 之前,相当于把 bean 注入 spring 上下文之前

·

postProcessAfterInitialization:初始化 bean 之后,相当于把 bean 注入 spring 上下文之后

@ComponentpublicclassMyBeanPostProcessorimplementsBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInitialization(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessBeforeInitialization beanName:"+beanName);returnnull;    }@OverridepublicObjectpostProcessAfterInitialization(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessAfterInitialization beanName:"+beanName);if(bean.getClass().isAssignableFrom(Student.class)){returnTeacher.class;        }returnnull;    }}

InstantiationAwareBeanPostProcessor

该接口也是 BeanPostProcessor的子接口,而BeanPostProcessor接口只在 bean 的初始化阶段进行扩展(Bean实例化完毕后及依赖注入完成后触发的),而

InstantiationAwareBeanPostProcessor 接口在此基础上增加了 3 个方法,把可扩展的范围增加了实例化阶段和属性注入阶段,该类主要的扩展点有以下 5 个方法,主要在 bean 生命周期的两大阶段:实例化阶段和初始化阶段**,而初始化阶段两个方法也即是上一节BeanPostProcessor提供的两个方法**

·

postProcessBeforeInstantiation:实例化 bean 之前,相当于 new 这个 bean 之前

·

postProcessAfterInstantiation:实例化 bean 之后,相当于 new 这个 bean 之后

· postProcessPropertyValues:bean 已经实例化完成,在属性注入时阶段触发,@Autowired,@Resource 等注解原理基于此方法实现

使用场景:这个扩展点非常有用 ,无论是写中间件还是业务中,都能利用这个特性;比如实现了某一类接口的 bean 在各个生命期间进行收集,或者对某个类型的 bean 进行统一的设置等等。

@ComponentpublicclassMyInstantiationAwareBeanPostProcessorimplementsInstantiationAwareBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInstantiation(Class<?> beanClass, String beanName)throwsBeansException{        System.out.println("postProcessBeforeInstantiation beanName"+ beanName);returnnull;    }@OverridepublicbooleanpostProcessAfterInstantiation(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessAfterInstantiation beanName"+ beanName);returnfalse;    }@OverridepublicPropertyValuespostProcessProperties(PropertyValues pvs, Object bean, String beanName)throwsBeansException{        System.out.println("postProcessProperties beanName"+ beanName);returnnull;    }}

**### **

该接口也是

InstantiationAwareBeanPostProcessor 的子接口

· predictBeanType:该触发点发生在

postProcessBeforeInstantiation 之前,这个方法用于预测 Bean 的类型,返回第一个预测成功的地方 Class 类型,如果不能预测返回 null;当你调用 BeanFactory.getType(name) 适当通过 bean 的名字无法得到 bean 类型信息时就调用该回调的方法来决定类型信息。

·

determineCandidateConstructors:该触发点发生在 postProcessBeforeInstantiation 之后,用于确定该 bean 构造函数之用,返回的是该 bean 的所有构造函数列表。用户可以扩展这个点,来自定义选择相应的构造器来实现这个 bean。

· getEarlyBeanReference:该触发点发生在

postProcessAfterInstantiation 之后,当有循环依赖的场景,当 bean 实例化好之后,为了防止有循环依赖,Spring主要解决是的属性的循环依赖,会提前暴露回调方法,用于 bean 实例化的后置处理,这个方法就是在提前暴露的回调方法中触发。

@ComponentpublicclassMySmartInstantiationAwareBeanPostProcessorimplementsSmartInstantiationAwareBeanPostProcessor{@OverridepublicClass predictBeanType(Class beanClass, String beanName)throwsBeansException {        System.out.println("predictBeanType beanName:"+beanName);returnnull;    }@OverridepublicConstructor[] determineCandidateConstructors(Class beanClass, String beanName)throwsBeansException {if(!beanClass.isAssignableFrom(Student.class)){            System.out.println("determineCandidateConstructors beanName:"+beanName);        }returnnull;    }@OverridepublicObjectgetEarlyBeanReference(Object bean, String beanName)throwsBeansException{        System.out.println("getEarlyBeanReference beanName:"+beanName);returnnull;    }}

ApplicationContextAwareProcessor

这个是一个实现了BeanPostProcessor的实现类,该类本身并没有扩展点,但是该类内部却有 多个扩展点可供实现 ,这些类触发的时机在 bean 实例化之后,初始化之前,可以看到,该类用于执行各种驱动接口,在 bean 实例化之后,属性填充之后,通过执行以上红框标出的扩展接口,来获取对应容器的变量。所以这里应该来说是有 6 个扩展点,这里就放一起来说了

· EnvironmentAware:用于获取 EnviromentAware 的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。当然个人认为这个 Aware 没必要去扩展,因为 spring 内部都可以通过注入的方式来直接获得。

·

EmbeddedValueResolverAware:用于获取 StringValueResolver 的一个扩展类, StringValueResolver 用于获取基于 String 类型的 properties 的变量,一般我们都用 @Value 的方式去获取,如果实现了这个 Aware 接口,把 StringValueResolver 缓存起来,通过这个类去获取 String 类型的变量,效果是一样的。

· ResourceLoaderAware:用于获取 ResourceLoader 的一个扩展类,ResourceLoader 可以用于获取 classpath 内所有的资源对象,可以扩展此类来拿到 ResourceLoader 对象。

·

ApplicationEventPublisherAware:用于获取 ApplicationEventPublisher 的一个扩展类,ApplicationEventPublisher 可以用来发布事件,结合 ApplicationListener 来共同使用,下文在介绍 ApplicationListener 时会详细提到。这个对象也可以通过 spring 注入的方式来获得。

· MessageSourceAware:用于获取 MessageSource 的一个扩展类,MessageSource 主要用来做国际化。

· ApplicationContextAware:用来获取 ApplicationContext 的一个扩展类,ApplicationContext 应该是很多人非常熟悉的一个类了,就是 spring 上下文管理器,可以手动的获取任何在 spring 上下文注册的 bean,我们经常扩展这个接口来缓存 spring 上下文,包装成静态方法。同时 ApplicationContext 也实现了 BeanFactory,MessageSource,ApplicationEventPublisher 等接口,也可以用来做相关接口的事情。

privatevoidinvokeAwareInterfaces(Object bean){if(beaninstanceofEnvironmentAware) {      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());  }if(beaninstanceofEmbeddedValueResolverAware) {      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);  }if(beaninstanceofResourceLoaderAware) {      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);  }if(beaninstanceofApplicationEventPublisherAware) {      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);  }if(beaninstanceofMessageSourceAware) {      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);  }if(beaninstanceofApplicationStartupAware) {      ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());  }if(beaninstanceofApplicationContextAware) {      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);  }}

SmartInitializingSingleton

这个接口中只有一个方法

afterSingletonsInstantiated,其作用是 在 spring 容器管理的所有单例对象(非懒加载对象)初始化完成之后调用的回调接口。其触发时机为 postProcessAfterInitialization 之后。使用场景:用户可以扩展此接口在对所有单例对象初始化完毕后,做一些后置的业务处理。

@ComponentpublicclassMySmartInitializingSingletonimplementsSmartInitializingSingleton{@OverridepublicvoidafterSingletonsInstantiated(){        System.out.println("afterSingletonsInstantiated");    }}

FactoryBean

Spring为此提供了一个

org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现;FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&;

@ComponentpublicclassMyFactoryBeanimplementsFactoryBean{@OverridepublicTeachergetObject()throwsException{returnnewTeacher();    }@OverridepublicClass getObjectType() {returnTeacher.class;    }}

publicclassApplicationExtend{publicstaticvoidmain(String[] args){        AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(MyConfig.class);//Student student = applicationContext.getBean("student", Student.class); //这里是原来获取Student的类型,扩展点修改后需要注释掉不然会类型转换错误Object student = applicationContext.getBean("student");        System.out.println("object:"+student);        System.out.println("factoryBeanReturn:"+applicationContext.getBean("myFactoryBean"));        System.out.println("factoryBeanSelf:"+applicationContext.getBean("&myFactoryBean"));    }}

CommandLineRunner

这个是Spring Boot提供扩展接口,这个接口也只有一个方法:run(String... args),触发时机为整个项目启动完毕后,自动执行。如果有多个 CommandLineRunner,可以利用 @Order注解 来进行排序,值越小越优先执行。使用场景:用户扩展此接口,进行启动项目之后一些业务的预处理。

@Component@Order(1)public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyCommandLineRunner run args:"+args);    }}

@Component@Order(0)public class MyTwoCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyTwoCommandLineRunner run args:"+args);    }}

ApplicationListener

ApplicationListener 可以监听某个事件的 event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件;但是 spring 内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。接下来罗列下 spring 主要的内置事件:

· ContextRefreshedEvent

· ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在

ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。此处的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。

· ContextStartedEvent

· 当使用

ConfigurableApplicationContext (ApplicationContext 子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

· ContextStoppedEvent

· 当使用

ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作

· ContextClosedEvent

· 当使用

ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启

· RequestHandledEvent

· 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用 DispatcherServlet 的 Web 应用。在使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件

@ConfigurationpublicclassMyApplicationListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ApplicationEvent applicationEvent){        System.out.println("MyApplicationListener onApplicationEvent");    }}

BeanNameAware

BeanNameAware也是 Aware 接口扩展的子接口,触发点在 bean 的初始化之前,也就是

postProcessBeforeInitialization 之前

如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;这个类的触发点方法只有一个:setBeanName使用场景为:用户可以扩展这个点,在初始化 bean 之前拿到 spring 容器中注册的的 beanName,来自行修改这个 ;

@ComponentpublicclassMyBeanNameAwareimplementsBeanNameAware{@OverridepublicvoidsetBeanName(String s){        System.out.println("setBeanName:"+s);    }}

BeanFactoryAware

BeanFactoryAware也是 Aware 接口扩展的子接口,只有一个触发点,发生在 bean 的实例化之后,注入属性之前,也就是 Setter 之前。这个类的扩展点方法为 setBeanFactory,可以拿到 BeanFactory 这个属性。使用场景为,你可以在 bean 实例化之后,但还未初始化之前,拿到 BeanFactory,在这个时候,可以对每个 bean 作特殊化的定制。也或者可以把 BeanFactory 拿到进行缓存,日后使用,如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;

@ComponentpublicclassMyBeanFactoryAwareimplementsBeanFactoryAware{@OverridepublicvoidsetBeanFactory(BeanFactory beanFactory)throwsBeansException{        System.out.println("MyBeanFactoryAware setBeanFactory:"+beanFactory.getBean("myBeanFactoryAware").getClass().getSimpleName());    }}

Bean初始化及销毁回调方法

InitializingBean是用来初始化 bean 的。InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候都会执行该方法。这个扩展点的触发时机在 postProcessAfterInitialization 之前。

DisposableBean这个扩展点也只有一个方法:destroy(),其触发时机为当此对象销毁时,会自动执行这个方法。比如说运行 applicationContext.registerShutdownHook 时,就会触发这个方法。

@PostConstruct这个并不算一个扩展点,其实就是一个标注。其作用是在 bean 的初始化阶段,如果对一个方法标注了 @PostConstruct,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在 postProcessBeforeInitialization 之后,InitializingBean.afterPropertiesSet 之前。使用场景:用户可以对某一方法进行标注,来进行初始化某一个属性

@PreDestroy修饰的方法会在服务器关闭Spring容器的时候运行,并且只会调用一次

使用场景:用户实现此接口,来进行系统启动的时候一些业务指标的初始化工作。

@RepositorypublicclassStudentimplementsInitializingBean,DisposableBean{@PostConstructpublicvoidinit()throwsException{        System.out.println("Student init");    }@PreDestroypublicvoidclose()throwsException{        System.out.println("Student close");    }@Overridepublicvoiddestroy()throwsException{        System.out.println("Student destroy");    }@OverridepublicvoidafterPropertiesSet()throwsException{        System.out.println("Student afterPropertiesSet");    }}

整合示例

mybatis-spring gitee源码地址

https://gitee.com/yongzhebuju/mybatis-spring

我们从这些 Spring&Spring Boot 的扩展点当中,大致可以窥视到整个 bean 的生命周期。在业务开发或者写中间件业务的时候,可以合理利用 Spring 提供给我们的扩展点,在 Spring 启动的各个阶段内做一些事情,以达到自定义初始化的目的。接下来我们一起学习一个常见Mybatis和Spring整合开发简易示例,上面内容我们初步对于Spring扩展点有了一些理解,由于本人阅读过部分Mybatic与Spring整合源码,思路也是来源于此,由于目前是简易示例,不在工程加入Mybatis依赖,着重在整合部分

思考问题

Spring的Bean是如何生成的?

Spring提供哪些扩展点来整合第三方框架?

Spring是如何来整理Mybatis的?Mybatis代理对象都是接口,不能直接通过New方式将Mapper Bean注册Spring容器里

主体思路

首先可以明确一点,我们需要利用到Jdk动态代理及反射机制

借助FactoryBean特性,FactoryBean可以返回动态代理的对象及类型

通过ImportBeanDefinitionRegistrar通过Import注解将FactoryBean通过BeanDefinition注册到BeanDefinitionRegistry通过后续Bean的生命周期最终放到Spring的容器里

Mybatic快速入门

从官网的就可以直接找到Java编程快速入门** ,这里只是大概说明一下**

Stringresource ="org/mybatis/example/mybatis-config.xml";InputStreaminputStream = Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try(SqlSession session = sqlSessionFactory.openSession()) {BlogMappermapper = session.getMapper(BlogMapper.class);Blogblog = mapper.selectBlog(101);}

整合示例实现

pom文件

<?xml version="1.0" encoding="UTF-8"?>4.0.0com.itxsmybatis-spring1.0-SNAPSHOT88org.springframeworkspring-context5.3.9

注解接口,通过@Import同时将

ItxsImportBeanDefinitionRegistrar导入到Spring容器里

@Retention(RetentionPolicy.RUNTIME)@Import({ItxsImportBeanDefinitionRegistrar.class})public@interfaceItxsScan {    String value()default"";}

配置类,@ItxsScan为我们自定义的注解,主要标记扫描mapper路径

@Configuration@ComponentScan({"com.itxs"})@ItxsScan("com.itxs.dao")public class AppConfig {}

mapper接口,@ItxsSelect为我们自定义的注解,主要标记mapper接口方法sql语句

packagecom.itxs.dao;importcom.itxs.annotation.ItxsSelect;importcom.itxs.pojo.User;publicinterfaceUserMapper{@ItxsSelect("select * from user where user_id = #{userId}")User selectByUserId(Integer userId);}

service实现类

@ComponentpublicclassUserService{@AutowiredUserMapper userMapper;@AutowiredOrderMapper orderMapper;publicvoidgetUser(){        userMapper.selectByUserId(1);        orderMapper.selectByOrderId(1);    }}

FactoryBean实现,这里通过构造函数传入接口的Class类型,将类型通过Jdk动态代理生成并返回对象,当调用目标对象后会执行代理对象invoke方法,从invoke方法通过反射与注解获取到sql语句,后续流程就可以利用Mybatis提供操作数据库流程,这里就不继续深入了

packagecom.itxs.utils;importcom.itxs.annotation.ItxsSelect;importorg.springframework.beans.factory.FactoryBean;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassItxsFactoryBeanimplementsFactoryBean{privateClass mapper;publicItxsFactoryBean(Class mapper) {this.mapper = mapper;    }@OverridepublicObject getObject() throws Exception {//使用动态代理机制Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {if(Object.class.equals(method.getDeclaringClass())){returnmethod.invoke(this,args);                }                ItxsSelectannotation= method.getAnnotation(ItxsSelect.class);System.out.println("调用方法名称是"+method.getName()+",sql语句为"+annotation.value());//后面可执行Mybatic操作数据库的相关操作returnnull;            }        });returno;    }@OverridepublicClass getObjectType() {returnmapper;    }}

ImportBeanDefinitionRegistrar的实现,通过获取@import上的注解找到mapper的扫描路径,通过classLoader加载磁盘下Class文件生成BeanDefinition并设置构造函数mapper类型参数,最终将BeanDefinition注册到BeanDefinitionRegistry

package com.itxs.utils;importcom.itxs.annotation.ItxsScan;importcom.itxs.dao.OrderMapper;importcom.itxs.dao.UserMapper;importorg.springframework.beans.factory.support.AbstractBeanDefinition;importorg.springframework.beans.factory.support.BeanDefinitionBuilder;importorg.springframework.beans.factory.support.BeanDefinitionRegistry;importorg.springframework.beans.factory.support.BeanNameGenerator;importorg.springframework.context.annotation.ImportBeanDefinitionRegistrar;importorg.springframework.core.type.AnnotationMetadata;importjava.io.File;importjava.net.URL;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;publicclassItxsImportBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar{    @Overridepublicvoid registerBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry,BeanNameGeneratorimportBeanNameGenerator) {//获取注解ItxsScan对象,并取出value值作为扫描的路径Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());StringmapperPath=annotationAttributes.get("value").toString();Listmappers=scan(mapperPath);for(Classmapper:mappers){BeanDefinitionBuilderbuilder =BeanDefinitionBuilder.genericBeanDefinition();AbstractBeanDefinitionbeanDefinition = builder.getBeanDefinition();            beanDefinition.setBeanClass(ItxsFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);        }    }privateListscan(Stringpath){List classList = newArrayList();        path = path.replace(".","/");ClassLoaderclassLoader =ItxsImportBeanDefinitionRegistrar.class.getClassLoader();URLurl=classLoader.getResource(path);Filefile=newFile(url.getFile());if(file.isDirectory()){File[] files = file.listFiles();for(int i =0; i < files.length; i++) {StringabsolutePath = files[i].getAbsolutePath();                absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));                absolutePath = absolutePath.replace("\\",".");try{Class aClass = classLoader.loadClass(absolutePath);                    classList.add(aClass);                }catch(ClassNotFoundExceptione) {                    e.printStackTrace();                }            }        }returnclassList;    }}

Main程序

packagecom.itxs;importcom.itxs.config.AppConfig;importcom.itxs.service.UserService;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassMybatisApplication{publicstaticvoidmain(String[] args){        AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(AppConfig.class);        UserService userService = applicationContext.getBean("userService", UserService.class);        userService.getUser();    }}

运行结果

Spring之所以能打败其他所有同类型Java开发框架屹立不倒的重要原因之一就是提供很多扩展点,让其他组件和框架很容易就整合到Spring框架里,所以也就诞生很多基于Spring的二次开发项目,接下来我们一起聊聊Spring提供哪些扩展点,这篇文章只是简单说明扩展点但不深入,有兴趣的伙伴可以后续一起学习交流,本篇最后我们再进行一个Mybatis和Spring整合工程简易开发示例

Spring加载上下文方式

Spring加载容器上下文主要提供以下三大类方式,第一种基于配置类为常用的使用方式,

AnnotationConfigApplicationContext传入参数是一个Class数组也即是支持多个配置类

publicclassApplicationDemo{publicstaticvoidmain(String[] args){//基于配置类加载Spring上下文AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(MyConfig.class);        Student student = applicationContext.getBean(Student.class);        System.out.println(student);//基于项目路径xml加载Spring上下文ClassPathXmlApplicationContext applicationContextXml =newClassPathXmlApplicationContext("spring.xml");        student = applicationContextXml.getBean(Student.class);        System.out.println(student);//基于文件系统绝对路径xml加载Spring上下文FileSystemXmlApplicationContext applicationContextFileXml =newFileSystemXmlApplicationContext("E://spring.xml");        student = applicationContextXml.getBean(Student.class);        System.out.println(student);    }}@Configuration@ComponentScan({"com.itxs.pojo","com.itxs.extend"})publicclassMyConfig{}

publicAnnotationConfigApplicationContext(Class<?>... componentClasses){this();register(componentClasses);    refresh();}

扩展点

ApplicationContextInitializer

这个是Spring Boot提供的扩展点,在整个 spring 容器在刷新之前初始化

ConfigurableApplicationContext 的回调接口,简单来说,就是在容器刷新之前调用此类的 initialize 方法。这个点允许用户自己扩展。用户可以在整个 spring 容器还没被初始化之前做过一些事情。可以想到的场景可能为,在最开始激活一些配置,或者利用这时候 class 还没被类加载器加载的时机,进行动态字节码注入等操作

publicclassMyApplicationContextInitializerimplementsApplicationContextInitializer{@Overridepublicvoidinitialize(ConfigurableApplicationContext configurableApplicationContext){        System.out.println("-----------------------MyApplicationContextInitializer initialize");    }}

@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){//SpringApplication Main函数添加初始化器方式SpringApplication springApplication =newSpringApplication(MyApplication.class);        springApplication.addInitializers(newMyApplicationContextInitializer());        springApplication.run(args);    }}

第二种配置文件中的配置

context:initializer:classes:com.itxs.extend.MyApplicationContextInitializer

第三种SpringBoot的SPI扩展

---META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=com.itxs.extend.MyApplicationContextInitializer

BeanDefinitionRegistryPostProcessor-bean定义注册后置处理器

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口,BeanFactoryPostProcessor的作用是在bean的定义信息已经加载但还没有进行初始化的时候执行postProcessBeanFactory()的方法,BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor的前面执行,在bean定义之后提供的扩展点,比如可以在这里动态注册自己的 beanDefinition,加载 classpath 之外的 bean信息。

@ComponentpublicclassMyBeanDefinitionRegistryPostProcessorimplementsBeanDefinitionRegistryPostProcessor{@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)throwsBeansException{        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanDefinitionRegistry");        System.out.println("BeanDefinitionCount:"+beanDefinitionRegistry.getBeanDefinitionCount());        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();for(String beanDefinitionName : beanDefinitionNames) {            System.out.println("beanDefinitionName:"+beanDefinitionName);        }    }@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)throwsBeansException{        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanFactory");    }}

BeanFactoryPostProcessor-Bean工厂后置处理器

BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 的扩展接口,在 Spring 在读取 beanDefinition 信息之后实例化 bean 之前读取 bean 定义并可以修改它。在这个时机,用户可以通过实现这个扩展接口来自行处理一些东西,比如修改已经注册的 beanDefinition 的元信息。下面将student bean类型修改为teacher bean类型

@ComponentpublicclassMyBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)throwsBeansException{        System.out.println("BeanFactoryPostProcessor-postProcessBeanFactory");        BeanDefinition student = configurableListableBeanFactory.getBeanDefinition("student");        student.setBeanClassName(Teacher.class.getName());    }}

BeanPostProcessor-Bean后置处理器

该接口也可称为后置处理器,而我们Spring提供很多种bean后置处理器(比如在Spring源码中九大后置处理器九次调用后置处理器地方的说法),其作用是在Bean对象实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的业务逻辑;注意是Bean实例化完毕后及依赖注入完成后触发的;在这个扩展点我们可以修改bean的属性,可以给bean生成一个动态代理实例等等。Spring AOP的底层处理主要也是通过实现BeanPostProcessor来执行代理包装逻辑。方法中输入是一个个的bean,返回值则是bean修改的对象,默认为null则是不修改;bean后置处理器可以有多个,可以通过实现Ordered接口或者标记@Order注解来决定其处理顺序。

·

postProcessBeforeInitialization:初始化 bean 之前,相当于把 bean 注入 spring 上下文之前

·

postProcessAfterInitialization:初始化 bean 之后,相当于把 bean 注入 spring 上下文之后

@ComponentpublicclassMyBeanPostProcessorimplementsBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInitialization(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessBeforeInitialization beanName:"+beanName);returnnull;    }@OverridepublicObjectpostProcessAfterInitialization(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessAfterInitialization beanName:"+beanName);if(bean.getClass().isAssignableFrom(Student.class)){returnTeacher.class;        }returnnull;    }}

InstantiationAwareBeanPostProcessor

该接口也是 BeanPostProcessor的子接口,而BeanPostProcessor接口只在 bean 的初始化阶段进行扩展(Bean实例化完毕后及依赖注入完成后触发的),而

InstantiationAwareBeanPostProcessor 接口在此基础上增加了 3 个方法,把可扩展的范围增加了实例化阶段和属性注入阶段,该类主要的扩展点有以下 5 个方法,主要在 bean 生命周期的两大阶段:实例化阶段和初始化阶段**,而初始化阶段两个方法也即是上一节BeanPostProcessor提供的两个方法**

·

postProcessBeforeInstantiation:实例化 bean 之前,相当于 new 这个 bean 之前

·

postProcessAfterInstantiation:实例化 bean 之后,相当于 new 这个 bean 之后

· postProcessPropertyValues:bean 已经实例化完成,在属性注入时阶段触发,@Autowired,@Resource 等注解原理基于此方法实现

使用场景:这个扩展点非常有用 ,无论是写中间件还是业务中,都能利用这个特性;比如实现了某一类接口的 bean 在各个生命期间进行收集,或者对某个类型的 bean 进行统一的设置等等。

@ComponentpublicclassMyInstantiationAwareBeanPostProcessorimplementsInstantiationAwareBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInstantiation(Class<?> beanClass, String beanName)throwsBeansException{        System.out.println("postProcessBeforeInstantiation beanName"+ beanName);returnnull;    }@OverridepublicbooleanpostProcessAfterInstantiation(Object bean, String beanName)throwsBeansException{        System.out.println("postProcessAfterInstantiation beanName"+ beanName);returnfalse;    }@OverridepublicPropertyValuespostProcessProperties(PropertyValues pvs, Object bean, String beanName)throwsBeansException{        System.out.println("postProcessProperties beanName"+ beanName);returnnull;    }}

**### **

该接口也是

InstantiationAwareBeanPostProcessor 的子接口

· predictBeanType:该触发点发生在

postProcessBeforeInstantiation 之前,这个方法用于预测 Bean 的类型,返回第一个预测成功的地方 Class 类型,如果不能预测返回 null;当你调用 BeanFactory.getType(name) 适当通过 bean 的名字无法得到 bean 类型信息时就调用该回调的方法来决定类型信息。

·

determineCandidateConstructors:该触发点发生在 postProcessBeforeInstantiation 之后,用于确定该 bean 构造函数之用,返回的是该 bean 的所有构造函数列表。用户可以扩展这个点,来自定义选择相应的构造器来实现这个 bean。

· getEarlyBeanReference:该触发点发生在

postProcessAfterInstantiation 之后,当有循环依赖的场景,当 bean 实例化好之后,为了防止有循环依赖,Spring主要解决是的属性的循环依赖,会提前暴露回调方法,用于 bean 实例化的后置处理,这个方法就是在提前暴露的回调方法中触发。

@ComponentpublicclassMySmartInstantiationAwareBeanPostProcessorimplementsSmartInstantiationAwareBeanPostProcessor{@OverridepublicClass predictBeanType(Class beanClass, String beanName)throwsBeansException {        System.out.println("predictBeanType beanName:"+beanName);returnnull;    }@OverridepublicConstructor[] determineCandidateConstructors(Class beanClass, String beanName)throwsBeansException {if(!beanClass.isAssignableFrom(Student.class)){            System.out.println("determineCandidateConstructors beanName:"+beanName);        }returnnull;    }@OverridepublicObjectgetEarlyBeanReference(Object bean, String beanName)throwsBeansException{        System.out.println("getEarlyBeanReference beanName:"+beanName);returnnull;    }}

ApplicationContextAwareProcessor

这个是一个实现了BeanPostProcessor的实现类,该类本身并没有扩展点,但是该类内部却有 多个扩展点可供实现 ,这些类触发的时机在 bean 实例化之后,初始化之前,可以看到,该类用于执行各种驱动接口,在 bean 实例化之后,属性填充之后,通过执行以上红框标出的扩展接口,来获取对应容器的变量。所以这里应该来说是有 6 个扩展点,这里就放一起来说了

· EnvironmentAware:用于获取 EnviromentAware 的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。当然个人认为这个 Aware 没必要去扩展,因为 spring 内部都可以通过注入的方式来直接获得。

·

EmbeddedValueResolverAware:用于获取 StringValueResolver 的一个扩展类, StringValueResolver 用于获取基于 String 类型的 properties 的变量,一般我们都用 @Value 的方式去获取,如果实现了这个 Aware 接口,把 StringValueResolver 缓存起来,通过这个类去获取 String 类型的变量,效果是一样的。

· ResourceLoaderAware:用于获取 ResourceLoader 的一个扩展类,ResourceLoader 可以用于获取 classpath 内所有的资源对象,可以扩展此类来拿到 ResourceLoader 对象。

·

ApplicationEventPublisherAware:用于获取 ApplicationEventPublisher 的一个扩展类,ApplicationEventPublisher 可以用来发布事件,结合 ApplicationListener 来共同使用,下文在介绍 ApplicationListener 时会详细提到。这个对象也可以通过 spring 注入的方式来获得。

· MessageSourceAware:用于获取 MessageSource 的一个扩展类,MessageSource 主要用来做国际化。

· ApplicationContextAware:用来获取 ApplicationContext 的一个扩展类,ApplicationContext 应该是很多人非常熟悉的一个类了,就是 spring 上下文管理器,可以手动的获取任何在 spring 上下文注册的 bean,我们经常扩展这个接口来缓存 spring 上下文,包装成静态方法。同时 ApplicationContext 也实现了 BeanFactory,MessageSource,ApplicationEventPublisher 等接口,也可以用来做相关接口的事情。

privatevoidinvokeAwareInterfaces(Object bean){if(beaninstanceofEnvironmentAware) {      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());  }if(beaninstanceofEmbeddedValueResolverAware) {      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);  }if(beaninstanceofResourceLoaderAware) {      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);  }if(beaninstanceofApplicationEventPublisherAware) {      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);  }if(beaninstanceofMessageSourceAware) {      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);  }if(beaninstanceofApplicationStartupAware) {      ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());  }if(beaninstanceofApplicationContextAware) {      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);  }}

SmartInitializingSingleton

这个接口中只有一个方法

afterSingletonsInstantiated,其作用是 在 spring 容器管理的所有单例对象(非懒加载对象)初始化完成之后调用的回调接口。其触发时机为 postProcessAfterInitialization 之后。使用场景:用户可以扩展此接口在对所有单例对象初始化完毕后,做一些后置的业务处理。

@ComponentpublicclassMySmartInitializingSingletonimplementsSmartInitializingSingleton{@OverridepublicvoidafterSingletonsInstantiated(){        System.out.println("afterSingletonsInstantiated");    }}

FactoryBean

Spring为此提供了一个

org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现;FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&;

@ComponentpublicclassMyFactoryBeanimplementsFactoryBean{@OverridepublicTeachergetObject()throwsException{returnnewTeacher();    }@OverridepublicClass getObjectType() {returnTeacher.class;    }}

publicclassApplicationExtend{publicstaticvoidmain(String[] args){        AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(MyConfig.class);//Student student = applicationContext.getBean("student", Student.class); //这里是原来获取Student的类型,扩展点修改后需要注释掉不然会类型转换错误Object student = applicationContext.getBean("student");        System.out.println("object:"+student);        System.out.println("factoryBeanReturn:"+applicationContext.getBean("myFactoryBean"));        System.out.println("factoryBeanSelf:"+applicationContext.getBean("&myFactoryBean"));    }}

CommandLineRunner

这个是Spring Boot提供扩展接口,这个接口也只有一个方法:run(String... args),触发时机为整个项目启动完毕后,自动执行。如果有多个 CommandLineRunner,可以利用 @Order注解 来进行排序,值越小越优先执行。使用场景:用户扩展此接口,进行启动项目之后一些业务的预处理。

@Component@Order(1)public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyCommandLineRunner run args:"+args);    }}

@Component@Order(0)public class MyTwoCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyTwoCommandLineRunner run args:"+args);    }}

ApplicationListener

ApplicationListener 可以监听某个事件的 event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件;但是 spring 内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。接下来罗列下 spring 主要的内置事件:

· ContextRefreshedEvent

· ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在

ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。此处的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。

· ContextStartedEvent

· 当使用

ConfigurableApplicationContext (ApplicationContext 子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

· ContextStoppedEvent

· 当使用

ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作

· ContextClosedEvent

· 当使用

ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启

· RequestHandledEvent

· 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用 DispatcherServlet 的 Web 应用。在使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件

@ConfigurationpublicclassMyApplicationListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ApplicationEvent applicationEvent){        System.out.println("MyApplicationListener onApplicationEvent");    }}

BeanNameAware

BeanNameAware也是 Aware 接口扩展的子接口,触发点在 bean 的初始化之前,也就是

postProcessBeforeInitialization 之前

如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;这个类的触发点方法只有一个:setBeanName使用场景为:用户可以扩展这个点,在初始化 bean 之前拿到 spring 容器中注册的的 beanName,来自行修改这个 ;

@ComponentpublicclassMyBeanNameAwareimplementsBeanNameAware{@OverridepublicvoidsetBeanName(String s){        System.out.println("setBeanName:"+s);    }}

BeanFactoryAware

BeanFactoryAware也是 Aware 接口扩展的子接口,只有一个触发点,发生在 bean 的实例化之后,注入属性之前,也就是 Setter 之前。这个类的扩展点方法为 setBeanFactory,可以拿到 BeanFactory 这个属性。使用场景为,你可以在 bean 实例化之后,但还未初始化之前,拿到 BeanFactory,在这个时候,可以对每个 bean 作特殊化的定制。也或者可以把 BeanFactory 拿到进行缓存,日后使用,如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;

@ComponentpublicclassMyBeanFactoryAwareimplementsBeanFactoryAware{@OverridepublicvoidsetBeanFactory(BeanFactory beanFactory)throwsBeansException{        System.out.println("MyBeanFactoryAware setBeanFactory:"+beanFactory.getBean("myBeanFactoryAware").getClass().getSimpleName());    }}

Bean初始化及销毁回调方法

InitializingBean是用来初始化 bean 的。InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候都会执行该方法。这个扩展点的触发时机在 postProcessAfterInitialization 之前。

DisposableBean这个扩展点也只有一个方法:destroy(),其触发时机为当此对象销毁时,会自动执行这个方法。比如说运行 applicationContext.registerShutdownHook 时,就会触发这个方法。

@PostConstruct这个并不算一个扩展点,其实就是一个标注。其作用是在 bean 的初始化阶段,如果对一个方法标注了 @PostConstruct,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在 postProcessBeforeInitialization 之后,InitializingBean.afterPropertiesSet 之前。使用场景:用户可以对某一方法进行标注,来进行初始化某一个属性

@PreDestroy修饰的方法会在服务器关闭Spring容器的时候运行,并且只会调用一次

使用场景:用户实现此接口,来进行系统启动的时候一些业务指标的初始化工作。

@RepositorypublicclassStudentimplementsInitializingBean,DisposableBean{@PostConstructpublicvoidinit()throwsException{        System.out.println("Student init");    }@PreDestroypublicvoidclose()throwsException{        System.out.println("Student close");    }@Overridepublicvoiddestroy()throwsException{        System.out.println("Student destroy");    }@OverridepublicvoidafterPropertiesSet()throwsException{        System.out.println("Student afterPropertiesSet");    }}

整合示例

mybatis-spring gitee源码地址

https://gitee.com/yongzhebuju/mybatis-spring

我们从这些 Spring&Spring Boot 的扩展点当中,大致可以窥视到整个 bean 的生命周期。在业务开发或者写中间件业务的时候,可以合理利用 Spring 提供给我们的扩展点,在 Spring 启动的各个阶段内做一些事情,以达到自定义初始化的目的。接下来我们一起学习一个常见Mybatis和Spring整合开发简易示例,上面内容我们初步对于Spring扩展点有了一些理解,由于本人阅读过部分Mybatic与Spring整合源码,思路也是来源于此,由于目前是简易示例,不在工程加入Mybatis依赖,着重在整合部分

思考问题

Spring的Bean是如何生成的?

Spring提供哪些扩展点来整合第三方框架?

Spring是如何来整理Mybatis的?Mybatis代理对象都是接口,不能直接通过New方式将Mapper Bean注册Spring容器里

主体思路

首先可以明确一点,我们需要利用到Jdk动态代理及反射机制

借助FactoryBean特性,FactoryBean可以返回动态代理的对象及类型

通过ImportBeanDefinitionRegistrar通过Import注解将FactoryBean通过BeanDefinition注册到BeanDefinitionRegistry通过后续Bean的生命周期最终放到Spring的容器里

Mybatic快速入门

从官网的就可以直接找到Java编程快速入门** ,这里只是大概说明一下**

Stringresource ="org/mybatis/example/mybatis-config.xml";InputStreaminputStream = Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try(SqlSession session = sqlSessionFactory.openSession()) {BlogMappermapper = session.getMapper(BlogMapper.class);Blogblog = mapper.selectBlog(101);}

整合示例实现

pom文件

<?xml version="1.0" encoding="UTF-8"?>4.0.0com.itxsmybatis-spring1.0-SNAPSHOT88org.springframeworkspring-context5.3.9

注解接口,通过@Import同时将

ItxsImportBeanDefinitionRegistrar导入到Spring容器里

@Retention(RetentionPolicy.RUNTIME)@Import({ItxsImportBeanDefinitionRegistrar.class})public@interfaceItxsScan {    String value()default"";}

配置类,@ItxsScan为我们自定义的注解,主要标记扫描mapper路径

@Configuration@ComponentScan({"com.itxs"})@ItxsScan("com.itxs.dao")public class AppConfig {}

mapper接口,@ItxsSelect为我们自定义的注解,主要标记mapper接口方法sql语句

packagecom.itxs.dao;importcom.itxs.annotation.ItxsSelect;importcom.itxs.pojo.User;publicinterfaceUserMapper{@ItxsSelect("select * from user where user_id = #{userId}")User selectByUserId(Integer userId);}

service实现类

@ComponentpublicclassUserService{@AutowiredUserMapper userMapper;@AutowiredOrderMapper orderMapper;publicvoidgetUser(){        userMapper.selectByUserId(1);        orderMapper.selectByOrderId(1);    }}

FactoryBean实现,这里通过构造函数传入接口的Class类型,将类型通过Jdk动态代理生成并返回对象,当调用目标对象后会执行代理对象invoke方法,从invoke方法通过反射与注解获取到sql语句,后续流程就可以利用Mybatis提供操作数据库流程,这里就不继续深入了

packagecom.itxs.utils;importcom.itxs.annotation.ItxsSelect;importorg.springframework.beans.factory.FactoryBean;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassItxsFactoryBeanimplementsFactoryBean{privateClass mapper;publicItxsFactoryBean(Class mapper) {this.mapper = mapper;    }@OverridepublicObject getObject() throws Exception {//使用动态代理机制Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {if(Object.class.equals(method.getDeclaringClass())){returnmethod.invoke(this,args);                }                ItxsSelectannotation= method.getAnnotation(ItxsSelect.class);System.out.println("调用方法名称是"+method.getName()+",sql语句为"+annotation.value());//后面可执行Mybatic操作数据库的相关操作returnnull;            }        });returno;    }@OverridepublicClass getObjectType() {returnmapper;    }}

ImportBeanDefinitionRegistrar的实现,通过获取@import上的注解找到mapper的扫描路径,通过classLoader加载磁盘下Class文件生成BeanDefinition并设置构造函数mapper类型参数,最终将BeanDefinition注册到BeanDefinitionRegistry

package com.itxs.utils;importcom.itxs.annotation.ItxsScan;importcom.itxs.dao.OrderMapper;importcom.itxs.dao.UserMapper;importorg.springframework.beans.factory.support.AbstractBeanDefinition;importorg.springframework.beans.factory.support.BeanDefinitionBuilder;importorg.springframework.beans.factory.support.BeanDefinitionRegistry;importorg.springframework.beans.factory.support.BeanNameGenerator;importorg.springframework.context.annotation.ImportBeanDefinitionRegistrar;importorg.springframework.core.type.AnnotationMetadata;importjava.io.File;importjava.net.URL;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;publicclassItxsImportBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar{    @Overridepublicvoid registerBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry,BeanNameGeneratorimportBeanNameGenerator) {//获取注解ItxsScan对象,并取出value值作为扫描的路径Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());StringmapperPath=annotationAttributes.get("value").toString();Listmappers=scan(mapperPath);for(Classmapper:mappers){BeanDefinitionBuilderbuilder =BeanDefinitionBuilder.genericBeanDefinition();AbstractBeanDefinitionbeanDefinition = builder.getBeanDefinition();            beanDefinition.setBeanClass(ItxsFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);        }    }privateListscan(Stringpath){List classList = newArrayList();        path = path.replace(".","/");ClassLoaderclassLoader =ItxsImportBeanDefinitionRegistrar.class.getClassLoader();URLurl=classLoader.getResource(path);Filefile=newFile(url.getFile());if(file.isDirectory()){File[] files = file.listFiles();for(int i =0; i < files.length; i++) {StringabsolutePath = files[i].getAbsolutePath();                absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));                absolutePath = absolutePath.replace("\\",".");try{Class aClass = classLoader.loadClass(absolutePath);                    classList.add(aClass);                }catch(ClassNotFoundExceptione) {                    e.printStackTrace();                }            }        }returnclassList;    }}

Main程序

packagecom.itxs;importcom.itxs.config.AppConfig;importcom.itxs.service.UserService;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassMybatisApplication{publicstaticvoidmain(String[] args){        AnnotationConfigApplicationContext applicationContext =newAnnotationConfigApplicationContext(AppConfig.class);        UserService userService = applicationContext.getBean("userService", UserService.class);        userService.getUser();    }}

运行结果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容