Spring 扩展点

**本人博客网站 **IT小神 www.itxiaoshen.com

官网地址****:https://spring.io/projects/spring-framework

The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform.

Spring框架为现代基于java的企业应用程序提供了全面的编程和配置模型——适用于任何类型的部署平台。

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

Spring加载上下文方式

Spring加载容器上下文主要提供以下三大类方式,第一种基于配置类为常用的使用方式,AnnotationConfigApplicationContext传入参数是一个Class数组也即是支持多个配置类

public class ApplicationDemo {
public static void main(String[] args) {
//基于配置类加载Spring上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Student student = applicationContext.getBean(Student.class);
System.out.println(student); //基于项目路径xml加载Spring上下文
ClassPathXmlApplicationContext applicationContextXml = new ClassPathXmlApplicationContext("spring.xml");
student = applicationContextXml.getBean(Student.class);
System.out.println(student); //基于文件系统绝对路径xml加载Spring上下文
FileSystemXmlApplicationContext applicationContextFileXml = new FileSystemXmlApplicationContext("E://spring.xml");
student = applicationContextXml.getBean(Student.class);
System.out.println(student);
}
} @Configuration
@ComponentScan({"com.itxs.pojo","com.itxs.extend"})
public class MyConfig {
}
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}

扩展点

ApplicationContextInitializer

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

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("-----------------------MyApplicationContextInitializer initialize");
}
}
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
//SpringApplication Main函数添加初始化器方式
SpringApplication springApplication = new SpringApplication(MyApplication.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
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信息。

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanDefinitionRegistry");
System.out.println("BeanDefinitionCount:"+beanDefinitionRegistry.getBeanDefinitionCount());
String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("beanDefinitionName:"+beanDefinitionName);
}
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanFactory");
}
}

BeanFactoryPostProcessor-Bean工厂后置处理器

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

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
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 上下文之后

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization beanName:"+beanName);
return null;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization beanName:"+beanName);
if (bean.getClass().isAssignableFrom(Student.class)){
return Teacher.class;
}
return null;
}
}

InstantiationAwareBeanPostProcessor

该接口也是 BeanPostProcessor的子接口,而BeanPostProcessor接口只在 bean 的初始化阶段进行扩展(Bean实例化完毕后及依赖注入完成后触发的),而 InstantiationAwareBeanPostProcessor 接口在此基础上增加了 3 个方法,把可扩展的范围增加了实例化阶段和属性注入阶段,该类主要的扩展点有以下 5 个方法,主要在 bean 生命周期的两大阶段:实例化阶段初始化阶段**,而初始化阶段两个方法也即是上一节BeanPostProcessor提供的两个方法**

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

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

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

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

@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("postProcessBeforeInstantiation beanName" + beanName);
return null;
} @Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInstantiation beanName" + beanName);
return false;
} @Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
System.out.println("postProcessProperties beanName" + beanName);
return null;
}
}

**### **

该接口也是 InstantiationAwareBeanPostProcessor 的子接口

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

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

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

@Component
public class MySmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor{
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("predictBeanType beanName:"+beanName);
return null;
} @Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
if (!beanClass.isAssignableFrom(Student.class)){
System.out.println("determineCandidateConstructors beanName:"+beanName);
}
return null;
} @Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
System.out.println("getEarlyBeanReference beanName:"+beanName);
return null;
}
}

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 等接口,也可以用来做相关接口的事情。

private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationStartupAware) {
((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}

SmartInitializingSingleton

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

@Component
public class MySmartInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
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之前加上&;

@Component
public class MyFactoryBean implements FactoryBean<Teacher> { @Override
public Teacher getObject() throws Exception {
return new Teacher();
} @Override
public Class<?> getObjectType() {
return Teacher.class;
}
}
public class ApplicationExtend {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(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 {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner run args:" +args);
}
}
@Component
@Order(0)
public class MyTwoCommandLineRunner implements CommandLineRunner {
@Override
public 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 处理用户请求结束后,系统会自动触发该事件

@Configuration
public class MyApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println("MyApplicationListener onApplicationEvent");
}
}

BeanNameAware

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

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

@Component
public class MyBeanNameAware implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("setBeanName:"+s);
}
}

BeanFactoryAware

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

@Component
public class MyBeanFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
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容器的时候运行,并且只会调用一次

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

@Repository
public class Student implements InitializingBean, DisposableBean { @PostConstruct
public void init() throws Exception {
System.out.println("Student init");
} @PreDestroy
public void close() throws Exception {
System.out.println("Student close");
} @Override
public void destroy() throws Exception {
System.out.println("Student destroy");
} @Override
public void afterPropertiesSet() throws Exception {
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编程快速入门** ,这里只是大概说明一下**

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}

整合示例实现

pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.itxs</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.0-SNAPSHOT</version> <properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties> <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
</dependencies>
</project>

注解接口,通过@Import同时将ItxsImportBeanDefinitionRegistrar导入到Spring容器里

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

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

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

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

package com.itxs.dao;

import com.itxs.annotation.ItxsSelect;
import com.itxs.pojo.User; public interface UserMapper { @ItxsSelect("select * from user where user_id = #{userId}")
User selectByUserId(Integer userId);
}

service实现类

@Component
public class UserService { @Autowired
UserMapper userMapper; @Autowired
OrderMapper orderMapper; public void getUser(){
userMapper.selectByUserId(1);
orderMapper.selectByOrderId(1);
}
}

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

package com.itxs.utils;

import com.itxs.annotation.ItxsSelect;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class ItxsFactoryBean implements FactoryBean { private Class mapper; public ItxsFactoryBean(Class mapper) {
this.mapper = mapper;
} @Override
public Object getObject() throws Exception {
//使用动态代理机制
Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())){
return method.invoke(this,args);
}
ItxsSelect annotation = method.getAnnotation(ItxsSelect.class);
System.out.println("调用方法名称是"+method.getName()+",sql语句为"+annotation.value());
//后面可执行Mybatic操作数据库的相关操作
return null;
}
});
return o;
} @Override
public Class<?> getObjectType() {
return mapper;
}
}

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

