【从零到1编写Mini版Easy-ES】完成一个Mapper模型
作者:沈自在
代码仓库:https://gitee.com/tian-haoran/mini-easy-es
本节教程分支:https://gitee.com/tian-haoran/mini-easy-es/tree/course_02_create_mapper/
⚠️注意:本项目会持续更新,直到功能完善
1 前置知识
1.1 Spring 相关
1.1.1 什么是 FactoryBean接口?
很多同学都知道BeanFactory
接口,这个是大名鼎鼎的Spring中的核心接口,IOC的根本所在。而这个FactoryBean
的作用是用来创建一类bean,它的源代码是这样的:
public interface FactoryBean<T> {
// 获取 ObjectType 的一个对象
T getObject() throws Exception;
// 当前实现类所要创建的对象类型
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
上面这个图可以很简单的去概括这个接口的作用,就是要一个对象,然后给一个对象的逻辑。
1.1.1.1 小小的深入一点
对于 FactoryBean
接口,其实还有一个子接口,叫做SmartFactoryBean
public interface SmartFactoryBean<T> extends FactoryBean<T> {
// 最核心的一个方法 --> 如果说这里返回 true 那么则会在 Spring容器初始化的时候就将这个Bean实例化
default boolean isEagerInit() {
return false;
}
}
下面这段代码则是对于SmartFactoryBean
的迫切加载在Spring中的体现:
// 这段代码来自 DefaultListableBeanFactory -> 922 行
public void preInstantiateSingletons() throws BeansException {
// 省略部分代码
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// 核心点便是这里了
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
}
1.1.1.2 实战一把
对于FactoryBean
的使用其实只分为俩步:
- 实现
FactoryBean
接口 - 将实现类注入Bean工厂
下面这段代码来自Easy ES
的源码,同理也可以在Mybatis
的底层代码中找到类似的设计(对SqlSession的FactoryBean),这样便相当于托管给实现类创建一类Bean的能力
@Component // 注入BeanFactory
public class MapperFactoryBean<T> implements FactoryBean<T> {
private final Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
@SuppressWarnings("all")
public T getObject() throws Exception {
EsMapperProxy<T> esMapperProxy = new EsMapperProxy<>(mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, esMapperProxy);
}
// 这个便是这个FactoryBean所要创建的类型
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
}
1.1.2 BeanDefinitionRegistryPostProcessor扩展点
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}
首先看一下BeanDefinitionRegistryPostProcessor
的父类BeanFactoryPostProcessor
,您可能对BeanDefinitionRegistryPostProcessor
有些陌生,但想必对BeanFactoryPostProcessor
一定不陌生吧,这个是在Spring容器刷新时,创建完BeanFactory后会调用的后置处理器
// 代码来自AbstractApplicationContext 545 行
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 嘿哥们,请注意,所有的BeanFactory后置处理器都是在这里被调用哒
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
从上一步点进去之后就可以看到下面这段:
// 代码来自 AbstractApplicationContext 746行
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// 再从 invokeBeanFactoryPostProcessors 这里点进去
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
接下来:
// 代码来自:PostProcessorRegistrationDelegate 78行
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
// 查出来所有的 BeanDefinitionRegistryPostProcessor 后置处理器
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
}
// 执行它们!!!!
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
这便是这个扩展点的渊源啦
1.1.2.1 如何使用呢?
// 这是在手写Mini版本Easy Es 中初期的一段代码,用于替换Mapper的扫描和BeanDefinition
@Component
public class MapperScannerRegister implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 1. 扫描包
Set<Class<?>> classes = ClassScanner.scanPackage("tax.szz.mini.test.mapper");
for (Class<?> clazz : classes) {
// 1. 创建 BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(clazz);
String beanClassName = clazz.getName();
// 2. 设置 BeanName
beanDefinition.setBeanClassName(beanClassName);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
beanDefinition.setBeanClass(MapperFactoryBean.class);
registry.registerBeanDefinition(StrUtil.lowerFirst(clazz.getSimpleName()), beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
1.1.3 Spring boot 的 spring.factories 机制
Spring Boot
的 spring.factories
配置机制类似于 Java SPI,工程代码中在 META-INF/spring.factories
文件中配置接口的实现类名称,然后 Spring Boot
在启动时扫描该配置文件并实例化配置文件中的Bean
比如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
tax.szz.mini.core.EsAutoConfiguration
Spring框架则会自己去扫描这个文件夹并把配置的这个类加载并且实例化
// 代码来自 SpringFactoriesLoader 95行 (如果你看过Dubbo SPI部分的源码的话会发现逻辑其实大差不差的)
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
// 同时在这里你会发现,Spring会讲这些扫描到的类一个一个全部实例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
而这也是SpringBoot插件机制的来源(Starter)
2.2 动态代理相关
2.2.1 什么是动态代理?
动态代理是指在运行时创建代理对象的过程,而不是在编译时确定。JDK动态代理利用Java的反射机制,在运行时动态生成代理类和代理对象,从而实现代理功能。这意味着我们可以在运行时为任何接口创建代理对象,而无需手动编写代理类。
2.2.2 如何使用JDK动态代理?
使用JDK动态代理非常简单,只需遵循以下几个步骤:
- 定义一个接口:首先,我们需要定义一个接口,该接口将成为代理对象和被代理对象之间的契约。接口应包含代理对象和被代理对象共同的方法。
- 实现被代理类:创建一个实现接口的被代理类,该类将包含实际的业务逻辑。
- 创建InvocationHandler:实现
InvocationHandler
接口,并重写invoke
方法。invoke
方法将在代理对象的方法被调用时执行,我们可以在该方法中添加额外的逻辑。 - 创建代理对象:使用
Proxy
类的newProxyInstance
方法创建代理对象。该方法接受三个参数:类加载器、接口数组和InvocationHandler
对象。通过调用该方法,我们将得到一个实现了指定接口的代理对象。 - 使用代理对象:现在,我们可以使用代理对象来调用接口中定义的方法。代理对象会在调用方法时自动触发
InvocationHandler
的invoke
方法,从而允许我们在方法调用前后添加自定义的逻辑。
2.2.3 一个简单的案例
// 这段代码来自 Easy ES 中,可以直接 双击 shift 查到
// 而这段代码的作用就是对 Mapper 进行代理,同时可以在MapperFactoryBean(下面那个代码块)中也可以看到它的核心逻辑就是 (定义Mapper 接口 -> 创建 Mapper 代理)
public class EsMapperProxy<T> implements InvocationHandler, Serializable {
private final Class<T> mapperInterface;
public EsMapperProxy(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BaseEsMapper<?> baseEsMapper = new BaseEsMapperImpl<>();
return method.invoke(baseEsMapper, args);
}
}
public class MapperFactoryBean<T> implements FactoryBean<T> {
private final Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
@SuppressWarnings("all")
public T getObject() throws Exception {
EsMapperProxy<T> esMapperProxy = new EsMapperProxy<>(mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, esMapperProxy);
}
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
}
2.2.4 动态代理的优势和应用场景
使用JDK动态代理有以下几个优势:
- 灵活性:动态代理允许我们在运行时为任意接口创建代理对象,无需手动编写代理类,从而提供了更大的灵活性。
- 解耦合:代理模式可以将代理对象和被代理对象解耦,使得它们可以独立进行修改和扩展。
-
横切关注点处理:动态代理可以用于处理横切关注点,例如日志记录、性能监控、事务管理等。我们可以通过在
InvocationHandler
的invoke
方法中添加相应的逻辑来实现这些功能。
JDK动态代理在以下场景中特别有用:
-
日志记录:通过在
InvocationHandler
中添加日志记录逻辑,我们可以方便地记录方法的调用信息,用于调试和分析。 -
事务管理:通过在
InvocationHandler
中添加事务管理逻辑,我们可以实现对方法的事务性控制,例如开启事务、提交事务、回滚事务等。 -
权限控制:通过在
InvocationHandler
中添加权限验证逻辑,我们可以对方法的调用进行权限控制,以确保只有具备相应权限的用户才能执行特定操作。
2 Mapper 模型设计
首先我们去分析一下要创建一个Mapper的映射需要做哪些工作:
- 第一:需要扫描到 Mapper
- 第二:Mapper 只是一个接口,我们需要提供一个实际操作(肯定是动态代理啦)
工程结构如下:
.
| |____src
| | |____main
| | | |____resources
| | | |____java
|____pom.xml
|____mini-easy-es-core
| |____src
| | |____main
| | | |____resources
| | | | |____META-INF
| | | | | |____spring.factories
| | | |____java
| | | | |____tax
| | | | | |____szz
| | | | | | |____mini
| | | | | | | |____core
| | | | | | | | |____core
| | | | | | | | | |____BaseEsMapper.java
| | | | | | | | | |____BaseEsMapperImpl.java
| | | | | | | | |____proxy
| | | | | | | | | |____EsMapperProxy.java
| | | | | | | | |____register
| | | | | | | | | |____MapperFactoryBean.java
| | | | | | | | |____factory
| | | | | | | | | |____MapperScannerRegister.java
| | | | | | | | |____EsAutoConfiguration.java
|____mini-easy-es-test
| |____src
| | |____test
| | | |____java
| | | | |____tax
| | | | | |____szz
| | | | | | |____mini
| | | | | | | |____test
| | | | | | | | |____TestSpringApplication.java
| | | | | | | | |____api
| | | | | | | | | |____course_02
| | | | | | | | | | |____ApiTest.java
| | |____main
| | | |____java
| | | | |____tax
| | | | | |____szz
| | | | | | |____mini
| | | | | | | |____test
| | | | | | | | |____mapper
| | | | | | | | | |____DocumentMapper.java
| | | | | | | | |____document
| | | | | | | | | |____Document.java
2.1 抽象Mapper
众所周知,一个Mapper其实就是一个接口,比如我们在使用MybatisPlus时候,可能会去继承BaseMapper
以获得一些基础功能比如:
- selectOne()
- save()
- 。。。
那我们也借鉴这种思想去定义一个 BaseEsMapper
public interface BaseEsMapper<T> {
// 粗浅定义一个方法去创建索引
Boolean createIndex(String indexName);
}
接下来有了基础方法,那么肯定需要对方法进行实现啦,我们编写一个BaseEsMapperImpl
对此进行实现
public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
@Override
public Boolean createIndex(String indexName) {
System.out.println("创建 Index");
System.out.println("indexName = " + indexName);
return Boolean.TRUE;
}
}
okok stop, 听我say一下
我们在用Mybatis Plus的时候是不是经常会有这样的写法:
public class ApiTest {
@Autowired
private UserMapper userMapper;
}
对,没错,Mapper是要注入到Spring容器当中的,只有这样,我们才可以用注解去自动注入
**(敲黑板)⚠️注意:**现在我们的BaseEsMapper还没有和我们将来自己的业务中的Mapper产生关系。那么缕一下思路,我们还差什么:
- 业务Mapper要和BaseEsMapper产生一个关系
- Mapper要注入到Spring中
那么这个绑定关系就要用动态代理来维持了,比如下面这种实现
// 我们对 Mapper 接口代理和 BaseMapper 产生一个代理绑定关系
public class EsMapperProxy<T> implements InvocationHandler, Serializable {
private final Class<T> mapperInterface;
public EsMapperProxy(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BaseEsMapper<?> baseEsMapper = new BaseEsMapperImpl<>();
return method.invoke(baseEsMapper, args);
}
}
接下来就是要把这种关系交给Spring来维持:
所谓维持就分为俩个点:
- Mapper对象的创建
- Mapper的注入
首先解决第一个问题,创建一个Mapper对象怎么办,很明显这是一类对象的创建,这个特点不就和 FactoryBean
很类似嘛
// 这下当要去获取某个Mapper接口的时候,不久会调用getObject()拿到我们提供的Mapper和BaseMapper之间的绑定代理了嘛,神奇的一批
public class MapperFactoryBean<T> implements FactoryBean<T> {
private final Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
@SuppressWarnings("all")
public T getObject() throws Exception {
EsMapperProxy<T> esMapperProxy = new EsMapperProxy<>(mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, esMapperProxy);
}
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
}
那么现在不就剩下了最后一步——注册Mapper了嘛,请看下文~~
2.2 注册Mapper
对于注册Mapper一般会有俩个方法:
- 自定义Scanner,比如去继承
ClassPathBeanDefinitionScanner
然后重写 **doScan()**方法 - 实现
BeanDefinitionRegistryPostProcessor
,自己去扫描Mapper接口然后封装BeanDefinition
注册
都可以解决问题,这里先暂且用BeanDefinitionRegistryPostProcessor顶着,这样逻辑会更清晰一点
public class MapperScannerRegister implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 1. 扫描包
Set<Class<?>> classes = ClassScanner.scanPackage("tax.szz.mini.test.mapper");
for (Class<?> clazz : classes) {
// 1. 创建 BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(clazz);
String beanClassName = clazz.getName();
// 2. 设置 Bean的一些属性
beanDefinition.setBeanClassName(beanClassName);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// ⚠️注意这里(偷梁换柱),假设这里是UserMapper 那么这样不就会在获取 userMapper Bean的时候去调用MapperFactoryBean去拿对象啦,一环扣一环就这样建立了联系
beanDefinition.setBeanClass(MapperFactoryBean.class);
// 3. 注册 BeanDefinition
registry.registerBeanDefinition(StrUtil.lowerFirst(clazz.getSimpleName()), beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
2.3 自动注册
在resources
中创建目录META-INF
并且在该目录下添加文件spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
tax.szz.mini.core.EsAutoConfiguration
// 这样就完成了一个基本的自动加载
@Configuration
public class EsAutoConfiguration {
@Bean
public MapperScannerRegister mapperScannerRegister() {
return new MapperScannerRegister();
}
}
2.4 测试一下
下面是测试工程结构:
.
|____src
| |____test
| | |____java
| | | |____tax
| | | | |____szz
| | | | | |____mini
| | | | | | |____test
| | | | | | | |____TestSpringApplication.java
| | | | | | | |____api
| | | | | | | | |____course_02
| | | | | | | | | |____ApiTest.java
| |____main
| | |____java
| | | |____tax
| | | | |____szz
| | | | | |____mini
| | | | | | |____test
| | | | | | | |____mapper
| | | | | | | | |____DocumentMapper.java
| | | | | | | |____document
| | | | | | | | |____Document.java
public class Document {
}
public interface DocumentMapper extends BaseEsMapper<DocumentMapper> {
}
@Disabled
@SpringBootTest(classes = TestSpringApplication.class)
public class ApiTest {
@Autowired
private DocumentMapper documentMapper;
@Test
void test(){
documentMapper.createIndex("hello");
}
}
@SpringBootApplication
public class TestSpringApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringApplication.class, args);
}
}
那么执行这个测试的结果就是:文章来源:https://www.toymoban.com/news/detail-766556.html
文章来源地址https://www.toymoban.com/news/detail-766556.html
到了这里,关于【别再做XX外卖啦!和我从零到1编写Mini版Easy-ES】完成一个Mapper模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!