Dubbo3和Spring?Boot整合過程源碼解析
前言
Dubbo3 已經(jīng)從一開始的 RPC 框架改頭換面,現(xiàn)在的定位是微服務(wù)框架,除了提供基本的 RPC 功能外,它還提供了一整套的服務(wù)治理方案。Dubbo 有自身的一套設(shè)計(jì)體系,不過通常很少單獨(dú)使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源碼,版本是 3.1.10,Github 地址:https://github.com/apache/dubbo/tree/dubbo-3.1.10。
Dubbo3 專門提供了一個(gè)模塊來和 Spring Boot 做整合,模塊名是 dubbo-spring-boot ,下面有4個(gè)子模塊:
dubbo-spring-boot -> dubbo-spring-boot-actuator -> dubbo-spring-boot-autoconfigure -> dubbo-spring-boot-compatible -> dubbo-spring-boot-starter
一般我們直接引入 dubbo-spring-boot-starter 即可開啟 Dubbo 的自動(dòng)裝配,在 Spring Boot 項(xiàng)目里使用 Dubbo。
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.1.10</version>
</dependency>所以我們以這個(gè)模塊為切入點(diǎn),看看 Dubbo 做了什么。
入口模塊
dubbo-spring-boot-starter 模塊沒有代碼,只有一個(gè) pom.xml 引入了幾個(gè)必要的依賴:
<dependencies>
<!-- Spring Boot 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志 依賴 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2_version}</version>
<optional>true</optional>
</dependency>
<!-- Dubbo 自動(dòng)裝配 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>我們直接看 dubbo-spring-boot-autoconfigure 模塊:
src/main -> java/org/apache/dubbo/spring/boot/autoconfigure ---> BinderDubboConfigBinder.java ---> DubboRelaxedBinding2AutoConfiguration.java -> resources/META-INF ---> spring.factories
這里用到了 Spring Boot 的自動(dòng)裝配功能,顯然就兩個(gè)類是無法實(shí)現(xiàn)如此復(fù)雜的整合的,再看 pom.xml 發(fā)現(xiàn)該模塊又依賴了 dubbo-spring-boot-autoconfigure-compatible 模塊:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-autoconfigure-compatible</artifactId>
<version>${project.version}</version>
</dependency>該模塊的 spring.factories 配置了一堆組件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,\ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration,\ org.apache.dubbo.spring.boot.autoconfigure.DubboListenerAutoConfiguration org.springframework.context.ApplicationListener=\ org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener org.springframework.boot.env.EnvironmentPostProcessor=\ org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor org.springframework.context.ApplicationContextInitializer=\ org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
OK,到這里看源碼的思路基本就有了,Dubbo 利用 Spring Boot 自動(dòng)裝配的功能,提供了兩個(gè)自動(dòng)裝配的模塊,配置了一堆自動(dòng)裝配的組件,通過這些組件來和 Spring Boot 做整合。
服務(wù)發(fā)布
Dubbo3 整合 Spring 后,如何將加了 @DubboService 的服務(wù)發(fā)布出去?
服務(wù)發(fā)布的關(guān)鍵節(jié)點(diǎn):
DubboAutoConfiguration#serviceAnnotationBeanProcessor ServiceBean后置處理器
ServiceAnnotationPostProcessor#postProcessBeanDefinitionRegistry
ServiceAnnotationPostProcessor#scanServiceBeans 掃描ServiceBean
DubboClassPathBeanDefinitionScanner#scan
ServiceAnnotationPostProcessor#processScannedBeanDefinition 處理BeanDefinition
ServiceAnnotationPostProcessor#buildServiceBeanDefinition 構(gòu)建ServiceBeanDefinition
BeanDefinitionRegistry#registerBeanDefinition 注冊(cè)BeanDefinition
ServiceBean#afterPropertiesSet
ModuleConfigManager#addService 交給ModuleConfigManager管理 此時(shí)還沒啟動(dòng)服務(wù)
DubboDeployApplicationListener#onApplicationEvent Spring事件監(jiān)聽
DubboDeployApplicationListener#onContextRefreshedEvent ContextRefreshed事件
DefaultModuleDeployer#start 模塊部署啟動(dòng)
DefaultModuleDeployer#exportServices 啟動(dòng)服務(wù) 暴露服務(wù)
DefaultModuleDeployer#referServices 引用服務(wù)AutoConfiguration
先看 dubbo-spring-boot-autoconfigure 模塊自動(dòng)裝配的配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
DubboRelaxedBinding2AutoConfiguration 是 Dubbo 提供的針對(duì) Spring Boot 2.0 的自動(dòng)裝配類,它先于 DubboRelaxedBindingAutoConfiguration 執(zhí)行,如果你用的是 Spring Boot 1.x 版本,則會(huì)觸發(fā)后者。核心:讀取 dubbo.scan.basePackages 配置,獲取要掃描的包路徑。
@ConditionalOnMissingBean(name = BASE_PACKAGES_BEAN_NAME)
@Bean(name = BASE_PACKAGES_BEAN_NAME)
public Set<String> dubboBasePackages(ConfigurableEnvironment environment) {
// 讀取 dubbo.scan.basePackages 掃描包路徑 注冊(cè)Set到Spring容器
PropertyResolver propertyResolver = dubboScanBasePackagesPropertyResolver(environment);
return propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
}
@ConditionalOnMissingBean(name = RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME, value = ConfigurationBeanBinder.class)
@Bean(RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME)
@Scope(scopeName = SCOPE_PROTOTYPE)
public ConfigurationBeanBinder relaxedDubboConfigBinder() {
return new BinderDubboConfigBinder();
}再看 DubboAutoConfiguration,它主要做的事:
- 注入 ServiceAnnotationPostProcessor,處理 ServiceBean
- DubboSpringInitializer 的初始化,注冊(cè)一些核心bean
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@Configuration
@AutoConfigureAfter(DubboRelaxedBindingAutoConfiguration.class)
@EnableConfigurationProperties(DubboConfigurationProperties.class)
@EnableDubboConfig// 引入 DubboConfigConfigurationRegistrar
public class DubboAutoConfiguration {
@ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
@ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME)
@Bean
public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME)
Set<String> packagesToScan) {
// 獲取掃描的包路徑
// 注入 ServiceBean 后置處理器
return new ServiceAnnotationPostProcessor(packagesToScan);
}
}ServiceAnnotationPostProcessor
Dubbo 對(duì)外提供的服務(wù),在 Spring 里面被封裝為 org.apache.dubbo.config.spring.ServiceBean 。

