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

SpringBoot自定義動態(tài)數(shù)據(jù)源的流程步驟

 更新時間:2024年06月25日 08:46:26   作者:AhYi8  
動態(tài)數(shù)據(jù)源,本質(zhì)上是把多個數(shù)據(jù)源存儲在一個?Map?中,當(dāng)需要使用某一個數(shù)據(jù)源時,使用?key?獲取指定數(shù)據(jù)源進(jìn)行處理,本文將給大家介紹一下SpringBoot自定義動態(tài)數(shù)據(jù)源的流程步驟,需要的朋友可以參考下

1. 原理

動態(tài)數(shù)據(jù)源,本質(zhì)上是把多個數(shù)據(jù)源存儲在一個 Map 中,當(dāng)需要使用某一個數(shù)據(jù)源時,使用 key 獲取指定數(shù)據(jù)源進(jìn)行處理。而在 Spring 中已提供了抽象類 AbstractRoutingDataSource 來實現(xiàn)此功能,繼承 AbstractRoutingDataSource 類并覆寫其 determineCurrentLookupKey() 方法監(jiān)聽獲取 key 即可,該方法只需要返回數(shù)據(jù)源 key 即可,也就是存放數(shù)據(jù)源的 Mapkey。

因此,我們在實現(xiàn)動態(tài)數(shù)據(jù)源的,只需要繼承它,實現(xiàn)自己的獲取數(shù)據(jù)源邏輯即可。AbstractRoutingDataSource 頂級繼承了 DataSource,所以它也是可以做為數(shù)據(jù)源對象,因此項目中使用它作為主數(shù)據(jù)源。

1.1. AbstractRoutingDataSource 源碼解析

        public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
        // 目標(biāo)數(shù)據(jù)源 map 集合,存儲將要切換的多數(shù)據(jù)源 bean 信息,可以通過 setTargetDataSource(Map<Object, Object> mp) 設(shè)置
        @Nullable
        private Map<Object, Object> targetDataSources;
        // 未指定數(shù)據(jù)源時的默認(rèn)數(shù)據(jù)源對象,可以通過 setDefaultTargetDataSouce(Object obj) 設(shè)置
        @Nullable
        private Object defaultTargetDataSource;
  ...
        // 數(shù)據(jù)源查找接口,通過該接口的 getDataSource(String dataSourceName) 獲取數(shù)據(jù)源信息
        private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        //解析 targetDataSources 之后的 DataSource 的 map 集合
        @Nullable
        private Map<Object, DataSource> resolvedDataSources;
        @Nullable
        private DataSource resolvedDefaultDataSource;

        //將 targetDataSources 的內(nèi)容轉(zhuǎn)化一下放到 resolvedDataSources 中,將 defaultTargetDataSource 轉(zhuǎn)為 DataSource 賦值給 resolvedDefaultDataSource
        public void afterPropertiesSet() {
            //如果目標(biāo)數(shù)據(jù)源為空,會拋出異常,在系統(tǒng)配置時應(yīng)至少傳入一個數(shù)據(jù)源
            if (this.targetDataSources == null) {
                throw new IllegalArgumentException("Property 'targetDataSources' is required");
            } else {
                //初始化 resolvedDataSources 的大小
                this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
                //遍歷目標(biāo)數(shù)據(jù)源信息 map 集合,對其中的 key,value 進(jìn)行解析
                this.targetDataSources.forEach((key, value) -> {
                    // resolveSpecifiedLookupKey 方法沒有做任何處理,只是將 key 繼續(xù)返回
                    Object lookupKey = this.resolveSpecifiedLookupKey(key);
                    // 將目標(biāo)數(shù)據(jù)源 map 集合中的 value 值(Druid 數(shù)據(jù)源信息)轉(zhuǎn)為 DataSource 類型
                    DataSource dataSource = this.resolveSpecifiedDataSource(value);
                    // 將解析之后的 key,value 放入 resolvedDataSources 集合中
                    this.resolvedDataSources.put(lookupKey, dataSource);
                });
                if (this.defaultTargetDataSource != null) {
                    // 將默認(rèn)目標(biāo)數(shù)據(jù)源信息解析并賦值給 resolvedDefaultDataSource
                    this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
                }

            }
        }

        protected Object resolveSpecifiedLookupKey(Object lookupKey) {
            return lookupKey;
        }

        protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
            if (dataSource instanceof DataSource) {
                return (DataSource)dataSource;
            } else if (dataSource instanceof String) {
                return this.dataSourceLookup.getDataSource((String)dataSource);
            } else {
                throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
            }
        }

        // 因為 AbstractRoutingDataSource 繼承 AbstractDataSource,而 AbstractDataSource 實現(xiàn)了 DataSource 接口,所有存在獲取數(shù)據(jù)源連接的方法
        public Connection getConnection() throws SQLException {
            return this.determineTargetDataSource().getConnection();
        }

        public Connection getConnection(String username, String password) throws SQLException {
            return this.determineTargetDataSource().getConnection(username, password);
        }

  // 最重要的一個方法,也是 DynamicDataSource 需要實現(xiàn)的方法
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            // 調(diào)用實現(xiàn)類中重寫的 determineCurrentLookupKey 方法拿到當(dāng)前線程要使用的數(shù)據(jù)源的名稱
            Object lookupKey = this.determineCurrentLookupKey();
            // 去解析之后的數(shù)據(jù)源信息集合中查詢該數(shù)據(jù)源是否存在,如果沒有拿到則使用默認(rèn)數(shù)據(jù)源 resolvedDefaultDataSource
            DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }

            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            } else {
                return dataSource;
            }
        }

        @Nullable
        protected abstract Object determineCurrentLookupKey();
    }

