springboot集成nacos读取nacos配置数据的原理

 更新时间:2023年05月29日 09:00:25   作者:IT人的天地  
这篇文章主要介绍了springboot集成nacos读取nacos配置数据的原理,文中有详细的代码流程,对大家学习springboot集成nacos有一定的帮助,需要的朋友可以参考下

1、Nacos config springboot starter包

我们在springboot应用中集成nacos配置中心时,添加了以下依赖:

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-starter</artifactId>
            <version>0.2.11</version>
        </dependency>

它会自动导入nacos-config-spring-boot-autoconfigure包和其他nacos客户端jar包。

看到nacos-config-spring-boot-autoconfigure这种自动配置包,我们要先打开这个jar包,看下包目录下的/META-INF/spring.factories文件,里面有如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration
#org.springframework.context.ApplicationContextInitializer=\
#  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor
org.springframework.context.ApplicationListener=\
  com.alibaba.boot.nacos.config.logging.NacosLoggingListener

本次我们关注的重点是org.springframework.boot.env.EnvironmentPostProcessor这个配置项的值,它只有一个值:

com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor

我们来看下NacosConfigEnvironmentProcessor这个类,它有如下定义:

public class NacosConfigEnvironmentProcessor
        implements EnvironmentPostProcessor, Ordered {
       。。。。省略部分代码
        @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        application.addInitializers(new NacosConfigApplicationContextInitializer(this));
        nacosConfigProperties = NacosConfigPropertiesUtils
                .buildNacosConfigProperties(environment);
        if (enable()) {
            System.out.println(
                    "[Nacos Config Boot] : The preload log configuration is enabled");
            loadConfig(environment);
            NacosConfigLoader nacosConfigLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);
            LogAutoFreshProcess.build(environment, nacosConfigProperties, nacosConfigLoader, builder).process();
        }
    }
}

NacosConfigEnvironmentProcessor就做了一件事,往spring容器中添加了NacosConfigApplicationContextInitializer初始化器,后续由它完成从nacos配置中心加载数据的操作。

1.1、重要的NacosConfigEnvironmentProcessor是在哪执行的

这个问题等我们看完下面的代码就有了答案了~

1.2、重要的NacosConfigApplicationContextInitializer是在哪执行的

这个问题等我们看完下面的代码也会有答案了~

2、应用的启动过程

我们从以下的启动类入手。

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

2.1、SpringApplication的构造方法做了啥

跟踪上面SpringApplication的run方法,SpringApplication.run方法内部会先创建一个SpringApplication对象,然后再调用该对象的另一个run实例方法。我们先进入SpringApplication的构造方法:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList<>(
                getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        //从类路径下面的META-INF/spring.factories文件中根据ApplicationContextInitializer配置值来初始化ApplicationContextInitializer实例
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //从类路径下面的META-INF/spring.factories文件中根据ApplicationListener配置值来初始化ApplicationListener实例
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

其中getSpringFactoriesInstances方法用于从类路径下面的META-INF/spring.factories文件中获取指定配置项对应的类的全路径名列表(多个类的全路径名之间用英文逗号隔开),根据类的全路径名创建相应的对象。

2.1.1、构造方法中的setInitializers方法

setInitializers方法用于设置容器的初始化器对象集合,它的参数来源于getSpringFactoriesInstances(ApplicationContextInitializer.class)的执行结果。getSpringFactoriesInstances方法从类路径下面的META-INF/spring.factories文件中获取ApplicationContextInitializer对应的类的全路径名列表,根据类的全路径名创建相应的对象。

2.1.2、构造方法中的setListeners方法

setListeners方法用于设置容器的ApplicationListener监听器对象,它的参数来源于getSpringFactoriesInstances(ApplicationListener.class)的执行结果。getSpringFactoriesInstances方法从类路径下面的META-INF/spring.factories文件中获取ApplicationListener对应的类的全路径名列表,根据类的全路径名创建相应的对象。spring的监听器就是监听器模式的一种实现,它可以设置自己感兴趣的事件,并且在相应事情发生时能接到通知并对该事件进行处理。我们在spring-boot.jar包下的META-INF/spring.factories可以看到如下的内容:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

这些监听器类都会被实例化,并存入spring容器的ApplicationListener监听器列表中。

2.1.3、重要的EnvironmentPostProcessorApplicationListener出现了

经过了2.1.2部分的代码观摩,我们知道容器的ApplicationListener监听器列表中已经有了EnvironmentPostProcessorApplicationListener对象,EnvironmentPostProcessorApplicationListener很重要,它能监听到ApplicationEnvironmentPreparedEvent类型的事件,并且会触发EnvironmentPostProcessor实例的执行。我们在第1部分讲到的NacosConfigEnvironmentProcessor就是个EnvironmentPostProcessor的实现类。此处我们先打个标记,后面我们会说下1.1小节的NacosConfigEnvironmentProcessor实例对象是在哪执行的。

2.2、SpringAppLication的run方法执行流程

完成2.1节的构造方法后,会执行如下的run方法(本方法很重要):

    public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //准备环境信息(读取应用系统需要的所有配置项)
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //准备容器上下文
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //刷新容器上下文(内部就是执行出名的refresh方法)
            refreshContext(context);
            。。。省略部分代码
            callRunners(context, applicationArguments);
        }
        。。省略部分代码
        return context;
    }

