Java 静态变量(Static Variables)指南

📖 静态变量基本概念

什么是静态变量?

  • 使用 static 关键字修饰的成员变量
  • 属于类本身,而不是类的某个实例
  • 所有对象共享同一个静态变量
  • 在类加载时初始化,生命周期与类相同

🎯 静态变量的特点

1. 类级别共享

java 复制代码
public class StaticVariableDemo {
    // 实例变量 - 每个对象独立拥有
    private int instanceVar = 0;
    
    // 静态变量 - 所有对象共享
    private static int staticVar = 0;
    
    public void increment() {
        instanceVar++;   // 修改实例变量
        staticVar++;     // 修改静态变量
    }
    
    public void show() {
        System.out.println("instanceVar = " + instanceVar + ", staticVar = " + staticVar);
    }
    
    public static void main(String[] args) {
        StaticVariableDemo obj1 = new StaticVariableDemo();
        StaticVariableDemo obj2 = new StaticVariableDemo();
        
        obj1.increment();
        obj1.show();  // 输出: instanceVar = 1, staticVar = 1
        
        obj2.increment();
        obj2.show();  // 输出: instanceVar = 1, staticVar = 2
        
        // 静态变量可以通过类名直接访问
        System.out.println("通过类名访问: " + StaticVariableDemo.staticVar);  // 输出: 2
        
        // 也可以通过对象访问(不推荐)
        System.out.println("通过对象访问: " + obj1.staticVar);  // 输出: 2
    }
}

2. 内存分配时机

java 复制代码
public class MemoryAllocation {
    // 静态变量在类加载时初始化
    static String staticField = "静态变量(类加载时初始化)";
    
    // 实例变量在对象创建时初始化
    String instanceField = "实例变量(对象创建时初始化)";
    
    // 静态代码块(类加载时执行)
    static {
        System.out.println("1. 静态代码块执行");
        System.out.println("   静态变量值: " + staticField);
    }
    
    // 实例代码块(对象创建时执行)
    {
        System.out.println("3. 实例代码块执行");
        System.out.println("   实例变量值: " + instanceField);
    }
    
    // 构造器(对象创建时执行)
    public MemoryAllocation() {
        System.out.println("4. 构造器执行");
    }
    
    public static void main(String[] args) {
        System.out.println("2. main方法开始");
        
        System.out.println("\n=== 创建第一个对象 ===");
        MemoryAllocation obj1 = new MemoryAllocation();
        
        System.out.println("\n=== 创建第二个对象 ===");
        MemoryAllocation obj2 = new MemoryAllocation();
        
        /*
        输出顺序:
        1. 静态代码块执行(只执行一次)
           静态变量值: 静态变量(类加载时初始化)
        2. main方法开始
        
        === 创建第一个对象 ===
        3. 实例代码块执行
           实例变量值: 实例变量(对象创建时初始化)
        4. 构造器执行
        
        === 创建第二个对象 ===
        3. 实例代码块执行
           实例变量值: 实例变量(对象创建时初始化)
        4. 构造器执行
        */
    }
}

🏗️ 静态变量的声明和使用

1. 基本语法

java 复制代码
public class StaticSyntax {
    // 声明静态变量
    static int count;           // 默认值 0
    static String name;         // 默认值 null
    static boolean flag;        // 默认值 false
    static double price;        // 默认值 0.0
    
    // 声明并初始化
    static int total = 100;
    static final double PI = 3.14159;  // 静态常量
    
    // 静态代码块初始化
    static {
        System.out.println("静态代码块执行");
        count = 10;
        name = "Java";
    }
    
    public static void main(String[] args) {
        // 访问静态变量(推荐通过类名)
        System.out.println("count = " + StaticSyntax.count);
        System.out.println("name = " + StaticSyntax.name);
        System.out.println("total = " + StaticSyntax.total);
        System.out.println("PI = " + StaticSyntax.PI);
        
        // 修改静态变量
        StaticSyntax.total = 200;
        System.out.println("修改后 total = " + StaticSyntax.total);
        
        // 静态常量不能修改
        // StaticSyntax.PI = 3.14;  // ❌ 编译错误
    }
}

