SpringBoot整合多数据源,并支持动态新增与切换

news2025/3/11 14:59:36

SpringBoot整合多数据源,并支持动态新增与切换

一、概述

在项目的开发过程中,遇到了需要从数据库中动态查询新的数据源信息并切换到该数据源做相应的查询操作,这样就产生了动态切换数据源的场景。为了能够灵活地指定具体的数据库,本文基于注解和AOP的方法实现多数据源自动切换。在使用过程中,只需要添加注解就可以使用,简单方便。(代码获取方式:见文章底部(开箱即用))

二、构建核心代码

2.1、AbstractRoutingDataSource构建


package com.wonders.dynamic;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;

/**
 * @Description: TODO:抽象类AbstractRoutingDataSource,实现动态数据源切换
 * @Author: yyalin
 * @CreateDate: 2023/7/16 14:40
 * @Version: V1.0
 */
public abstract class AbstractRoutingDataSource extends AbstractDataSource
        implements InitializingBean {
    //目标数据源map集合,存储将要切换的多数据源bean信息
    @Nullable
    private Map<Object, Object> targetDataSources;
    //未指定数据源时的默认数据源对象
    @Nullable
    private Object defaultTargetDataSource;
    private boolean lenientFallback = true;
    //数据源查找接口,通过该接口的getDataSource(String dataSourceName)获取数据源信息
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    //解析targetDataSources之后的DataSource的map集合
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
    @Nullable
    private DataSource resolvedDefaultDataSource;

    //将targetDataSources的内容转化一下放到resolvedDataSources中,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource
    public void afterPropertiesSet() {
        //如果目标数据源为空,会抛出异常,在系统配置时应至少传入一个数据源
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            //初始化resolvedDataSources的大小
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            //遍历目标数据源信息map集合,对其中的key,value进行解析
            this.targetDataSources.forEach((key, value) -> {
                //resolveSpecifiedLookupKey方法没有做任何处理,只是将key继续返回
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                //将目标数据源map集合中的value值(德鲁伊数据源信息)转为DataSource类型
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                //将解析之后的key,value放入resolvedDataSources集合中
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                //将默认目标数据源信息解析并赋值给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实现了DataSource接口,所有存在获取数据源连接的方法
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

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

    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        //调用实现类中重写的determineCurrentLookupKey方法拿到当前线程要使用的数据源的名称
        Object lookupKey = this.determineCurrentLookupKey();
        //去解析之后的数据源信息集合中查询该数据源是否存在,如果没有拿到则使用默认数据源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();
}

**2.2、**DynamicDataSource类

/**
 * @Description: TODO:动态数据源
 * @Author: yyalin
 * @CreateDate: 2023/7/16 14:46
 * @Version: V1.0
 */
/**
 *
 * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,
 * 并将新的数据源信息添加到map中,并替换targetdatasources中的map
 * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称")
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
    //备份所有数据源信息,
    private Map<Object, Object> defineTargetDataSources;

    /**
     * 决定当前线程使用哪个数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDynamicDataSourceKey();
    }

}

2.3、DynamicDataSourceHolder


/**
 * @Description: TODO:数据源切换处理
 * DynamicDataSourceHolder类主要是设置当前线程的数据源名称,
 * 移除数据源名称,以及获取当前数据源的名称,便于动态切换
 * @Author: yyalin
 * @CreateDate: 2023/7/16 14:51
 * @Version: V1.0
 */
@Slf4j
public class DynamicDataSourceHolder {
    /**
     * 保存动态数据源名称
     */
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();

    /**
     * 设置/切换数据源,决定当前线程使用哪个数据源
     */
    public static void setDynamicDataSourceKey(String key){
        log.info("数据源切换为:{}",key);
        DYNAMIC_DATASOURCE_KEY.set(key);
    }

    /**
     * 获取动态数据源名称,默认使用mater数据源
     */
    public static String getDynamicDataSourceKey(){
        String key = DYNAMIC_DATASOURCE_KEY.get();
        return key == null ? DbsConstant.mysql_db_01 : key;
    }

    /**
     * 移除当前数据源
     */
    public static void removeDynamicDataSourceKey(){
        log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());
        DYNAMIC_DATASOURCE_KEY.remove();
    }

}

2.4、数据源工具类

/**
 * @Description: TODO:数据源工具类
 * @Author: yyalin
 * @CreateDate: 2023/7/16 15:00
 * @Version: V1.0
 */
@Slf4j
@Component
public class DataSourceUtils {
    @Resource
    DynamicDataSource dynamicDataSource;

    /**
     * @Description: 根据传递的数据源信息测试数据库连接
     * @Author zhangyu
     */
    public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(dataSourceInfo.getUrl());
        druidDataSource.setUsername(dataSourceInfo.getUserName());
        druidDataSource.setPassword(dataSourceInfo.getPassword());
        druidDataSource.setDriverClassName(dataSourceInfo.getDriverClassName());
        druidDataSource.setBreakAfterAcquireFailure(true);
        druidDataSource.setConnectionErrorRetryAttempts(0);
        try {
            druidDataSource.getConnection(2000);
            log.info("数据源连接成功");
            return druidDataSource;
        } catch (SQLException throwables) {
            log.error("数据源 {} 连接失败,用户名:{},密码 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassword());
            return null;
        }
    }

    /**
     * @Description: 将新增的数据源加入到备份数据源map中
     * @Author zhangyu
     */
    public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){
        Map<Object, Object> defineTargetDataSources = dynamicDataSource.getDefineTargetDataSources();
        defineTargetDataSources.put(dataSourceName, druidDataSource);
        dynamicDataSource.setTargetDataSources(defineTargetDataSources);
        dynamicDataSource.afterPropertiesSet();
    }

2.5、DynamicDataSourceConfig

/**
 * @Description: TODO:数据源信息配置类,读取数据源配置信息并注册成bean。
 * @Author: yyalin
 * @CreateDate: 2023/7/16 14:54
 * @Version: V1.0
 */
@Configuration
@MapperScan("com.wonders.mapper")
@Slf4j
public class DynamicDataSourceConfig {
    @Bean(name = DbsConstant.mysql_db_01)
    @ConfigurationProperties("spring.datasource.mysqldb01")
    public DataSource masterDataSource() {
        log.info("数据源切换为:{}",DbsConstant.mysql_db_01);
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean(name = DbsConstant.mysql_db_02)
    @ConfigurationProperties("spring.datasource.mysqldb02")
    public DataSource slaveDataSource() {
        log.info("数据源切换为:{}",DbsConstant.mysql_db_02);
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }


    @Bean(name = DbsConstant.oracle_db_01)
    @ConfigurationProperties("spring.datasource.oracledb01")
    public DataSource oracleDataSource() {
        log.info("数据源切换为oracle:{}",DbsConstant.oracle_db_01);
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(){
        Map<Object, Object> dataSourceMap = new HashMap<>(3);
        dataSourceMap.put(DbsConstant.mysql_db_01,masterDataSource());
        dataSourceMap.put(DbsConstant.mysql_db_02,slaveDataSource());
        dataSourceMap.put(DbsConstant.oracle_db_01,oracleDataSource());
        //设置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        //将数据源信息备份在defineTargetDataSources中
        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

}

三、测试代码


/**
 * @Description: TODO
 * @Author: yyalin
 * @CreateDate: 2023/7/16 15:02
 * @Version: V1.0
 */
@Slf4j
@Api(tags="动态切换多数据源测试")
@RestController
public class TestController {
    @Resource
    DataSourceUtils dataSourceUtils;
    @Autowired
    private StudentMapper studentMapper;
    @ApiOperation(value="动态切换多数据源测试", notes="test")
    @GetMapping("/test")
    public Map<String, Object> dynamicDataSourceTest(String id){
        Map<String, Object> map = new HashMap<>();
        //1、默认库中查询数据
        Student student=studentMapper.selectById(id);
        map.put("1、默认库中查询到的数据",student);
        //2、指定库中查询的数据
        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);
        Student student02=studentMapper.selectById(id);
        map.put("2、指定库中查询的数据",student02);
        //3、从数据库获取连接信息,然后获取数据
        //模拟从数据库中获取的连接
        DataSourceInfo dataSourceInfo = new DataSourceInfo(
                "jdbc:mysql://127.0.0.1:3308/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false",
                 "root",
                "root",
                "mysqldb03",
                "com.mysql.cj.jdbc.Driver");
        map.put("dataSource",dataSourceInfo);
        log.info("数据源信息:{}",dataSourceInfo);
        //测试数据源连接
        DruidDataSource druidDataSource = dataSourceUtils.createDataSourceConnection(dataSourceInfo);
        if (Objects.nonNull(druidDataSource)){
            //将新的数据源连接添加到目标数据源map中
            dataSourceUtils.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDatasourceKey());
            //设置当前线程数据源名称-----代码形式
            DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDatasourceKey());
            //在新的数据源中查询用户信息
            Student student03=studentMapper.selectById(id);
            map.put("3、动态数据源查询的数据",student03);
            //关闭数据源连接
            druidDataSource.close();
        }
        //4、指定oracle库中查询的数据
        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.oracle_db_01);
        Student student04=studentMapper.selectById(id);
        map.put("4、指定oracle库中查询的数据",student04);
        return map;
    }
}

测试结果如下:

从结果中可以明显的看出,通过切换不同的数据源,可以从不同的库中获取不同的数据,包括:常见库Mysql、oracle、sqlserver等数据库相互切换。也可以从数据库的某张表中获取连接信息,实现*动态切换数据库。*

图片

四、使用注解方式切换数据源

从上述TestController 中代码不难看出,若要想切换数据源需要在mapper调用之前调用:

DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);