结合我们本篇要说读取nacos配置中心的原理,本次我们主要关注getRunListeners、prepareEnvironment方法和prepareContext这三个方法,我们先按照代码的执行顺序来依次看下这几个方法。

2.2.1、getRunListeners方法

getRunListeners方法如下:

private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger,
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
         this.applicationStartup);
}

它会读取类路径下面的META-INF/spring.factories文件中SpringApplicationRunListener配置项对应的值,并创建相应的实例对象,赋值给SpringApplicationRunListeners的listeners属性(List类型),并返回SpringApplicationRunListeners对象。由于spring-boot包的META-INF/spring.factories文件有如下内容:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

所以SpringApplicationRunListeners的listeners属性中就包含了EventPublishingRunListener实例对象,而且实际上只有这一个对象。

2.2.2、prepareEnvironment方法开始执行

prepareEnvironment方法内容如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        //通过监听器通知环境信息已准备好,触发EnvironmentPostProcessor的执行
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
            environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

此处划重点,在执行listeners.environmentPrepared方法之前,我们的操作系统环境变量、jvm系统属性和业务系统的application.properties(以及其他的配置文件)的配置项信息都被填充到了容器的environment对象中。

2.2.1部分的getRunListeners方法返回的SpringApplicationRunListeners对象会作为参数传递给prepareEnvironment方法的listeners参数,而listeners.environmentPrepared就是执行SpringApplicationRunListeners的environmentPrepared方法,方法内容如下:

//SpringApplicationRunListeners类
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        doWithListeners("spring.boot.application.environment-prepared",
                (listener) -> listener.environmentPrepared(bootstrapContext, environment));
    }

里面的doWithListeners方法最后会执行到重载的doWithListeners方法:

//SpringApplicationRunListeners类
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
            Consumer<StartupStep> stepAction) {
        StartupStep step = this.applicationStartup.start(stepName);
        this.listeners.forEach(listenerAction);
        if (stepAction != null) {
            stepAction.accept(step);
        }
        step.end();
    }

前面2.2.1部分我们说了,listeners对象就是个list,此处它只有一个元素,是EventPublishingRunListener类型的实例对象。

listenerAction对象接收的是前面environmentPrepared方法内部传递的lambda表达式:

(listener) -> listener.environmentPrepared(bootstrapContext, environment) 

当执行this.listeners.forEach(listenerAction)时,就会进入EventPublishingRunListener类的environmentPrepared方法:

//EventPublishingRunListener类
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
            ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(
                new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
    }

此处会调用SimpleApplicationEventMulticaster类的multicastEvent方法,并传递ApplicationEnvironmentPreparedEvent事件进行广播。

//另外此处要先说明下:当spring创建EventPublishingRunListener对象的时候,就已经将SpringApplication对象的ApplicationListener对象列表添加到了EventPublishingRunListener对象的initialMulticaster属性的监听器列表中,EventPublishingRunListener构造函数代码如下:
public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

然后我们继续进入SimpleApplicationEventMulticaster类的multicastEvent方法:

//SimpleApplicationEventMulticaster类
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                //会直接调用监听器
                invokeListener(listener, event);
            }
        }
    }

这里的getApplicationListeners(event, type)会根据事件类型获取对该事件感兴趣的监听器列表,然后挨个调用监听器对象,也就是触发各个监听器的处理流程。由于此处的event参数是接收了ApplicationEnvironmentPreparedEvent对象,那么getApplicationListeners方法就会获取到EnvironmentPostProcessorApplicationListener对象(2.1.3部分咱们提到过它)。为啥呢?我们进入EnvironmentPostProcessorApplicationListener类,它有如下的supportsEventType方法:

