Dubbo3和Spring?Boot整合過(guò)程源碼解析
前言
Dubbo3 已經(jīng)從一開(kāi)始的 RPC 框架改頭換面,現(xiàn)在的定位是微服務(wù)框架,除了提供基本的 RPC 功能外,它還提供了一整套的服務(wù)治理方案。Dubbo 有自身的一套設(shè)計(jì)體系,不過(guò)通常很少單獨(dú)使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源碼,版本是 3.1.10,Github 地址:https://github.com/apache/dubbo/tree/dubbo-3.1.10。
Dubbo3 專(zhuān)門(mén)提供了一個(gè)模塊來(lái)和 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
即可開(kāi)啟 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
模塊沒(méi)有代碼,只有一個(gè) pom.xml
引入了幾個(gè)必要的依賴(lài):
<dependencies> <!-- Spring Boot 依賴(lài) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <optional>true</optional> </dependency> <!-- 日志 依賴(lài) --> <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è)類(lèi)是無(wú)法實(shí)現(xiàn)如此復(fù)雜的整合的,再看 pom.xml
發(fā)現(xiàn)該模塊又依賴(lài)了 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)裝配的組件,通過(guò)這些組件來(lái)和 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í)還沒(méi)啟動(dòng)服務(wù) DubboDeployApplicationListener#onApplicationEvent Spring事件監(jiān)聽(tīng) 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)裝配類(lèi),它先于 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ì)在類(lèi)路徑下掃描,所以會(huì) new 一個(gè) DubboClassPathBeanDefinitionScanner 掃描器,它依賴(lài)于 Spring 的 ClassPathBeanDefinitionScanner,將類(lèi)路徑下加了 @DubboService
、 @Service
的類(lèi)封裝成 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)在還沒(méi)啟動(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)聽(tīng)器,它監(jiān)聽(tīng)到 ContextRefreshedEvent 事件會(huì)啟動(dòng) Dubbo 服務(wù);監(jiān)聽(tīng)到 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)建Proxy
ReferenceAnnotationBeanPostProcessor
DubboSpringInitializer 初始化的時(shí)候會(huì)注冊(cè) ReferenceAnnotationBeanPostProcessor 類(lèi),它的職責(zé)是完成 @DubboReference
依賴(lài)的注入。
它是 BeanFactoryPostProcessor 的子類(lèi),是 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ù)依賴(lài)注入時(shí)就可以直接從 Spring 容器獲取對(duì)應(yīng)的 bean 實(shí)例了。
protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException { try { // 遍歷注入點(diǎn) 注冊(cè) ReferenceBean 此時(shí)記錄的只是一個(gè)referenceBeanName 還沒(méi)注入 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; // 緩存起來(lái) injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName); } } }
postProcessPropertyValues()
才是真正的依賴(lài)注入:
public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { try { // 已經(jīng)解析過(guò)了,這里直接從緩存獲取 AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs); prepareInjection(metadata); // 依賴(lài)注入 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
依賴(lài)注入的對(duì)象被封裝為 ReferenceBean,它是一個(gè) FactoryBean,所以在注入的時(shí)候,其實(shí)會(huì)調(diào)用 ReferenceBean#getObject()
獲取 bean 實(shí)例。
public T getObject() { if (lazyProxy == null) { createLazyProxy(); } return (T) lazyProxy; }
Dubbo 這里并沒(méi)有直接去引用遠(yuǎn)程服務(wù),而是先創(chuàng)建了一個(gè) LazyProxy,Dubbo 希望在真正發(fā)起 RPC 調(diào)用時(shí)才去引用服務(wù),為什么這么做呢?LazyProxy 的創(chuàng)建過(guò)程,利用 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 整合的過(guò)程可謂是一波三折,過(guò)程還是挺繞的,除了要了解 Dubbo 的底層原理,還要對(duì) Spring Boot 的原理、各種后置處理器很熟悉才行。Dubbo 首先是提供了一個(gè)單獨(dú)的模塊來(lái)和 Spring Boot 做整合,利用 Spring Boot 自動(dòng)裝配的功能,配置了一堆自動(dòng)裝配的組件。首先是讀取到要掃描的包路徑,然后掃描 DubboService Bean,并把它注冊(cè)到 Spring 容器,而后統(tǒng)一交給 ModuleConfigManager 管理;再通過(guò)監(jiān)聽(tīng) ContextRefreshedEvent 事件來(lái)完成服務(wù)的啟動(dòng)和發(fā)布。針對(duì) DubboReference 的注入,則依賴(lài) ReferenceAnnotationBeanPostProcessor 后置處理器,它會(huì)遍歷 Spring 容器內(nèi)所有的 BeanDefinition,然后查找注入點(diǎn),把注入點(diǎn)封裝成 ReferenceBean 并注冊(cè)到 Spring 容器,依賴(lài)注入時(shí)再通過(guò)容器獲取對(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ò)程的文章就介紹到這了,更多相關(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ù)的使用以及注意說(shuō)明
這篇文章主要介紹了Transactional事務(wù)的使用以及注意說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Elasticsearch查詢(xún)Range Query語(yǔ)法示例
這篇文章主要為大家介紹了Elasticsearch查詢(xún)Range Query語(yǔ)法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析
這篇文章主要為大家介紹了java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11Java Mybatis框架多表操作與注解開(kāi)發(fā)詳解分析
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過(guò)簡(jiǎn)單的 XML 或注解來(lái)配置和映射原始類(lèi)型、接口和 Java POJO為數(shù)據(jù)庫(kù)中的記錄2021-10-10SpringBoot實(shí)現(xiàn)elasticsearch 查詢(xún)操作(RestHighLevelClient 
這篇文章主要給大家介紹了SpringBoot如何實(shí)現(xiàn)elasticsearch 查詢(xún)操作,文中有詳細(xì)的代碼示例和操作流程,具有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07基于Java SSM框架開(kāi)發(fā)圖書(shū)借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開(kāi)發(fā)圖書(shū)借閱系統(tǒng),開(kāi)發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫(kù),前端框架使用bootstrap4框架,完美了實(shí)現(xiàn)圖書(shū)借閱系統(tǒng),喜歡的朋友快來(lái)體驗(yàn)吧2021-05-05Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01