1.2. 關(guān)鍵類說明

忽略掉 controller/service/entity/mapper/xml介紹。

  • application.yml:數(shù)據(jù)源配置文件。但是如果數(shù)據(jù)源比較多的話,根據(jù)實際使用,最佳的配置方式還是獨立配置比較好。
  • DynamicDataSourceRegister:動態(tài)數(shù)據(jù)源注冊配置文件
  • DynamicDataSource:動態(tài)數(shù)據(jù)源配置類,繼承自 AbstractRoutingDataSource
  • TargetDataSource:動態(tài)數(shù)據(jù)源注解,切換當(dāng)前線程的數(shù)據(jù)源
  • DynamicDataSourceAspect:動態(tài)數(shù)據(jù)源設(shè)置切面,環(huán)繞通知,切換當(dāng)前線程數(shù)據(jù)源,方法注解優(yōu)先
  • DynamicDataSourceContextHolder:動態(tài)數(shù)據(jù)源上下文管理器,保存當(dāng)前數(shù)據(jù)源的 key,默認(rèn)數(shù)據(jù)源名,所有數(shù)據(jù)源 key

1.3. 開發(fā)流程

  • 添加配置文件,設(shè)置默認(rèn)數(shù)據(jù)源配置,和其他數(shù)據(jù)源配置
  • 編寫 DynamicDataSource 類,繼承 AbstractRoutingDataSource 類,并實現(xiàn) determineCurrentLookupKey() 方法
  • 編寫 DynamicDataSourceHolder 上下文管理類,管理當(dāng)前線程的使用的數(shù)據(jù)源,及所有數(shù)據(jù)源的 key;
  • 編寫 DynamicDataSourceRegister 類通過讀取配置文件動態(tài)注冊多數(shù)據(jù)源,并在啟動類上導(dǎo)入(@Import)該類
  • 自定義數(shù)據(jù)源切換注解 TargetDataSource,并實現(xiàn)相應(yīng)的切面,環(huán)繞通知切換當(dāng)前線程數(shù)據(jù)源,注解優(yōu)先級(DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class

2. 實現(xiàn)

2.1. 引入 Maven 依賴

<!-- web 模塊依賴 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring 核心 aop 模塊依賴 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Druid 數(shù)據(jù)源連接池依賴 -->
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 <version>1.2.8</version>
</dependency>
<!-- mybatis 依賴 -->
<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.2.2</version>
</dependency>
<!-- mysql驅(qū)動 -->
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.24</version>
</dependency>
<!-- lombok 模塊依賴 -->
<dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
</dependency>
<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-text</artifactId>
 <version>1.10.0</version>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
</dependency>

2.2. application.yml 配置文件

spring:
  datasource:
 type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding-utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
custom:
  datasource:
    names: ds1,ds2
    ds1:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/content_center?useUnicode
      username: root
      password: root
    ds2:
   type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/trade?useUnicode
      username: root
      password: root

2.3. 創(chuàng)建 DynamicDataSource 繼承 AbstractRoutingDataSource 類

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

/**
 * @Description: 繼承Spring AbstractRoutingDataSource 實現(xiàn)路由切換
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {

 /**
  * 決定當(dāng)前線程使用哪種數(shù)據(jù)源
  * @return 數(shù)據(jù)源 key
  */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

2.4. 編寫 DynamicDataSourceHolder 類,管理 DynamicDataSource 上下文

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 動態(tài)數(shù)據(jù)源上下文管理
 */
public class DynamicDataSourceHolder {
    // 存放當(dāng)前線程使用的數(shù)據(jù)源類型信息
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<String>();
    // 存放數(shù)據(jù)源 key
    private static final List<String> DATASOURCE_KEYS = new ArrayList<String>();
    // 默認(rèn)數(shù)據(jù)源 key
    public static final String DEFAULT_DATESOURCE_KEY = "master";

    //設(shè)置數(shù)據(jù)源
    public static void setDynamicDataSourceType(String key) {
        DYNAMIC_DATASOURCE_KEY.set(key);
    }

    //獲取數(shù)據(jù)源
    public static String getDynamicDataSourceType() {
        return DYNAMIC_DATASOURCE_KEY.get();
    }

    //清除數(shù)據(jù)源
    public static void removeDynamicDataSourceType() {
        DYNAMIC_DATASOURCE_KEY.remove();
    }

 public static void addDataSourceKey(String key) {
  DATASOURCE_KEYS.add(key)
 }

    /**
     * 判斷指定 key 當(dāng)前是否存在
     *
     * @param key
     * @return boolean
     */
    public static boolean containsDataSource(String key){
        return DATASOURCE_KEYS.contains(key);
    }
}

2.5. 編寫 DynamicDataSourceRegister 讀取配置文件注冊多數(shù)據(jù)源

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;

/**
 * @Description: 注冊動態(tài)數(shù)據(jù)源
 * 初始化數(shù)據(jù)源和提供了執(zhí)行動態(tài)切換數(shù)據(jù)源的工具類
 * EnvironmentAware(獲取配置文件配置的屬性值)
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
 private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    // 指定默認(rèn)數(shù)據(jù)源類型 (springboot2.0 默認(rèn)數(shù)據(jù)源是 hikari 如何想使用其他數(shù)據(jù)源可以自己配置)
    // private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
    private static final String DEFAULT_DATASOURCE_TYPE = "com.alibaba.druid.pool.DruidDataSource";
    // 默認(rèn)數(shù)據(jù)源
    private DataSource defaultDataSource;
    // 用戶自定義數(shù)據(jù)源
    private Map<String, DataSource> customDataSources  = new HashMap<>();

    /**
     * 加載多數(shù)據(jù)源配置
     * @param env 當(dāng)前環(huán)境
     */
    @Override
    public void setEnvironment(Environment env) {
        initDefaultDataSource(env);
        initCustomDataSources(env);
    }



    /**
     * 初始化主數(shù)據(jù)源
     * @param env
     */
    private void initDefaultDataSource(Environment env) {
        // 讀取主數(shù)據(jù)源
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", env.getProperty("spring.datasource.type", DEFAULT_DATASOURCE_TYPE));
        dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name"));
        dsMap.put("url", env.getProperty("spring.datasource.url"));
        dsMap.put("username", env.getProperty("spring.datasource.username"));
        dsMap.put("password", env.getProperty("spring.datasource.password"));
        defaultDataSource = buildDataSource(dsMap);
    }


    /**
     * 初始化更多數(shù)據(jù)源
     * @param env
     */
    private void initCustomDataSources(Environment env) {
        // 讀取配置文件獲取更多數(shù)據(jù)源
        String dsPrefixs = env.getProperty("custom.datasource.names");
        if (!StringUtils.isBlank(dsPrefixs)) {
         for (String dsPrefix : dsPrefixs.split(",")) {
          dsPrefix = fsPrefix.trim()
          if (!StringUtils.isBlank(dsPrefix)) {
           Map<String, Object> dsMap = new HashMap<>();
           dsMap.put("type", env.getProperty("custom.datasource." + dsPrefix + ".type", DEFAULT_DATASOURCE_TYPE));
              dsMap.put("driver", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name"));
              dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url"));
              dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username"));
              dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password"));
              DataSource ds = buildDataSource(dsMap);
              customDataSources.put(dsPrefix, ds);
          }
         }
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中
        targetDataSources.put(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY, defaultDataSource);
        DynamicDataSourceHolder.addDataSourceKey(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY);
        // 添加更多數(shù)據(jù)源
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.addDataSourceKey(key);
        }

        // 創(chuàng)建 DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition); // 注冊到 Spring 容器中
        LOGGER.info("Dynamic DataSource Registry");
    }

    /**
     * 創(chuàng)建 DataSource
     * @param dsMap 數(shù)據(jù)庫配置參數(shù)
     * @return DataSource
     */
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        try {
            Object type = dsMap.get("type");
            if (type == null)
                type = DEFAULT_DATASOURCE_TYPE;// 默認(rèn)DataSource

            Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
            String driverClassName = String.valueOf(dsMap.get("driver"));
            String url = String.valueOf(dsMap.get("url"));
            String username = String.valueOf(dsMap.get("username"));
            String password = String.valueOf(dsMap.get("password"));

            // 自定義 DataSource 配置
            DataSourceBuilder<? extends DataSource> factory = DataSourceBuilder.create()
                    .driverClassName(driverClassName)
                    .url(url)
                    .username(username)
                    .password(password)
                    .type(dataSourceType);
            return factory.build();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2.6. 在啟動器類上添加 @Import,導(dǎo)入 register 類

// 注冊動態(tài)多數(shù)據(jù)源
@Import({ DynamicDataSourceRegister.class })
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.7. 自定義注解 @TargetDataSource

/**
 * 自定義多數(shù)據(jù)源切換注解
 * 優(yōu)先級:DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切換數(shù)據(jù)源名稱
     */
    public String value() default DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
}

2.8. 定義切面攔截 @TargetDataSource

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;

@Aspect
// 保證在 @Transactional 等注解前面執(zhí)行
@Order(-1)
@Component
public class DataSourceAspect {

    // 設(shè)置 DataSource 注解的切點表達(dá)式
    @Pointcut("@annotation(com.ayi.config.datasource.DynamicDataSource)")
    public void dynamicDataSourcePointCut(){

    }

    //環(huán)繞通知
    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        String key = getDefineAnnotation(joinPoint).value();
        if (!DynamicDataSourceHolder.containsDataSource(key)) {
         LOGGER.error("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源[{}]", key, DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY)
         key = DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
        }
        DynamicDataSourceHolder.setDynamicDataSourceKey(key);
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
    }

    /**
     * 先判斷方法的注解,后判斷類的注解,以方法的注解為準(zhǔn)
     * @param joinPoint 切點
     * @return TargetDataSource
     */
    private TargetDataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        TargetDataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(TargetDataSource.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(TargetDataSource.class);
        }
    }

}