//EnvironmentPostProcessorApplicationListener类
@Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
                || ApplicationPreparedEvent.class.isAssignableFrom(eventType)
                || ApplicationFailedEvent.class.isAssignableFrom(eventType);
    }

supportsEventType方法就说明了该类支持处理的事件类型,里面很明确表达了它支持ApplicationEnvironmentPreparedEvent事件,所以getApplicationListeners就能把它筛选到。

我们接着上面的invokeListener方法往下看,invokeListener内部最后会直接调用监听器的onApplicationEvent方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        }
        。。省略部分代码
}

此处我们假定当前的listener已经是EnvironmentPostProcessorApplicationListener实例对象,那我们去看看EnvironmentPostProcessorApplicationListener类的onApplicationEvent方法:

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent();
        }
        if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        SpringApplication application = event.getSpringApplication();
        //重点,获取所有的EnvironmentPostProcessor实例对象
        for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
                event.getBootstrapContext())) {
            postProcessor.postProcessEnvironment(environment, application);
        }
    }

上面就又出现了重点部分,这里会获取所有的EnvironmentPostProcessor实例,并且挨个执行它们的postProcessEnvironment方法,所以此时NacosConfigEnvironmentProcessor对象的postProcessEnvironment方法也就要被执行了。

2.2.3、重要的NacosConfigEnvironmentProcessor对象终于要被执行了

经过了一番探索,我们在1.1小节提出的问题在此处终于有了答案~

我们来看下NacosConfigEnvironmentProcessor的postProcessEnvironment方法:

//NacosConfigEnvironmentProcessor类
@Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        //重点,往容器中添加了NacosConfigApplicationContextInitializer对象
        application.addInitializers(new NacosConfigApplicationContextInitializer(this));
        nacosConfigProperties = NacosConfigPropertiesUtils
                .buildNacosConfigProperties(environment);
        //此方法内部逻辑可以先忽略
        if (enable()) {
            System.out.println(
                    "[Nacos Config Boot] : The preload log configuration is enabled");
            loadConfig(environment);
            NacosConfigLoader nacosConfigLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);
            LogAutoFreshProcess.build(environment, nacosConfigProperties, nacosConfigLoader, builder).process();
        }
    }

上述代码主要就做了一件事,往spring容器中添加了NacosConfigApplicationContextInitializer初始化器。

2.2.4、NacosConfigApplicationContextInitializer终于被加入到了spring容器中

2.1.1部分我们看到spring容器中已经有了一批ApplicationInitializer对象。刚才2.2.3部分的NacosConfigEnvironmentProcessor又往容器中添加了一个NacosConfigApplicationContextInitializer对象。这些实现了ApplicationInitializer接口的对象,它们会在执行ApplicationContext的refresh方法之前得到调用。我们继续往下看。

2.2.5、prepareEnvironment方法执行完成

此处只是做个标记,咱们继续往下看~

2.2.6、prepareContext方法开始执行

紧接着我们就来到了run方法内部的prepareContext方法,它的内容如下:

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        //执行初始化器
        applyInitializers(context);
        。。。。省略部分代码
 }

其中applyInitializers方法的代码如下,它会挨个调用每个ApplicationContextInitializer实例的initialize方法,完成应用上下文的初始化操作:

/**
     * Apply any {@link ApplicationContextInitializer}s to the context before it is
     * refreshed.
     * @param context the configured ApplicationContext (not refreshed yet)
     * @see ConfigurableApplicationContext#refresh()
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            //执行初始化器的初始化方法
            initializer.initialize(context);
        }
    }

我们注意到ApplicationContextInitializer实例是从getInitializers()方法获取的,而getInitializers()方法就是返回了容器中所有的ApplicationContextInitializer对象,当然也包括NacosConfigApplicationContextInitializer对象。

2.2.7、重要的NacosConfigApplicationContextInitializer对象终于要被执行了

我们看下NacosConfigApplicationContextInitializer类的initialize方法:

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        singleton.setApplicationContext(context);
        environment = context.getEnvironment();
        nacosConfigProperties = NacosConfigPropertiesUtils
                .buildNacosConfigProperties(environment);
        final NacosConfigLoader configLoader = NacosConfigLoaderFactory.getSingleton(
                nacosConfigProperties, environment, builder);
        if (!enable()) {
            logger.info("[Nacos Config Boot] : The preload configuration is not enabled");
        }
        else {
            // If it opens the log level loading directly will cache
            // DeferNacosPropertySource release
            if (processor.enable()) {
                processor.publishDeferService(context);
                configLoader
                        .addListenerIfAutoRefreshed(processor.getDeferPropertySources());
            }
            else {
                //远程访问nacos配置中心读取配置数据
                configLoader.loadConfig();
                //设置监听器来监听nacos配置中心数据的变更并更新到本地
                configLoader.addListenerIfAutoRefreshed();
            }
        }
        final ConfigurableListableBeanFactory factory = context.getBeanFactory();
        if (!factory
                .containsSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME)) {
            factory.registerSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME,
                    configLoader.getGlobalProperties());
        }
    }

下面展示下NacosConfigLoader类的loadConfig方法,内部实现比较易懂,就是根据我们配置的nacos的访问地址读取指定的dataId的数据,并将其封装为一个属性源存放到environment对象中。

//NacosConfigLoader类 
public void loadConfig() {
        MutablePropertySources mutablePropertySources = environment.getPropertySources();
        List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties,
                nacosConfigProperties.getType());
        for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
            List<NacosPropertySource> elements = reqSubNacosConfig(config,
                    globalProperties, config.getType());
            sources.addAll(elements);
        }
       //如果远程nacos的配置数据比本地配置数据的优先级高,则执行以下方法
        if (nacosConfigProperties.isRemoteFirst()) {
            for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {
                //这里是个关键点,可以确保远程的配置数据会被优先使用
                mutablePropertySources.addAfter(
                        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());
            }
        } else {
            for (NacosPropertySource propertySource : sources) {
                mutablePropertySources.addLast(propertySource);
            }
        }
    }

nacos属性源被添加到容器的environment对象中的代码如下:

MutablePropertySources mutablePropertySources = environment.getPropertySources();
。。。省略部分代码
mutablePropertySources.addAfter(
                        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());

到此为止,spring启动所需的所有的配置项数据都已准备完毕,后面spring在初始化各个bean对象的时候也能根据我们在nacos上面配置的数据来创建各个实例对象,比如dataSource对象~

2.2.8、prepareContext方法执行完成

到此,容器的应用上下文都准备完毕,紧接着就会执行大名鼎鼎的refresh方法来完成spring容器的整体创建过程,此处就不再赘述了。

refreshContext(context); 

以上就是springboot集成nacos读取nacos配置数据的原理的详细内容,更多关于springboot集成nacos读取数据的资料请关注脚本之家其它相关文章!

相关文章

  • C语言实现矩阵运算案例详解

    C语言实现矩阵运算案例详解

    这篇文章主要介绍了C语言实现矩阵运算案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 使用java判断输入年份是否为闰年完整代码

    使用java判断输入年份是否为闰年完整代码

    闰年的引入确保了我们的日历与地球运行轨道的对齐,使得时间的计算更加准确,在编程中判断给定年份是否为闰年是一项常见的任务,这篇文章主要给大家介绍了关于使用java判断输入年份是否为闰年的相关资料,需要的朋友可以参考下
    2023-10-10
  • springboot使用redis对单个对象进行自动缓存更新删除的实现

    springboot使用redis对单个对象进行自动缓存更新删除的实现

    本文主要介绍了springboot使用redis对单个对象进行自动缓存更新删除的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • SpringBoot实现OneDrive文件上传的详细步骤

    SpringBoot实现OneDrive文件上传的详细步骤

    这篇文章主要介绍了SpringBoot实现OneDrive文件上传的详细步骤,文中通过代码示例和图文讲解的非常详细,对大家实现OneDrive文件上传有一定的帮助,需要的朋友可以参考下
    2024-02-02
  • mybatis 根据id批量删除的实现操作

    mybatis 根据id批量删除的实现操作

    这篇文章主要介绍了mybatis 根据id批量删除的实现操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 使用Mybatis如何实现删除多个数据

    使用Mybatis如何实现删除多个数据

    这篇文章主要介绍了使用Mybatis如何实现删除多个数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java实现国产加密算法SM4的示例详解

    Java实现国产加密算法SM4的示例详解

    这篇文章主要为大家详细介绍了Java如何实现国产加密算法SM4(ECB和CBC两种模式),文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-01-01
  • 详解Java中-classpath和路径的使用

    详解Java中-classpath和路径的使用

    本篇文章主要介绍了Java中-classpath和路径的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • 一次mybatis连接查询遇到的坑实战记录

    一次mybatis连接查询遇到的坑实战记录

    这篇文章主要给大家介绍了关于一次mybatis连接查询遇到的坑的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • java如何实现基于opencv全景图合成实例代码

    java如何实现基于opencv全景图合成实例代码

    全景图相信大家应该都不陌生,下面这篇文章主要给大家介绍了关于java如何实现基于opencv全景图合成的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-07-07

最新评论