2. 静态变量的访问方式

java 复制代码
public class AccessWays {
    static String message = "Hello, Static!";
    String instanceMsg = "Hello, Instance!";
    
    public static void main(String[] args) {
        // ✅ 正确方式1:通过类名访问(推荐)
        System.out.println("类名访问: " + AccessWays.message);
        
        // ✅ 正确方式2:在静态方法中直接访问(同一类内)
        System.out.println("直接访问: " + message);
        
        // ✅ 正确方式3:通过对象访问(不推荐,会产生误导)
        AccessWays obj = new AccessWays();
        System.out.println("对象访问: " + obj.message);  // 可以但不推荐
        
        // ❌ 错误:静态方法中不能直接访问实例变量
        // System.out.println(instanceMsg);  // 编译错误
        
        // ✅ 正确:需要先创建对象
        System.out.println("实例变量: " + obj.instanceMsg);
        
        // 访问其他类的静态变量
        System.out.println("\n访问其他类的静态变量:");
        System.out.println("Integer最大值: " + Integer.MAX_VALUE);
        System.out.println("Math的PI: " + Math.PI);
    }
}

// 另一个类
class AnotherClass {
    static String info = "来自另一个类的静态变量";
}

🔧 静态变量的应用场景

1. 计数器/ID生成器

java 复制代码
public class Counter {
    // 静态变量作为计数器
    private static int count = 0;
    
    // 实例变量:每个对象的唯一ID
    private final int id;
    
    public Counter() {
        count++;            // 每次创建对象计数器加1
        id = count;         // 为对象分配唯一ID
        System.out.println("创建第 " + id + " 个对象");
    }
    
    // 获取当前对象数量
    public static int getCount() {
        return count;
    }
    
    // 获取对象ID
    public int getId() {
        return id;
    }
    
    public static void main(String[] args) {
        System.out.println("初始对象数: " + Counter.getCount());
        
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
        
        System.out.println("\n对象信息:");
        System.out.println("c1 ID: " + c1.getId());
        System.out.println("c2 ID: " + c2.getId());
        System.out.println("c3 ID: " + c3.getId());
        System.out.println("总对象数: " + Counter.getCount());
    }
}

2. 配置信息/全局常量

java 复制代码
public class AppConfig {
    // 全局配置信息(静态常量)
    public static final String APP_NAME = "MyApplication";
    public static final String VERSION = "1.0.0";
    public static final int MAX_USERS = 1000;
    public static final double TAX_RATE = 0.13;
    
    // 运行时配置(静态变量)
    private static String language = "zh-CN";
    private static boolean debugMode = false;
    private static int connectionTimeout = 30000;
    
    // 提供静态方法访问和修改配置
    public static String getLanguage() {
        return language;
    }
    
    public static void setLanguage(String lang) {
        language = lang;
    }
    
    public static boolean isDebugMode() {
        return debugMode;
    }
    
    public static void setDebugMode(boolean debug) {
        debugMode = debug;
    }
    
    public static int getConnectionTimeout() {
        return connectionTimeout;
    }
    
    public static void setConnectionTimeout(int timeout) {
        if (timeout > 0) {
            connectionTimeout = timeout;
        }
    }
    
    public static void printConfig() {
        System.out.println("=== 应用配置 ===");
        System.out.println("应用名称: " + APP_NAME);
        System.out.println("版本: " + VERSION);
        System.out.println("语言: " + language);
        System.out.println("调试模式: " + debugMode);
        System.out.println("连接超时: " + connectionTimeout + "ms");
        System.out.println("最大用户数: " + MAX_USERS);
        System.out.println("税率: " + TAX_RATE);
    }
    