不够简洁优雅,所以下面推荐使用注解的方式来动态进行数据源的切换。

4.1、创建注解类DataSource

/**
 * @Description: TODO:自定义多数据源切换注解
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 * @Author: yyalin
 * @CreateDate: 2023/7/17 14:00
 * @Version: V1.0
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    //切换数据源名称,默认mysql_db_01
    public String value() default DbsConstant.mysql_db_01;
}

**4.2、**创建切面DataSourceAspect类

/**
 * @Description: TODO:创建切面DataSourceAspect类
 * @Author: yyalin
 * @CreateDate: 2023/7/17 14:03
 * @Version: V1.0
 */
@Aspect
@Component
public class DataSourceAspect {
    // 设置DataSource注解的切点表达式
    @Pointcut("@annotation(com.wonders.dynamic.DataSource)")
    public void dynamicDataSourcePointCut(){}

    //环绕通知
    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        String key = getDefineAnnotation(joinPoint).value();
        DynamicDataSourceHolder.setDynamicDataSourceKey(key);
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
    }
    /**
     * 功能描述:先判断方法的注解,后判断类的注解,以方法的注解为准
     * @MethodName: getDefineAnnotation
     * @MethodParam: [joinPoint]
     * @Return: com.wonders.dynamic.DataSource
     * @Author: yyalin
     * @CreateDate: 2023/7/17 14:09
     */
    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(DataSource.class);
        }
    }
}

