欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Dubbo3和Spring?Boot整合過程源碼解析

 更新時間:2023年08月25日 10:58:54   作者:程序員小潘  
Dubbo首先是提供了一個單獨的模塊來和Spring Boot做整合,利用 Spring Boot自動裝配的功能,配置了一堆自動裝配的組件,本文介紹Dubbo3和Spring?Boot整合過程,需要的朋友一起看看吧

前言

Dubbo3 已經(jīng)從一開始的 RPC 框架改頭換面,現(xiàn)在的定位是微服務框架,除了提供基本的 RPC 功能外,它還提供了一整套的服務治理方案。Dubbo 有自身的一套設計體系,不過通常很少單獨使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源碼,版本是 3.1.10,Github 地址:https://github.com/apache/dubbo/tree/dubbo-3.1.10。

Dubbo3 專門提供了一個模塊來和 Spring Boot 做整合,模塊名是 dubbo-spring-boot ,下面有4個子模塊:

dubbo-spring-boot
-> dubbo-spring-boot-actuator
-> dubbo-spring-boot-autoconfigure
-> dubbo-spring-boot-compatible
-> dubbo-spring-boot-starter

一般我們直接引入 dubbo-spring-boot-starter 即可開啟 Dubbo 的自動裝配,在 Spring Boot 項目里使用 Dubbo。

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.1.10</version>
</dependency>

所以我們以這個模塊為切入點,看看 Dubbo 做了什么。

入口模塊

dubbo-spring-boot-starter 模塊沒有代碼,只有一個 pom.xml 引入了幾個必要的依賴:

<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 自動裝配 -->
    <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 的自動裝配功能,顯然就兩個類是無法實現(xiàn)如此復雜的整合的,再看 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 自動裝配的功能,提供了兩個自動裝配的模塊,配置了一堆自動裝配的組件,通過這些組件來和 Spring Boot 做整合。

服務發(fā)布

Dubbo3 整合 Spring 后,如何將加了 @DubboService 的服務發(fā)布出去?

服務發(fā)布的關鍵節(jié)點:

DubboAutoConfiguration#serviceAnnotationBeanProcessor ServiceBean后置處理器
  ServiceAnnotationPostProcessor#postProcessBeanDefinitionRegistry
    ServiceAnnotationPostProcessor#scanServiceBeans 掃描ServiceBean
      DubboClassPathBeanDefinitionScanner#scan
        ServiceAnnotationPostProcessor#processScannedBeanDefinition 處理BeanDefinition
          ServiceAnnotationPostProcessor#buildServiceBeanDefinition 構(gòu)建ServiceBeanDefinition
            BeanDefinitionRegistry#registerBeanDefinition 注冊BeanDefinition
              ServiceBean#afterPropertiesSet
                ModuleConfigManager#addService 交給ModuleConfigManager管理 此時還沒啟動服務
DubboDeployApplicationListener#onApplicationEvent Spring事件監(jiān)聽
  DubboDeployApplicationListener#onContextRefreshedEvent ContextRefreshed事件
    DefaultModuleDeployer#start 模塊部署啟動
      DefaultModuleDeployer#exportServices 啟動服務 暴露服務
      DefaultModuleDeployer#referServices 引用服務

AutoConfiguration

先看 dubbo-spring-boot-autoconfigure 模塊自動裝配的配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration

DubboRelaxedBinding2AutoConfiguration 是 Dubbo 提供的針對 Spring Boot 2.0 的自動裝配類,它先于 DubboRelaxedBindingAutoConfiguration 執(zhí)行,如果你用的是 Spring Boot 1.x 版本,則會觸發(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 掃描包路徑 注冊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 的初始化,注冊一些核心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 對外提供的服務,在 Spring 里面被封裝為 org.apache.dubbo.config.spring.ServiceBean 。

它繼承自 ServiceConfig,所以它是一個標準的 Dubbo Service,在整合 Spring 方面做了擴展。ServiceAnnotationPostProcessor 是 Spring BeanFactory 的后置處理器,它的職責是:掃描 DubboService Bean,注冊到 Spring 容器。拿到需要掃描的包路徑,然后掃描 DubboService Bean:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    this.registry = registry;
    // 掃描 DubboService Bean
    scanServiceBeans(resolvedPackagesToScan, registry);
}

Dubbo 會在類路徑下掃描,所以會 new 一個 DubboClassPathBeanDefinitionScanner 掃描器,它依賴于 Spring 的 ClassPathBeanDefinitionScanner,將類路徑下加了 @DubboService 、 @Service 的類封裝成 BeanDefinition,然后注冊到容器:

private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
    // 實例化一個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 -> 注冊
            processScannedBeanDefinition(beanDefinitionHolder);
            servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());
        }
        servicePackagesHolder.addScannedPackage(packageToScan);
    }
}

ServiceBean 注冊到容器了,Spring 會正常實例化 bean。又因為 ServiceBean 實現(xiàn)了 InitializingBean 接口,所以會觸發(fā)它的 afterPropertiesSet() ,ServiceBean 會把自己交給 ModuleConfigManager 管理。

Tips:服務現(xiàn)在還沒啟動,目前只是先收集 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 會初始化 DubboSpringInitializer,初始化的時候會向 Spring 容器注冊一堆 bean,其中就包含 DubboDeployApplicationListener。它是 Spring 應用程序的事件監(jiān)聽器,它監(jiān)聽到 ContextRefreshedEvent 事件會啟動 Dubbo 服務;監(jiān)聽到 ContextClosedEvent 事件關閉 Dubbo 服務。

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);
        }
    }
}

啟動服務其實就是觸發(fā) ModuleDeployer#start() ,Dubbo 會啟動相關組件,然后暴露服務和應用服務。

private void onContextRefreshedEvent(ContextRefreshedEvent event) {
    ModuleDeployer deployer = moduleModel.getDeployer();
    Assert.notNull(deployer, "Module deployer is null");
    // 啟動模塊部署
    Future future = deployer.start();
    if (!deployer.isBackground()) {
        try {
            // 等待啟動完成
            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);
        }
    }
}

至此,服務啟動完畢。

服務引用

Dubbo3 整合 Spring 后,如何注入加了 @DubboReference 的屬性/方法,完成服務引用?

服務引用的關鍵節(jié)點:

DubboConfigConfigurationRegistrar#registerBeanDefinitions
  DubboSpringInitializer#initialize Dubbo引用初始化
    DubboBeanUtils#registerCommonBeans 注冊公共bean
      ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory 遍歷BeanDefinition
        AbstractAnnotationBeanPostProcessor#findInjectionMetadata 查找注入點
          ReferenceAnnotationBeanPostProcessor#prepareInjection 準備注入
            ReferenceAnnotationBeanPostProcessor#registerReferenceBean 注冊ReferenceBean
      ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues bean屬性值后置處理
        AnnotatedInjectElement#inject 注入
          ReferenceBean#getObject 獲取注入對象
            ReferenceBean#createLazyProxy 創(chuàng)建延遲代理
              DubboReferenceLazyInitTargetSource#createObject 調(diào)用bean時才創(chuàng)建真正的Proxy
                ReferenceConfig#get 引用服務 創(chuàng)建Proxy

ReferenceAnnotationBeanPostProcessor

DubboSpringInitializer 初始化的時候會注冊 ReferenceAnnotationBeanPostProcessor 類,它的職責是完成 @DubboReference 依賴的注入。

它是 BeanFactoryPostProcessor 的子類,是 Spring BeanFactory 的后置處理器,Spring 啟動時會觸發(fā) postProcessBeanFactory() 。首先是遍歷容器中所有的 BeanDefinition,解析 BeanClass 上的屬性或方法是否有加相關注解,也就是尋找注入點。

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 遍歷所有已注冊的BeanDefinition > 查找注入點 > 準備注入
    String[] beanNames = beanFactory.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        Class<?> beanType;
        if (beanFactory.isFactoryBean(beanName)) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
        if (beanType != null) {
            // 查找注入點
            AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            // 準備注入
            prepareInjection(metadata);
        }
    }
}

AnnotatedInjectionMetadata 元數(shù)據(jù)解析完以后,Spring 會把注入點封裝成 ReferenceBean,同時注冊到 Spring 容器,AnnotatedFieldElement 只是記錄一下引用的 beanName,后續(xù)依賴注入時就可以直接從 Spring 容器獲取對應的 bean 實例了。

protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException {
    try {
        // 遍歷注入點 注冊 ReferenceBean 此時記錄的只是一個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 會把收集到的注入點 挨個進行注入,最終調(diào)用 AnnotatedInjectElement#inject() 。

protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    // ReferenceBean#getObject() 拿到一個延遲代理對象
    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

依賴注入的對象被封裝為 ReferenceBean,它是一個 FactoryBean,所以在注入的時候,其實會調(diào)用 ReferenceBean#getObject() 獲取 bean 實例。

public T getObject() {
    if (lazyProxy == null) {
        createLazyProxy();
    }
    return (T) lazyProxy;
}

Dubbo 這里并沒有直接去引用遠程服務,而是先創(chuàng)建了一個 LazyProxy,Dubbo 希望在真正發(fā)起 RPC 調(diào)用時才去引用服務,為什么這么做呢?LazyProxy 的創(chuàng)建過程,利用 JDK 動態(tài)代理,創(chuàng)建了一個空殼的代理對象:

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 的方法會觸發(fā) JdkDynamicAopProxy#invoke() ,它會在第一次調(diào)用非 Object 方法時創(chuàng)建實際的對象。創(chuàng)建實際的對象由 DubboReferenceLazyInitTargetSource 完成:

private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
    @Override
    protected Object createObject() throws Exception {
        // 首次調(diào)用 ReferenceBean 方法才會創(chuàng)建
        return getCallProxy();
    }
    @Override
    public synchronized Class<?> getTargetClass() {
        return getInterfaceClass();
    }
}

getCallProxy() 其實就是調(diào)用 ReferenceConfig#get() 引用服務。

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 的底層原理,還要對 Spring Boot 的原理、各種后置處理器很熟悉才行。Dubbo 首先是提供了一個單獨的模塊來和 Spring Boot 做整合,利用 Spring Boot 自動裝配的功能,配置了一堆自動裝配的組件。首先是讀取到要掃描的包路徑,然后掃描 DubboService Bean,并把它注冊到 Spring 容器,而后統(tǒng)一交給 ModuleConfigManager 管理;再通過監(jiān)聽 ContextRefreshedEvent 事件來完成服務的啟動和發(fā)布。針對 DubboReference 的注入,則依賴 ReferenceAnnotationBeanPostProcessor 后置處理器,它會遍歷 Spring 容器內(nèi)所有的 BeanDefinition,然后查找注入點,把注入點封裝成 ReferenceBean 并注冊到 Spring 容器,依賴注入時再通過容器獲取對應的 bean 實例。Dubbo 采用的是延遲注入的方式,默認會注入一個空殼的 LazyProxy 對象,在首次調(diào)用 RPC 方法時再去調(diào)用底層的服務引用方法。

到此這篇關于Dubbo3和Spring Boot整合過程的文章就介紹到這了,更多相關Dubbo3和Spring Boot整合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java對象集合按照指定元素順序排序的實現(xiàn)

    Java對象集合按照指定元素順序排序的實現(xiàn)

    最近在對一個集合列表的數(shù)據(jù)進行排序,需求是要集合數(shù)據(jù)按照一個排序狀態(tài)值進行排序,而這個狀態(tài)值,不是按照從小到大這樣的順序排序的,而是要按照特定的順序,所以本文給大家介紹了Java對象集合按照指定元素順序排序的實現(xiàn),需要的朋友可以參考下
    2024-07-07
  • 基于Transactional事務的使用以及注意說明

    基于Transactional事務的使用以及注意說明

    這篇文章主要介紹了Transactional事務的使用以及注意說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Elasticsearch查詢Range Query語法示例

    Elasticsearch查詢Range Query語法示例

    這篇文章主要為大家介紹了Elasticsearch查詢Range Query語法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • java快速排序和選擇排序?qū)崿F(xiàn)實例解析

    java快速排序和選擇排序?qū)崿F(xiàn)實例解析

    這篇文章主要為大家介紹了java快速排序和選擇排序?qū)崿F(xiàn)實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • Java Mybatis框架多表操作與注解開發(fā)詳解分析

    Java Mybatis框架多表操作與注解開發(fā)詳解分析

    MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫中的記錄
    2021-10-10
  • SpringBoot實現(xiàn)elasticsearch 查詢操作(RestHighLevelClient 的案例實戰(zhàn))

    SpringBoot實現(xiàn)elasticsearch 查詢操作(RestHighLevelClient 

    這篇文章主要給大家介紹了SpringBoot如何實現(xiàn)elasticsearch 查詢操作,文中有詳細的代碼示例和操作流程,具有一定的參考價值,需要的朋友可以參考下
    2023-07-07
  • 基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼

    基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼

    本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫,前端框架使用bootstrap4框架,完美了實現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗吧
    2021-05-05
  • java中Path和ClassPath用法比較

    java中Path和ClassPath用法比較

    在本篇文章里小編給大家分享了關于java中Path和ClassPath用法比較內(nèi)容,有需要的朋友們學習下。
    2019-01-01
  • Java控制臺版五子棋的簡單實現(xiàn)方法

    Java控制臺版五子棋的簡單實現(xiàn)方法

    這篇文章主要給大家介紹了關于Java控制臺版五子棋的簡單實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • 圖片疊加效果Java代碼實現(xiàn)

    圖片疊加效果Java代碼實現(xiàn)

    這篇文章主要為大家詳細介紹了圖片疊加效果Java代碼實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02

最新評論