    public static void main(String[] args) {
        // 使用全局配置
        AppConfig.printConfig();
        
        // 修改运行时配置
        AppConfig.setDebugMode(true);
        AppConfig.setLanguage("en-US");
        
        System.out.println("\n=== 修改后配置 ===");
        AppConfig.printConfig();
        
        // 在其他类中使用配置
        System.out.println("\n在业务代码中使用:");
        double price = 100.0;
        double tax = price * AppConfig.TAX_RATE;
        System.out.println("商品价格: $" + price);
        System.out.println("税费: $" + tax);
        System.out.println("总价: $" + (price + tax));
    }
}

3. 缓存数据

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class DataCache {
    // 静态Map作为缓存
    private static Map<String, Object> cache = new HashMap<>();
    
    // 缓存过期时间(毫秒)
    private static Map<String, Long> expireTime = new HashMap<>();
    private static final long DEFAULT_EXPIRE = 60000; // 默认1分钟
    
    /**
     * 存入缓存
     */
    public static void put(String key, Object value) {
        put(key, value, DEFAULT_EXPIRE);
    }
    
    public static void put(String key, Object value, long expireMillis) {
        cache.put(key, value);
        expireTime.put(key, System.currentTimeMillis() + expireMillis);
        System.out.println("缓存已存入: " + key + " = " + value);
    }
    
    /**
     * 获取缓存
     */
    public static Object get(String key) {
        if (!cache.containsKey(key)) {
            System.out.println("缓存未命中: " + key);
            return null;
        }
        
        // 检查是否过期
        long now = System.currentTimeMillis();
        if (now > expireTime.get(key)) {
            System.out.println("缓存已过期: " + key);
            cache.remove(key);
            expireTime.remove(key);
            return null;
        }
        
        System.out.println("缓存命中: " + key);
        return cache.get(key);
    }
    
    /**
     * 清理过期缓存
     */
    public static void cleanExpired() {
        long now = System.currentTimeMillis();
        int count = 0;
        
        for (String key : new HashMap<>(expireTime).keySet()) {
            if (now > expireTime.get(key)) {
                cache.remove(key);
                expireTime.remove(key);
                count++;
            }
        }
        
        System.out.println("清理了 " + count + " 个过期缓存");
    }
    
    /**
     * 清空所有缓存
     */
    public static void clear() {
        cache.clear();
        expireTime.clear();
        System.out.println("缓存已清空");
    }
    
    /**
     * 获取缓存统计信息
     */
    public static void showStats() {
        System.out.println("\n=== 缓存统计 ===");
        System.out.println("缓存数量: " + cache.size());
        System.out.println("缓存内容:");
        for (Map.Entry<String, Object> entry : cache.entrySet()) {
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // 测试缓存功能
        DataCache.put("user:1", "张三");
        DataCache.put("product:100", "iPhone", 5000);  // 5秒过期
        
        // 立即获取
        System.out.println("\n获取缓存:");
        System.out.println("user:1 = " + DataCache.get("user:1"));
        System.out.println("product:100 = " + DataCache.get("product:100"));
        
        // 等待6秒后再次获取
        System.out.println("\n等待6秒后...");
        Thread.sleep(6000);
        
        System.out.println("user:1 = " + DataCache.get("user:1"));
        System.out.println("product:100 = " + DataCache.get("product:100"));  // 应已过期
        
        // 清理过期缓存
        DataCache.cleanExpired();
        DataCache.showStats();
    }
}

⚠️ 静态变量的注意事项

1. 线程安全问题

java 复制代码
public class ThreadSafety {
    // 非线程安全的静态变量
    private static int counter = 0;
    
    // 线程安全的递增方法(使用synchronized)
    public static synchronized void safeIncrement() {
        counter++;
    }
    
    // 非线程安全的递增方法
    public static void unsafeIncrement() {
        counter++;
    }
    
    public static void main(String[] args) throws InterruptedException {
        // 创建10个线程,每个线程递增1000次
        Thread[] threads = new Thread[10];
        
        // 测试非线程安全的方法
        counter = 0;  // 重置计数器
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    unsafeIncrement();  // 非线程安全
                }
            });
            threads[i].start();
        }
        
        // 等待所有线程完成
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("非线程安全结果: " + counter + " (期望: 10000)");
        
        // 测试线程安全的方法
        counter = 0;  // 重置计数器
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    safeIncrement();  // 线程安全
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("线程安全结果: " + counter + " (期望: 10000)");
        
        // 更好的线程安全方案:使用AtomicInteger
        java.util.concurrent.atomic.AtomicInteger atomicCounter = 
            new java.util.concurrent.atomic.AtomicInteger(0);
        
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    atomicCounter.incrementAndGet();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("AtomicInteger结果: " + atomicCounter.get() + " (期望: 10000)");
    }
}