4.3、进行数据源切换

//@Mapper 与 启动类的@MapperScan({"com.example.demo.mapper"}) 二选一即可
@Repository
public interface StudentMapper extends BaseMapper<Student> {
    /**
     * 功能描述:在mysql_db_01中查询数据
     * @MethodName: findStudentById
     * @MethodParam: [id]
     * @Return: com.wonders.entity.Student
     * @Author: yyalin
     * @CreateDate: 2023/7/17 14:20
     */
    @DataSource(value = DbsConstant.oracle_db_01)
    Student findStudentById(String id);
}

或在service层

@Service
public class StudentServiceImpl implements StudentService{
    @Autowired
    private StudentMapper studentMapper;
    //注解加在实现层才能生效
    @DataSource(value = DbsConstant.mysql_db_01)
    @Override
    public Student findStudentById(String id) {
        return studentMapper.selectById(id);
    }
}

4.4、测试效果


@ApiOperation(value="使用注解方式动态切换多数据源", notes="test02")
    @GetMapping("/test02")
    public Student test02(String id){
        Student student=studentMapper.findStudentById(id);
        return student;
    }

–结果如下:

图片

五、功能点

1、使用注解的方式来动态进行数据源的切换;

2、支持动态新增新的数据源;

3、支持oracle\mysql等常见数据库切换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1351137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php ext-sodium 拓展安装 linux+windows