以上就是SpringBoot自定義動態(tài)數(shù)據(jù)源的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動態(tài)數(shù)據(jù)源的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Java中NullPointerException異常的原因和解決辦法

    詳解Java中NullPointerException異常的原因和解決辦法

    本文主要介紹了詳解Java中NullPointerException異常的原因和解決辦法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java實現(xiàn)八個常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序等

    Java實現(xiàn)八個常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序等

    這篇文章主要介紹了Java如何實現(xiàn)八個常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序 、快速排序、歸并排序、堆排序和LST基數(shù)排序,需要的朋友可以參考下
    2015-07-07
  • SpringSecurity安全框架的使用

    SpringSecurity安全框架的使用

    SpringSecurity是一個用于企業(yè)應(yīng)用系統(tǒng)的安全框架,可以控制用戶登錄權(quán)限,實現(xiàn)不同權(quán)限用戶訪問不同內(nèi)容,文章介紹了SpringSecurity的簡單配置和使用,包括環(huán)境搭建、測試用例和自定義登錄頁面的配置
    2025-02-02
  • 詳細(xì)總結(jié)Java組合模式

    詳細(xì)總結(jié)Java組合模式

    今天帶大家了解Java設(shè)計模式中的組合模式,下文中對組合模式介紹的非常詳細(xì),還有相關(guān)代碼,對正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • java多線程使用mdc追蹤日志方式

    java多線程使用mdc追蹤日志方式

    這篇文章主要介紹了java多線程使用mdc追蹤日志方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • IDEA如何實現(xiàn)遠(yuǎn)程斷點調(diào)試jar包

    IDEA如何實現(xiàn)遠(yuǎn)程斷點調(diào)試jar包

    這篇文章主要介紹了IDEA如何實現(xiàn)遠(yuǎn)程斷點調(diào)試jar包的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Java實戰(zhàn)之OutOfMemoryError異常問題及解決方法

    Java實戰(zhàn)之OutOfMemoryError異常問題及解決方法

    這篇文章主要介紹了Java實戰(zhàn)之OutOfMemoryError異常,主要結(jié)合著深入理解Java虛擬機(jī)一書當(dāng)中整理了本篇內(nèi)容,感興趣的朋友一起看看吧
    2022-04-04
  • java Tapestry4.1.2入門說明教程

    java Tapestry4.1.2入門說明教程

    不必關(guān)心鏈接!不必關(guān)心請求(http request)到了哪里!不必關(guān)心響應(yīng)(http response)要轉(zhuǎn)向哪里!Tapestry構(gòu)建于底層的request-resonse模式,基于Servlet技術(shù),抽象出面向組件開發(fā)的模型。Tapestry關(guān)心的是:頁面、組件、事件、對象、方法、屬性!
    2008-11-11
  • 如何使用Sentry 監(jiān)控你的Spring Boot應(yīng)用

    如何使用Sentry 監(jiān)控你的Spring Boot應(yīng)用

    這篇文章主要介紹了如何使用Sentry 監(jiān)控你的Spring Boot應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • MyBatis一級與二級緩存相關(guān)配置

    MyBatis一級與二級緩存相關(guān)配置

    mybatis-plus是一個Mybatis的增強(qiáng)工具,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,這篇文章帶你了解Mybatis的一級和二級緩存
    2023-01-01

最新評論