2. 静态变量与继承

java 复制代码
class Parent {
    // 父类静态变量
    static String staticField = "父类静态变量";
    
    // 父类实例变量
    String instanceField = "父类实例变量";
    
    // 静态方法
    static void staticMethod() {
        System.out.println("父类静态方法");
    }
    
    // 实例方法
    void instanceMethod() {
        System.out.println("父类实例方法");
    }
}

class Child extends Parent {
    // 子类静态变量(隐藏父类同名静态变量)
    static String staticField = "子类静态变量";
    
    // 子类实例变量(隐藏父类同名实例变量)
    String instanceField = "子类实例变量";
    
    // 静态方法(隐藏父类同名静态方法)
    static void staticMethod() {
        System.out.println("子类静态方法");
    }
    
    // 实例方法(重写父类实例方法)
    @Override
    void instanceMethod() {
        System.out.println("子类实例方法");
    }
}

public class StaticInheritance {
    public static void main(String[] args) {
        System.out.println("=== 静态成员 ===");
        
        // 静态成员属于类,通过类名访问
        System.out.println("Parent.staticField: " + Parent.staticField);
        System.out.println("Child.staticField: " + Child.staticField);
        
        Parent.staticMethod();
        Child.staticMethod();
        
        System.out.println("\n=== 实例成员 ===");
        
        Parent parent = new Parent();
        Child child = new Child();
        Parent polymorphic = new Child();  // 多态
        
        System.out.println("parent.instanceField: " + parent.instanceField);
        System.out.println("child.instanceField: " + child.instanceField);
        System.out.println("polymorphic.instanceField: " + polymorphic.instanceField);  // 访问父类的
        
        parent.instanceMethod();
        child.instanceMethod();
        polymorphic.instanceMethod();  // 多态:调用子类重写的方法
        
        System.out.println("\n=== 通过子类访问父类静态成员 ===");
        // 可以通过子类类名访问父类静态成员,但不推荐
        System.out.println("通过子类访问: " + Child.staticField);  // 子类的
        
        // 强制访问父类静态成员
        System.out.println("强制访问父类: " + ((Parent)null).staticField);  // 父类的
        
        // 总结:
        // 1. 静态成员不存在重写(Override),只有隐藏(Hide)
        // 2. 静态成员通过类名访问,编译时确定
        // 3. 实例成员通过对象访问,运行时确定(多态)
    }
}

3. 静态变量的初始化顺序

java 复制代码
public class InitializationOrder {
    // 静态变量(按顺序初始化)
    static int a = init("静态变量 a");
    static int b = init("静态变量 b");
    
    // 静态代码块(按顺序执行)
    static {
        System.out.println("静态代码块 1");
    }
    
    static int c = init("静态变量 c");
    
    static {
        System.out.println("静态代码块 2");
    }
    
    // 实例变量
    int x = init("实例变量 x");
    
    // 实例代码块
    {
        System.out.println("实例代码块");
    }
    
    // 构造器
    public InitializationOrder() {
        System.out.println("构造器执行");
    }
    
    static int d = init("静态变量 d");
    
    // 初始化方法
    static int init(String message) {
        System.out.println("初始化: " + message);
        return 0;
    }
    