php编译安装(linux)&#xff0c;可以参考&#xff1a;php编译安装 一、windows soduim源码包自带&#xff0c;直接修改php.ini&#xff0c;取消extensionsodium注释即可 二、linux 1.安装依赖 apt-get install libsodium-dev2.进入源码目录 这里写自己的源码目录 cd /us…

鸿蒙OpenHarmony开发实战-0开始做游戏渲染引擎

首先实现了一个通用的画廊组件来作为练手项目&#xff0c;它主要使用了四个基础组件和容器组件&#xff1a; 我们放置一个按钮来触发 showGallery 方法&#xff0c;该方法控制 panel 弹出式组件的显示和隐藏&#xff0c;这里的 div 和 button 标签就是 hml 内置的组件&#xf…

深度学习(学习记录)

题型&#xff1a;填空题判断题30分、简答题20分、计算题20分、综合题&#xff08;30分&#xff09; 综合题&#xff08;解决实际工程问题&#xff0c;不考实验、不考代码、考思想&#xff09; 一、深度学习绪论&#xff08;非重点不做考察&#xff09; 1、传统机器学习&…

Vue3 结合typescript 组合式函数

在App.vue文件中 实现鼠标点击文件&#xff0c;显示坐标值 第一种方法 第二种方法&#xff1a;组合式函数 结果&#xff1a; 官网推荐组合函数&#xff1a;https://vueuse.org

图论及其应用(匈牙利算法)---期末胡乱复习版

目录 题目知识点解题步骤小结题目 T1:从下图中给定的 M = {x1y4,x2y2,x3y1,x4y5},用 Hungariam算法【匈牙利算法】 求出图中的完美匹配,并写出步骤。 知识点 关于匈牙利算法: 需要注意的是,匈牙利算法仅适用于二分图,并且能够找到完美匹配。什么是交替路?从一个未匹…

SpringMVC-HelloWorld

一、SpringMVC简介 1.1 SpringMVC和三层架构 MVC是一种软件架构思想&#xff0c;将软件按照模型、视图和控制器三个部分划分。 M&#xff1a;model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;用于处理数据。JavaBean分为两类&#xff1a; 实体类Bean&…

Redis VS Memcached:选择哪个更适合您的应用?

目录 1、前言 2、概念简介 2.1 Redis 2.2 Memcached 3、数据模型 4、持久性 5、分布式能力 6、性能和扩展性 7、如何选择适合您引用的缓存系统 8、结语 1、前言 Redis和Memcached都是常见的内存缓存系统&#xff0c;用于提升应用程序的性能和可扩展性。它们都具有高…

Jenkins的Transfers路径怎么配置,解决Transfers配置不生效的问题

Transfers配置: 1.配置Source files: 要填写jar包的相对路径,从当前项目工作空间的根目录开始,看看我的工作空间你就懂了 !如图 我填的是 parent/build/libs/parent-1.0.0.jar,即不要 fdw1/ 的前缀 2.配置Remote directory: 远程目标文件夹,也就是你jar包要放到远程…

TSConfig 配置(tsconfig.json)

详细总结一下TSConfig 的相关配置项。个人笔记&#xff0c;仅供参考&#xff0c;欢迎批评指正&#xff01; 根目录 {/* 指定编译文件/目录 */"files": [], // 指定被编译的文件"include": [], // 指定被编译文件所在的目录"exclude": [], // 指…

《Ensemble deep learning: A review》阅读笔记

论文标题 《Ensemble deep learning: A review》 集成深度学习&#xff1a; 综述 作者 M.A. Ganaie 和 Minghui Hu 来自印度理工学院印多尔分校数学系和南洋理工大学电气与电子工程学院 本文写的大而全。 初读 摘要 集成学习思想&#xff1a; 结合几个单独的模型以获得…