它繼承自 ServiceConfig,所以它是一個(gè)標(biāo)準(zhǔn)的 Dubbo Service,在整合 Spring 方面做了擴(kuò)展。ServiceAnnotationPostProcessor 是 Spring BeanFactory 的后置處理器,它的職責(zé)是:掃描 DubboService Bean,注冊(cè)到 Spring 容器。拿到需要掃描的包路徑,然后掃描 DubboService Bean:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
// 掃描 DubboService Bean
scanServiceBeans(resolvedPackagesToScan, registry);
}Dubbo 會(huì)在類路徑下掃描,所以會(huì) new 一個(gè) DubboClassPathBeanDefinitionScanner 掃描器,它依賴于 Spring 的 ClassPathBeanDefinitionScanner,將類路徑下加了 @DubboService 、 @Service 的類封裝成 BeanDefinition,然后注冊(cè)到容器:
private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 實(shí)例化一個(gè)ClassPath掃描器
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 掃描bean的包含規(guī)則 有@DubboService @Service注解
for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) {
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
}
// 排除規(guī)則
ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter();
scanner.addExcludeFilter(scanExcludeFilter);
for (String packageToScan : packagesToScan) {
// 掃描包路徑
scanner.scan(packageToScan);
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
// 處理掃描到的BeanDefinition -> 注冊(cè)
processScannedBeanDefinition(beanDefinitionHolder);
servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());
}
servicePackagesHolder.addScannedPackage(packageToScan);
}
}ServiceBean 注冊(cè)到容器了,Spring 會(huì)正常實(shí)例化 bean。又因?yàn)?ServiceBean 實(shí)現(xiàn)了 InitializingBean 接口,所以會(huì)觸發(fā)它的 afterPropertiesSet() ,ServiceBean 會(huì)把自己交給 ModuleConfigManager 管理。
Tips:服務(wù)現(xiàn)在還沒啟動(dòng),目前只是先收集 ServiceBean。
public void afterPropertiesSet() throws Exception {
if (StringUtils.isEmpty(getPath())) {
if (StringUtils.isNotEmpty(getInterface())) {
setPath(getInterface());
}
}
//register service bean
ModuleModel moduleModel = DubboBeanUtils.getModuleModel(applicationContext);
// 交給 ModuleConfigManager 管理
moduleModel.getConfigManager().addService(this);
moduleModel.getDeployer().setPending();
}DubboDeployApplicationListener
DubboConfigConfigurationRegistrar 會(huì)初始化 DubboSpringInitializer,初始化的時(shí)候會(huì)向 Spring 容器注冊(cè)一堆 bean,其中就包含 DubboDeployApplicationListener。它是 Spring 應(yīng)用程序的事件監(jiān)聽器,它監(jiān)聽到 ContextRefreshedEvent 事件會(huì)啟動(dòng) Dubbo 服務(wù);監(jiān)聽到 ContextClosedEvent 事件關(guān)閉 Dubbo 服務(wù)。
public void onApplicationEvent(ApplicationContextEvent event) {
if (nullSafeEquals(applicationContext, event.getSource())) {
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
}啟動(dòng)服務(wù)其實(shí)就是觸發(fā) ModuleDeployer#start() ,Dubbo 會(huì)啟動(dòng)相關(guān)組件,然后暴露服務(wù)和應(yīng)用服務(wù)。
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
ModuleDeployer deployer = moduleModel.getDeployer();
Assert.notNull(deployer, "Module deployer is null");
// 啟動(dòng)模塊部署
Future future = deployer.start();
if (!deployer.isBackground()) {
try {
// 等待啟動(dòng)完成
future.get();
} catch (InterruptedException e) {
logger.warn(CONFIG_FAILED_START_MODEL, "", "", "Interrupted while waiting for dubbo module start: " + e.getMessage());
} catch (Exception e) {
logger.warn(CONFIG_FAILED_START_MODEL, "", "", "An error occurred while waiting for dubbo module start: " + e.getMessage(), e);
}
}
}至此,服務(wù)啟動(dòng)完畢。
服務(wù)引用
Dubbo3 整合 Spring 后,如何注入加了 @DubboReference 的屬性/方法,完成服務(wù)引用?
服務(wù)引用的關(guān)鍵節(jié)點(diǎn):
DubboConfigConfigurationRegistrar#registerBeanDefinitions
DubboSpringInitializer#initialize Dubbo引用初始化
DubboBeanUtils#registerCommonBeans 注冊(cè)公共bean
ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory 遍歷BeanDefinition
AbstractAnnotationBeanPostProcessor#findInjectionMetadata 查找注入點(diǎn)
ReferenceAnnotationBeanPostProcessor#prepareInjection 準(zhǔn)備注入
ReferenceAnnotationBeanPostProcessor#registerReferenceBean 注冊(cè)ReferenceBean
ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues bean屬性值后置處理
AnnotatedInjectElement#inject 注入
ReferenceBean#getObject 獲取注入對(duì)象
ReferenceBean#createLazyProxy 創(chuàng)建延遲代理
DubboReferenceLazyInitTargetSource#createObject 調(diào)用bean時(shí)才創(chuàng)建真正的Proxy
ReferenceConfig#get 引用服務(wù) 創(chuàng)建ProxyReferenceAnnotationBeanPostProcessor
DubboSpringInitializer 初始化的時(shí)候會(huì)注冊(cè) ReferenceAnnotationBeanPostProcessor 類,它的職責(zé)是完成 @DubboReference 依賴的注入。