    public static void main(String[] args) {
        System.out.println("\n=== main方法开始 ===");
        System.out.println("\n=== 第一次创建对象 ===");
        new InitializationOrder();
        
        System.out.println("\n=== 第二次创建对象 ===");
        new InitializationOrder();
        
        /*
        输出顺序:
        初始化: 静态变量 a
        初始化: 静态变量 b
        静态代码块 1
        初始化: 静态变量 c
        静态代码块 2
        初始化: 静态变量 d
        
        === main方法开始 ===
        
        === 第一次创建对象 ===
        初始化: 实例变量 x
        实例代码块
        构造器执行
        
        === 第二次创建对象 ===
        初始化: 实例变量 x
        实例代码块
        构造器执行
        */
    }
}

💡 静态变量最佳实践

1. 单例模式(Singleton)

java 复制代码
public class Singleton {
    // 1. 私有静态变量(唯一实例)
    private static Singleton instance;
    
    // 2. 私有构造器(防止外部创建实例)
    private Singleton() {
        System.out.println("Singleton实例被创建");
    }
    
    // 3. 公共静态方法(全局访问点)
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();  // 懒加载
        }
        return instance;
    }
    
    // 4. 线程安全版本(双重检查锁定)
    public static Singleton getInstanceThreadSafe() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    // 5. 静态内部类版本(推荐)
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstanceByHolder() {
        return SingletonHolder.INSTANCE;
    }
    
    // 实例方法
    public void showMessage() {
        System.out.println("Hello Singleton!");
    }
    
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println("s1 == s2? " + (s1 == s2));  // true
        s1.showMessage();
    }
}

2. 工具类(Utility Class)

java 复制代码
// 工具类通常设计为final,包含静态方法,私有构造器防止实例化
public final class StringUtils {
    
    // 私有构造器,防止实例化
    private StringUtils() {
        throw new AssertionError("不能实例化工具类");
    }
    
    // 静态工具方法
    