C++Qt6 多种排序算法的比较 数据结构课程设计 | JorbanS

一、 问题描述 在计算机科学与数学中&#xff0c;一个排序算法&#xff08;英语&#xff1a;Sorting algorithm&#xff09;是一种能将一串资料依照特定排序方式排列的算法。最常用到的排序方式是数值顺序以及字典顺序。有效的排序算法在一些算法&#xff08;例如搜索算法与合…

github短视频去除水印项目Douyin_TikTok_Download_API介绍

当下正值短视频盛行的时代。在我们浏览短视频的同时&#xff0c;经常能发现一些精美的图片、引人入胜的文案以及吸引眼球的视频&#xff0c;想要将它们保存到本地。然而&#xff0c;保存下来的图片或视频通常伴随着不太愉悦的水印&#xff0c;这显著降低了使用体验。因此&#…

uniapp中uview组件库的Input 输入框 的使用方法

目录 #平台差异说明 #基本使用 #输入框的类型 #可清空字符 #下划线 #前后图标 #前后插槽 API #Props #Events #Methods #Slots 去除fixed、showWordLimit、showConfirmBar、disableDefaultPadding、autosize字段 此组件为一个输入框&#xff0c;默认没有边框和样式…

VS 2022 控制台程序运行时不显示控制台

Visual Studio 2022&#xff0c;C#控制台程序运行时不显示控制台。此外&#xff0c;C#程序修改运行时的程序名。 文章目录 不显示控制台修改运行时的程序名打包成.exe 文件 不显示控制台 1 选中需要项目&#xff0c;右击属性&#xff0c;选中常规。 2 将输出类型从控制台改为…

Go语言命令行参数及cobra使用教程

Go语言命令行参数及cobra使用教程 1.原生命令行参数2.使用CIL框架Cobra创建 rootCmd创建你的 main.go创建其他命令子命令返回和处理错误 3.cobra使用标志4.Cobra位置参数和自定义参数5.Cobra PreRun和PostRun钩子 1.原生命令行参数 os 包以跨平台的方式&#xff0c;提供了一些…

机器学习-基于attention机制来实现对Image Caption图像描述实验

机器学习-基于attention机制来实现对Image Caption图像描述实验 实验目的 基于attention机制来实现对Image Caption图像描述 实验内容 1.了解一下RNN的Encoder-Decoder结构 在最原始的RNN结构中&#xff0c;输入序列和输出序列必须是严格等长的。但在机器翻译等任务中&…

你真的会用Pycharm?这本耗时2年编写的《Pycharm中文指南》,解决你的困惑!

很多读者应该非常了解 JetBrains 开发的 PyCharm 了&#xff0c;它差不多是 Python 最常用的 IDE之一。PyCharm的优势在于可以为我们节省大量时间、管理代码&#xff0c;并完成大量其他任务&#xff0c;如 debug 和可视化等。 需要最新专业版PyCharm永久使用权限的扫码获取 那…

华为高级Java面试真题

今年IT寒冬&#xff0c;大厂都裁员或者准备裁员&#xff0c;作为开猿节流主要目标之一&#xff0c;我们更应该时刻保持竞争力。为了抱团取暖&#xff0c;林老师开通了《知识星球》&#xff0c;并邀请我阿里、快手、腾讯等的朋友加入&#xff0c;分享八股文、项目经验、管理经验…

解决jenkins的Exec command命令不生效,或者执行停不下来的问题

Jenkins构建完后将war包通过 Publish Over SSH 的插件发布到服务器上&#xff0c;在服务器上执行脚本时&#xff0c;脚本中的 nohup 命令无法执行&#xff0c;并不生效&#xff0c;我配置的Exec command命令是后台启动一个war包&#xff0c;并输出日志文件。 nohup java -jar /…

第二十三章 反射

第二十三章 反射 1.反射机制问题2.反射快速入门3.发射原理图4.反射相关类5.发射调用优化6.Class类分析7.Class常用方法8.获取Class对象的6种方式9.哪些类型有Class对象10.动态和静态加载11.类加载流程图12.类加载五个阶段&#xff08;1&#xff09;13.类加载五个阶段&#xff0…