【從零到1編寫Mini版Easy-ES】完成一個Mapper模型
作者:沈自在
代碼倉庫:https://gitee.com/tian-haoran/mini-easy-es
本節(jié)教程分支:https://gitee.com/tian-haoran/mini-easy-es/tree/course_02_create_mapper/
??注意:本項目會持續(xù)更新,直到功能完善
1 前置知識
1.1 Spring 相關(guān)
1.1.1 什么是 FactoryBean接口?
很多同學都知道BeanFactory
接口,這個是大名鼎鼎的Spring中的核心接口,IOC的根本所在。而這個FactoryBean
的作用是用來創(chuàng)建一類bean,它的源代碼是這樣的:
public interface FactoryBean<T> {
// 獲取 ObjectType 的一個對象
T getObject() throws Exception;
// 當前實現(xiàn)類所要創(chuàng)建的對象類型
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中的體現(xiàn):
// 這段代碼來自 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 實戰(zhàn)一把
對于FactoryBean
的使用其實只分為倆步:
- 實現(xiàn)
FactoryBean
接口 - 將實現(xiàn)類注入Bean工廠
下面這段代碼來自Easy ES
的源碼,同理也可以在Mybatis
的底層代碼中找到類似的設計(對SqlSession的FactoryBean),這樣便相當于托管給實現(xiàn)類創(chuàng)建一類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所要創(chuàng)建的類型
@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容器刷新時,創(chuàng)建完BeanFactory后會調(diào)用的后置處理器
// 代碼來自AbstractApplicationContext 545 行
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 嘿哥們,請注意,所有的BeanFactory后置處理器都是在這里被調(diào)用噠
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);
}
}
}
// 執(zhí)行它們?。。。?/span>
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. 創(chuàng)建 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
文件中配置接口的實現(xiàn)類名稱,然后 Spring Boot
在啟動時掃描該配置文件并實例化配置文件中的Bean
比如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
tax.szz.mini.core.EsAutoConfiguration
Spring框架則會自己去掃描這個文件夾并把配置的這個類加載并且實例化
// 代碼來自 SpringFactoriesLoader 95行 (如果你看過Dubbo SPI部分的源碼的話會發(fā)現(xiàn)邏輯其實大差不差的)
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) {
// 同時在這里你會發(fā)現(xiàn),Spring會講這些掃描到的類一個一個全部實例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
而這也是SpringBoot插件機制的來源(Starter)
2.2 動態(tài)代理相關(guān)
2.2.1 什么是動態(tài)代理?
動態(tài)代理是指在運行時創(chuàng)建代理對象的過程,而不是在編譯時確定。JDK動態(tài)代理利用Java的反射機制,在運行時動態(tài)生成代理類和代理對象,從而實現(xiàn)代理功能。這意味著我們可以在運行時為任何接口創(chuàng)建代理對象,而無需手動編寫代理類。
2.2.2 如何使用JDK動態(tài)代理?
使用JDK動態(tài)代理非常簡單,只需遵循以下幾個步驟:
- 定義一個接口:首先,我們需要定義一個接口,該接口將成為代理對象和被代理對象之間的契約。接口應包含代理對象和被代理對象共同的方法。
- 實現(xiàn)被代理類:創(chuàng)建一個實現(xiàn)接口的被代理類,該類將包含實際的業(yè)務邏輯。
- 創(chuàng)建InvocationHandler:實現(xiàn)
InvocationHandler
接口,并重寫invoke
方法。invoke
方法將在代理對象的方法被調(diào)用時執(zhí)行,我們可以在該方法中添加額外的邏輯。 - 創(chuàng)建代理對象:使用
Proxy
類的newProxyInstance
方法創(chuàng)建代理對象。該方法接受三個參數(shù):類加載器、接口數(shù)組和InvocationHandler
對象。通過調(diào)用該方法,我們將得到一個實現(xiàn)了指定接口的代理對象。 - 使用代理對象:現(xiàn)在,我們可以使用代理對象來調(diào)用接口中定義的方法。代理對象會在調(diào)用方法時自動觸發(fā)
InvocationHandler
的invoke
方法,從而允許我們在方法調(diào)用前后添加自定義的邏輯。
2.2.3 一個簡單的案例
// 這段代碼來自 Easy ES 中,可以直接 雙擊 shift 查到
// 而這段代碼的作用就是對 Mapper 進行代理,同時可以在MapperFactoryBean(下面那個代碼塊)中也可以看到它的核心邏輯就是 (定義Mapper 接口 -> 創(chuàng)建 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 動態(tài)代理的優(yōu)勢和應用場景
使用JDK動態(tài)代理有以下幾個優(yōu)勢:
- 靈活性:動態(tài)代理允許我們在運行時為任意接口創(chuàng)建代理對象,無需手動編寫代理類,從而提供了更大的靈活性。
- 解耦合:代理模式可以將代理對象和被代理對象解耦,使得它們可以獨立進行修改和擴展。
-
橫切關(guān)注點處理:動態(tài)代理可以用于處理橫切關(guān)注點,例如日志記錄、性能監(jiān)控、事務管理等。我們可以通過在
InvocationHandler
的invoke
方法中添加相應的邏輯來實現(xiàn)這些功能。
JDK動態(tài)代理在以下場景中特別有用:
-
日志記錄:通過在
InvocationHandler
中添加日志記錄邏輯,我們可以方便地記錄方法的調(diào)用信息,用于調(diào)試和分析。 -
事務管理:通過在
InvocationHandler
中添加事務管理邏輯,我們可以實現(xiàn)對方法的事務性控制,例如開啟事務、提交事務、回滾事務等。 -
權(quán)限控制:通過在
InvocationHandler
中添加權(quán)限驗證邏輯,我們可以對方法的調(diào)用進行權(quán)限控制,以確保只有具備相應權(quán)限的用戶才能執(zhí)行特定操作。
2 Mapper 模型設計
首先我們?nèi)シ治鲆幌乱獎?chuàng)建一個Mapper的映射需要做哪些工作:
- 第一:需要掃描到 Mapper
- 第二:Mapper 只是一個接口,我們需要提供一個實際操作(肯定是動態(tài)代理啦)
工程結(jié)構(gòu)如下:
.
| |____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> {
// 粗淺定義一個方法去創(chuàng)建索引
Boolean createIndex(String indexName);
}
接下來有了基礎方法,那么肯定需要對方法進行實現(xiàn)啦,我們編寫一個BaseEsMapperImpl
對此進行實現(xiàn)
public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
@Override
public Boolean createIndex(String indexName) {
System.out.println("創(chuàng)建 Index");
System.out.println("indexName = " + indexName);
return Boolean.TRUE;
}
}
okok stop, 聽我say一下
我們在用Mybatis Plus的時候是不是經(jīng)常會有這樣的寫法:
public class ApiTest {
@Autowired
private UserMapper userMapper;
}
對,沒錯,Mapper是要注入到Spring容器當中的,只有這樣,我們才可以用注解去自動注入
**(敲黑板)??注意:**現(xiàn)在我們的BaseEsMapper還沒有和我們將來自己的業(yè)務中的Mapper產(chǎn)生關(guān)系。那么縷一下思路,我們還差什么:
- 業(yè)務Mapper要和BaseEsMapper產(chǎn)生一個關(guān)系
- Mapper要注入到Spring中
那么這個綁定關(guān)系就要用動態(tài)代理來維持了,比如下面這種實現(xiàn)
// 我們對 Mapper 接口代理和 BaseMapper 產(chǎn)生一個代理綁定關(guān)系
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);
}
}
接下來就是要把這種關(guān)系交給Spring來維持:
所謂維持就分為倆個點:
- Mapper對象的創(chuàng)建
- Mapper的注入
首先解決第一個問題,創(chuàng)建一個Mapper對象怎么辦,很明顯這是一類對象的創(chuàng)建,這個特點不就和 FactoryBean
很類似嘛
// 這下當要去獲取某個Mapper接口的時候,不久會調(diào)用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;
}
}
那么現(xiàn)在不就剩下了最后一步——注冊Mapper了嘛,請看下文~~
2.2 注冊Mapper
對于注冊Mapper一般會有倆個方法:
- 自定義Scanner,比如去繼承
ClassPathBeanDefinitionScanner
然后重寫 **doScan()**方法 - 實現(xiàn)
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. 創(chuàng)建 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的時候去調(diào)用MapperFactoryBean去拿對象啦,一環(huán)扣一環(huán)就這樣建立了聯(lián)系
beanDefinition.setBeanClass(MapperFactoryBean.class);
// 3. 注冊 BeanDefinition
registry.registerBeanDefinition(StrUtil.lowerFirst(clazz.getSimpleName()), beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
2.3 自動注冊
在resources
中創(chuàng)建目錄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 測試一下
下面是測試工程結(jié)構(gòu):
.
|____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);
}
}
那么執(zhí)行這個測試的結(jié)果就是:文章來源:http://www.zghlxwxcb.cn/news/detail-766556.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-766556.html
到了這里,關(guān)于【別再做XX外賣啦!和我從零到1編寫Mini版Easy-ES】完成一個Mapper模型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!