package com.itxs.utils;

import com.itxs.annotation.ItxsScan;
import com.itxs.dao.OrderMapper;
import com.itxs.dao.UserMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata; import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; public class ItxsImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
//获取注解ItxsScan对象,并取出value值作为扫描的路径
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());
String mapperPath = annotationAttributes.get("value").toString();
List<Class> mappers = scan(mapperPath);
for (Class mapper : mappers) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setBeanClass(ItxsFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);
registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);
}
} private List<Class> scan(String path) {
List<Class> classList = new ArrayList<Class>();
path = path.replace(".", "/");
ClassLoader classLoader = ItxsImportBeanDefinitionRegistrar.class.getClassLoader();
URL url = classLoader.getResource(path);
File file = new File(url.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
String absolutePath = 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 (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
return classList;
}
}

Main程序

package com.itxs;

import com.itxs.config.AppConfig;
import com.itxs.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MybatisApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.getUser();
}
}

运行结果

手写Mybatis和Spring整合简单版示例窥探Spring的强大扩展能力的更多相关文章

  1. 要想精通Mybatis?从手写Mybatis框架开始吧!

    1.Mybatis组成 动态SQL Config配置 Mapper配置 2.核心源码分析 Configuration源码解析 SqlSessionFactory源码解析 SqlSession源码解析 ...

  2. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

  3. 手写mybatis框架笔记

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  4. 手写MyBatis流程

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  5. spring整合mybatisXML版

    引用的所有jar包,本demo是一个普通的java项目测试的: aopalliance.jarasm-3.3.1.jaraspectjweaver.jarcglib-2.2.2.jarcommons- ...

  6. 带码农《手写Mybatis》进度3:实现映射器的注册和使用

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!

  7. 框架源码系列十二:Mybatis源码之手写Mybatis

    一.需求分析 1.Mybatis是什么? 一个半自动化的orm框架(Object Relation Mapping). 2.Mybatis完成什么工作? 在面向对象编程中,我们操作的都是对象,Myba ...

  8. 手写mybatis框架-增加缓存&事务功能

    前言 在学习mybatis源码之余,自己完成了一个简单的ORM框架.已完成基本SQL的执行和对象关系映射.本周在此基础上,又加入了缓存和事务功能.所有代码都没有copy,如果也对此感兴趣,请赏个Sta ...

  9. spring整合ssmXML版

    以下是一个简单的ssm项目:如果中途报错,肯定是tomcat配置或者数据库配置有问题,在程序中注意将包名等配置换成自己的.数据库表需要提前建好,并加入数据,注意表结构要和实体对象对应. 1.开发条件: ...

随机推荐

  1. [前端随笔][Vue] 多级菜单实现思路——组件嵌套

    说在前面 本篇记录学习了vue-element-admin中的多级菜单的实现 [传送门] @vue/cli 4.2.2:vuex:scss:组件嵌套 正文 创建项目 npm create 项目名 // ...

  2. 美妙绝伦面向node引用-zico图标(逐浪矢量全真图标)1.9发布

    15年前,那个农村小伙初入广告行业被讥笑没有审美 于是他狠下决心,积极研发,缔就技术之核, 再后来,那些PPT和美工er们随便怎么自好,无法让其心怵. 因为他是中华人民共和国唯一具备web.cms.o ...

  3. SpringCloud远程服务调用

    笔记 在微服务中,若想要使用远程调用,需要引入spring-cloud-starter-openfeign(在使用注册中心的环境下) <dependency> <groupId> ...

  4. Windows11下的快捷键(win10通用,部分win11独有的不通用)

    给大家介绍一下win11下我常用的几个快捷键,在微软官方的文档里面都可以查到,官网链接 https://support.microsoft.com/zh-cn/windows/windows-%E7% ...

  5. PAT A1020——已知后序中序遍历求层序遍历

    1020 Tree Traversals Suppose that all the keys in a binary tree are distinct positive integers. Give ...

  6. [luogu4107]兔子和樱花

    有两个贪心:1.自底向上dfs,能删就删,这样显然是正确的,因为它最多只会造成它父亲不能删除:2.对于一个节点,优先删除其代价($c[i]+son[i]$)最大的i删除,一定最优,证明略 1 #inc ...

  7. 5、使用ZSetOperations(有序)操作redis(Zset有序集合)

    文章来源:https://www.cnblogs.com/shiguotao-com/p/10564454.html 方法 c参数 s说明   void set(K key, V value); ke ...

  8. npm 配置 registry 以及使用 nrm

    由于众所周知的原因,我们的内网链接互联网时非常不稳定,速度慢而且经常下载失败.为了提高下载安装 npm 包的体验,很多人都会把 npm 的 registry 配置成国内镜像,我们一般用的比较多的就是淘 ...

  9. AtCoder Beginner Contest 204

    身败名裂了,\(AK\)场转掉分场. 都是水题不说了. 这篇文鸽了.

  10. pcm.x代码分析

    简介 运行说明 pcm 监控结果可以分为核心.socket 和系统三部分.在核心监控部分,结果包括如下内容: • EXEC • IPC:每 CPU 周期指令数 • FREQ:普通CPU频率系数 • A ...