它是 BeanFactoryPostProcessor 的子類,是 Spring BeanFactory 的后置處理器,Spring 啟動(dòng)時(shí)會(huì)觸發(fā) postProcessBeanFactory() 。首先是遍歷容器中所有的 BeanDefinition,解析 BeanClass 上的屬性或方法是否有加相關(guān)注解,也就是尋找注入點(diǎn)。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 遍歷所有已注冊(cè)的BeanDefinition > 查找注入點(diǎn) > 準(zhǔn)備注入
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
Class<?> beanType;
if (beanFactory.isFactoryBean(beanName)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanType != null) {
// 查找注入點(diǎn)
AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
// 準(zhǔn)備注入
prepareInjection(metadata);
}
}
}AnnotatedInjectionMetadata 元數(shù)據(jù)解析完以后,Spring 會(huì)把注入點(diǎn)封裝成 ReferenceBean,同時(shí)注冊(cè)到 Spring 容器,AnnotatedFieldElement 只是記錄一下引用的 beanName,后續(xù)依賴注入時(shí)就可以直接從 Spring 容器獲取對(duì)應(yīng)的 bean 實(shí)例了。
protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException {
try {
// 遍歷注入點(diǎn) 注冊(cè) ReferenceBean 此時(shí)記錄的只是一個(gè)referenceBeanName 還沒注入
for (AnnotatedFieldElement fieldElement : metadata.getFieldElements()) {
if (fieldElement.injectedObject != null) {
continue;
}
Class<?> injectedType = fieldElement.field.getType();
AnnotationAttributes attributes = fieldElement.attributes;
String referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field);
fieldElement.injectedObject = referenceBeanName;
// 緩存起來
injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName);
}
}
}postProcessPropertyValues() 才是真正的依賴注入:
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
try {
// 已經(jīng)解析過了,這里直接從緩存獲取
AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
prepareInjection(metadata);
// 依賴注入
metadata.inject(bean, beanName, pvs);
}
return pvs;
}AnnotatedInjectionMetadata 會(huì)把收集到的注入點(diǎn) 挨個(gè)進(jìn)行注入,最終調(diào)用 AnnotatedInjectElement#inject() 。
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
// ReferenceBean#getObject() 拿到一個(gè)延遲代理對(duì)象
Object injectedObject = getInjectedObject(attributes, bean, beanName, getInjectedType(), this);
if (member instanceof Field) {// 屬性注入
Field field = (Field) member;
ReflectionUtils.makeAccessible(field);
field.set(bean, injectedObject);
} else if (member instanceof Method) {// 方法注入
Method method = (Method) member;
ReflectionUtils.makeAccessible(method);
method.invoke(bean, injectedObject);
}
}ReferenceBean