    /**
     * 检查字符串是否为空或空白
     */
    public static boolean isBlank(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    /**
     * 检查字符串是否不为空
     */
    public static boolean isNotBlank(String str) {
        return !isBlank(str);
    }
    
    /**
     * 首字母大写
     */
    public static String capitalize(String str) {
        if (isBlank(str)) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
    
    /**
     * 重复字符串
     */
    public static String repeat(String str, int times) {
        if (str == null || times <= 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < times; i++) {
            sb.append(str);
        }
        return sb.toString();
    }
    
    /**
     * 反转字符串
     */
    public static String reverse(String str) {
        if (str == null) {
            return null;
        }
        return new StringBuilder(str).reverse().toString();
    }
    
    public static void main(String[] args) {
        // 使用工具类的静态方法
        System.out.println("isBlank测试:");
        System.out.println("  null: " + StringUtils.isBlank(null));
        System.out.println("  '': " + StringUtils.isBlank(""));
        System.out.println("  '  ': " + StringUtils.isBlank("  "));
        System.out.println("  'abc': " + StringUtils.isBlank("abc"));
        
        System.out.println("\ncapitalize测试:");
        System.out.println("  'hello': " + StringUtils.capitalize("hello"));
        System.out.println("  'WORLD': " + StringUtils.capitalize("WORLD"));
        
        System.out.println("\nrepeat测试:");
        System.out.println("  'ab'*3: " + StringUtils.repeat("ab", 3));
        
        System.out.println("\nreverse测试:");
        System.out.println("  '12345': " + StringUtils.reverse("12345"));
        
        // 尝试实例化工具类(会抛出异常)
        try {
            // StringUtils utils = new StringUtils();  // 运行时异常
        } catch (Exception e) {
            System.out.println("\n不能实例化工具类: " + e.getMessage());
        }
    }
}

3. 常量类

java 复制代码
// 专门存放常量的类
public final class Constants {
    
    private Constants() {
        // 防止实例化
    }
    
    // 数据库相关常量
    public static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
    public static final String DB_USER = "root";
    public static final String DB_PASSWORD = "password";
    public static final int DB_MAX_CONNECTIONS = 10;
    
    // 应用配置常量
    public static final String APP_NAME = "MyApp";
    public static final String VERSION = "2.1.0";
    public static final String DEFAULT_LANGUAGE = "zh-CN";
    public static final int SESSION_TIMEOUT = 1800; // 30分钟
    
    // 文件路径常量
    public static final String LOG_DIR = "/var/log/myapp";
    public static final String CONFIG_FILE = "/etc/myapp/config.properties";
    public static final String TEMP_DIR = "/tmp/myapp";
    
    // 业务常量
    public static final int MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
    public static final int MAX_LOGIN_ATTEMPTS = 5;
    public static final double TAX_RATE = 0.13;
    
    // 状态码常量
    public static final int STATUS_OK = 200;
    public static final int STATUS_ERROR = 500;
    public static final int STATUS_NOT_FOUND = 404;
    
    // 颜色常量(RGB)
    public static final int COLOR_RED = 0xFF0000;
    public static final int COLOR_GREEN = 0x00FF00;
    public static final int COLOR_BLUE = 0x0000FF;
    public static final int COLOR_WHITE = 0xFFFFFF;
    
    // 使用枚举也是更好的选择
    public enum UserRole {
        ADMIN, USER, GUEST
    }
    
    public enum OrderStatus {
        PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
    }
    
    public static void main(String[] args) {
        // 使用常量
        System.out.println("应用名称: " + Constants.APP_NAME);
        System.out.println("版本: " + Constants.VERSION);
        System.out.println("最大文件大小: " + Constants.MAX_FILE_SIZE + " bytes");
        System.out.println("税率: " + Constants.TAX_RATE);
        
        // 使用枚举
        System.out.println("\n用户角色:");
        for (UserRole role : UserRole.values()) {
            System.out.println("  " + role);
        }
    }
}

📊 静态变量 vs 实例变量

特性 静态变量 实例变量
所属级别 类级别 对象级别
内存分配 类加载时 对象创建时
内存位置 方法区 堆内存
共享性 所有对象共享 每个对象独立
生命周期 与类相同 与对象相同
访问方式 类名.变量名 对象.变量名
默认值 有(同实例变量)
初始化 静态代码块/声明时 实例代码块/声明时/构造器
线程安全 需要额外处理 每个对象独立

🎓 总结要点

  1. 静态变量属于类:所有对象共享同一份
  2. 初始化时机:类加载时初始化,只初始化一次
  3. 访问方式:推荐通过类名访问,不推荐通过对象访问
  4. 线程安全:静态变量需要特别注意线程安全问题
  5. 内存管理:静态变量生命周期长,要避免内存泄漏
  6. 使用场景
    • 共享数据(计数器、缓存)
    • 工具类、常量类
    • 单例模式
    • 配置信息

记住:静态变量是全局的,要谨慎使用!过多使用静态变量会导致代码耦合度高、难以测试、内存泄漏等问题。合理使用静态变量可以让代码更简洁高效。

相关推荐
加瓦点灯1 小时前
我用 AI,一天就把一个桌面提醒插件撸完了
后端
红色石头本尊1 小时前
27-集成swagger接口文档
后端
嘻哈baby1 小时前
Linux系统性能排查实战指南:从定位到解决
后端
开心就好20251 小时前
使用开心上架上架 App Store,一次跨平台团队的完整发布流程调度记录
后端
南囝coding1 小时前
《独立开发者精选工具》第 023 期
前端·后端·开源
文心快码BaiduComate1 小时前
Agent如何重塑跨角色协作的AI提效新范式
前端·后端·程序员
C***u1762 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
Java水解2 小时前
# SpringBoot权限认证-Sa-Token的使用与详解
spring boot·后端
头发那是一根不剩了2 小时前
Spring Boot「多数据源并存」的设计思路,它与动态数据源又有什么区别?
java·spring boot·后端