依賴注入的對(duì)象被封裝為 ReferenceBean,它是一個(gè) FactoryBean,所以在注入的時(shí)候,其實(shí)會(huì)調(diào)用 ReferenceBean#getObject() 獲取 bean 實(shí)例。
public T getObject() {
if (lazyProxy == null) {
createLazyProxy();
}
return (T) lazyProxy;
}Dubbo 這里并沒有直接去引用遠(yuǎn)程服務(wù),而是先創(chuàng)建了一個(gè) LazyProxy,Dubbo 希望在真正發(fā)起 RPC 調(diào)用時(shí)才去引用服務(wù),為什么這么做呢?LazyProxy 的創(chuàng)建過程,利用 JDK 動(dòng)態(tài)代理,創(chuàng)建了一個(gè)空殼的代理對(duì)象:
private void createLazyProxy() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
proxyFactory.addInterface(interfaceClass);
Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces();
for (Class<?> anInterface : internalInterfaces) {
proxyFactory.addInterface(anInterface);
}
if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) {
try {
Class<?> serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader);
proxyFactory.addInterface(serviceInterface);
} catch (ClassNotFoundException e) {
}
}
this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
}調(diào)用 LazyProxy 的方法會(huì)觸發(fā) JdkDynamicAopProxy#invoke() ,它會(huì)在第一次調(diào)用非 Object 方法時(shí)創(chuàng)建實(shí)際的對(duì)象。創(chuàng)建實(shí)際的對(duì)象由 DubboReferenceLazyInitTargetSource 完成:
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
@Override
protected Object createObject() throws Exception {
// 首次調(diào)用 ReferenceBean 方法才會(huì)創(chuàng)建
return getCallProxy();
}
@Override
public synchronized Class<?> getTargetClass() {
return getInterfaceClass();
}
}getCallProxy() 其實(shí)就是調(diào)用 ReferenceConfig#get() 引用服務(wù)。
private Object getCallProxy() throws Exception {
if (referenceConfig == null) {
throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
}
synchronized (((DefaultSingletonBeanRegistry)getBeanFactory()).getSingletonMutex()) {
return referenceConfig.get();
}
}尾巴
Dubbo3 和 Spring Boot 整合的過程可謂是一波三折,過程還是挺繞的,除了要了解 Dubbo 的底層原理,還要對(duì) Spring Boot 的原理、各種后置處理器很熟悉才行。Dubbo 首先是提供了一個(gè)單獨(dú)的模塊來和 Spring Boot 做整合,利用 Spring Boot 自動(dòng)裝配的功能,配置了一堆自動(dòng)裝配的組件。首先是讀取到要掃描的包路徑,然后掃描 DubboService Bean,并把它注冊(cè)到 Spring 容器,而后統(tǒng)一交給 ModuleConfigManager 管理;再通過監(jiān)聽 ContextRefreshedEvent 事件來完成服務(wù)的啟動(dòng)和發(fā)布。針對(duì) DubboReference 的注入,則依賴 ReferenceAnnotationBeanPostProcessor 后置處理器,它會(huì)遍歷 Spring 容器內(nèi)所有的 BeanDefinition,然后查找注入點(diǎn),把注入點(diǎn)封裝成 ReferenceBean 并注冊(cè)到 Spring 容器,依賴注入時(shí)再通過容器獲取對(duì)應(yīng)的 bean 實(shí)例。Dubbo 采用的是延遲注入的方式,默認(rèn)會(huì)注入一個(gè)空殼的 LazyProxy 對(duì)象,在首次調(diào)用 RPC 方法時(shí)再去調(diào)用底層的服務(wù)引用方法。
到此這篇關(guān)于Dubbo3和Spring Boot整合過程的文章就介紹到這了,更多相關(guān)Dubbo3和Spring Boot整合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn)
最近在對(duì)一個(gè)集合列表的數(shù)據(jù)進(jìn)行排序,需求是要集合數(shù)據(jù)按照一個(gè)排序狀態(tài)值進(jìn)行排序,而這個(gè)狀態(tài)值,不是按照從小到大這樣的順序排序的,而是要按照特定的順序,所以本文給大家介紹了Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn),需要的朋友可以參考下2024-07-07
基于Transactional事務(wù)的使用以及注意說明
這篇文章主要介紹了Transactional事務(wù)的使用以及注意說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Elasticsearch查詢Range Query語法示例
這篇文章主要為大家介紹了Elasticsearch查詢Range Query語法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析
這篇文章主要為大家介紹了java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Java Mybatis框架多表操作與注解開發(fā)詳解分析
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲(chǔ)過程以及高級(jí)映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡(jiǎn)單的 XML 或注解來配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫中的記錄2021-10-10
SpringBoot實(shí)現(xiàn)elasticsearch 查詢操作(RestHighLevelClient 
這篇文章主要給大家介紹了SpringBoot如何實(shí)現(xiàn)elasticsearch 查詢操作,文中有詳細(xì)的代碼示例和操作流程,具有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07
基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫,前端框架使用bootstrap4框架,完美了實(shí)現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗(yàn)吧2021-05-05
Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

