【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar

简介: 【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar

注入案例代码


如何通过实现SpringBoot框架带有的ImportBeanDefinitionRegistrar注册器,注入我们想要注册的bean对象实例。只需要采用@Import的注解进行注入对应的一类相关的bean对象。

@Import({DataSourceRegister.class,A.class})
@SpringBootApplication
@ComponentScan("com.libo")
public class LiboApplication {
    public static void main(String[] args) {
        SpringApplication sa = new SpringApplication(LiboApplication.class);
        sa.run(args);
    }
}
复制代码



DataSourceRegister的开发实现


  • 在springboot启动的时候,loader模块会根据“清单文件”加载该Application类,并反射调用psvm入口函数main,@Import注解也可以导入一个常规类,并且创建注入很多对象实例


  • DataSourceRegister类是用来进行初始化数据源和并提供了执行动态切换数据源的工具类。




DataSourceRegister注入主数据源和从数据源


这里DataSourceRegister继承的EnvironmentAware接口,没有真正意义上去用它的用途,本身可以通过这个setEnvironment方法,进行注入Environment对象,从而可以读取其他的配置信息,目前主要用作一个hook方法。



读取对应的环境变量

public final void setEnvironment(Environment environment) {
        DruidEntity druidEntity = FileUtil.
      readYmlByClassPath("db_info", DruidEntity.class);
        defaultTargetDataSource =
      DataSourceUtil.createMainDataSource(druidEntity);
    }
复制代码


主要用于读取Druid的数据源模型信息。进行创建对应的数据源对象defaultTargetDataSource

注入Bean到Spring容器中




registerBeanDefinitions注册BeanDefinition对象模型


public final void registerBeanDefinitions(AnnotationMetadata  annotationMetadata, BeanDefinitionRegistry  beanDefinitionRegistry) {
        // 0.将主数据源添加到数据源集合中
        DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,defaultTargetDataSource);
        //1.创建DataSourceBean
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //spring名称约定为defaultTargetDataSource和targetDataSources
        mpv.addPropertyValue("defaultTargetDataSource",defaultTargetDataSource);
        mpv.addPropertyValue("targetDataSources",DataSourceSet.getTargetDataSourcesMap());
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    }
复制代码


完整的DataSourceRegister的案例

public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {
    private javax.sql.DataSource defaultTargetDataSource;
    static final String MAINDATASOURCE = "mainDataSource";
    public final void setEnvironment(Environment environment) {
        DruidEntity druidEntity = FileUtil.
      readYmlByClassPath("db_info", DruidEntity.class);
        defaultTargetDataSource =
      DataSourceUtil.createMainDataSource(druidEntity);
    }
    public final void registerBeanDefinitions(AnnotationMetadata 
                        annotationMetadata, BeanDefinitionRegistry 
                        beanDefinitionRegistry) {
        // 0.将主数据源添加到数据源集合中
        DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,
    defaultTargetDataSource);
        //1.创建DataSourceBean
        GenericBeanDefinition beanDefinition = new
      GenericBeanDefinition();
        beanDefinition.setBeanClass(DataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //spring名称约定为defaultTargetDataSource和targetDataSources
        mpv.addPropertyValue("defaultTargetDataSource",
    defaultTargetDataSource);
        mpv.addPropertyValue("targetDataSources",
    DataSourceSet.getTargetDataSourcesMap());
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    }
}
复制代码

执行流程解读


  • 动态数据源注册器类实现了ImportBeanDefinitionRegistrar接口,没错就是这个原因,由于实现了该接口让该类成为了拥有注册bean的能力。


  • 原理上也能说得通作为一个Bean的注册类是没有必要被注册为Spring容器的Bean对象


  • 虽然这样解释也不为过但我仍然想一探究竟,本来想大概找找spring涉及关键类如:


  • ConfigurationClass,ConfigurationClassParser等,接下来我们需要看一下SpringBoot的总体加载流程。




SpringApplication类的run方法


SpringBoot启动时使用了SpringApplication类的run方法来牵引整个spring的初始化过程,源码如下。


public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
     // 错误原因分析器
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
     // 重点分析:启动所有的运行的监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();
        try {
      // 解析ApplicationArgument数据
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 通过监听器和传递的参数,实现相关的配置环境对象信息,预先
      // 进行配置Environment对象,设置全局环境变量容器
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
      // 输出相关的Banner控制,根据配置以及相关的Environment
            Banner printedBanner = this.printBanner(environment);
      // 创建Spring容器上下文。
            context = this.createApplicationContext();
      //创建和赋值错误解析器
            analyzers = new FailureAnalyzers(context);
      // 准备环境上下文进行配置相关的容器的上下文的参数。
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 刷新上下文容器
            this.refreshContext(context);
      // 后置执行上下文操作
            this.afterRefresh(context, applicationArguments);
      // 完成监听器的后置完成处理操作
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).
          logStarted(this.getApplicationLog(), stopWatch);
            }
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, 
                  (FailureAnalyzers)analyzers, 
                  var9);
            throw new IllegalStateException(var9);
        }
    }
复制代码

根据上面的源码流程可以分析重点的加载过程


  1. 重点分析:启动所有的运行的监听器,获取SpringApplicationRunListeners; 从类路径下META-INF/spring.factories。回调所有的获取SpringApplicationRunListener.starting() 方法。
SpringApplicationRunListeners listeners = this.getRunListeners(args);
 listeners.starting();
复制代码



  1. 解析ApplicationArgument数据以及封装命令行参数,通过program参数的部分进行解析,并且加载到PropertiesSourcePlaceHolder中的环境变量容器内部。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
复制代码



通过上面的监听器和传递的参数,实现相关的配置环境对象信息,预先进行配置Environment对象,设置全局环境变量容器


  1. 根据上面的全局变量容器进行配置以及初始化相关的Environment对象。
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
复制代码



  1. 创建Spring容器上下文,在createApplicationContext当中,由于我们是web项目,则spring默认给我们创建了一个
context = this.createApplicationContext();
复制代码

image.png

AnnotationConfigEmbeddedWebApplicationContext,当然它也是继承GenericWebApplicationContext类和GenericApplicationContext类的,那么他默认会持有一个DefaultListableBeanFactory对象,这个对象可以用来创建Bean。



  1. 准备环境上下文进行配置相关的容器的上下文的参数
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
复制代码



  1. 刷新上下文容器,refreshContext就是在做spring运行后的初始化工作。
this.refreshContext(context);
复制代码



接着往下走,进入refreshContext中会调用一系列的refresh方法,最终进入AbstractApplicationContext中,主要将SpringBoot的容器对象数据和原本基础的Spring Framework的框架对象进行加载到容器中。

@Override
    public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory =
        obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                initMessageSource();
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // Check for listener beans and register them.
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context 
                initialization - " +"cancelling refresh attempt: " + ex);
                }
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();
                // Reset 'active' flag.
                cancelRefresh(ex);
                // Propagate exception to caller.
                throw ex;
            }
            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }
复制代码



invokeBeanFactoryPostProcessors() 方法就是Bean在注册前期做的一系列数据收集工作,BeanDefinitionRegistry的容器注册BeanDefinition之前,调用相关的


跟着堆栈继续深入,会进入到这个方法中,这个方法就是初始化bean前的所有轨迹:

image.png

在invokeBeanFactoryPostProcessors方法中继续跟进一系列方法就会看到在一开始的时候spring会初始化几个系统固有的Bean

image.png



继续调试后的关键点出现在这个方法中:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed 
                 as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }
        // Sort by previously determined @Order value, if applicable
        Collections.sort(configCandidates, new 
             Comparator<BeanDefinitionHolder>() {
            @Override
            public int compare(BeanDefinitionHolder bd1, 
                 BeanDefinitionHolder bd2) {
                int i1 = 
          ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = 
          ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
            }
        });
        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
        do {
            parser.parse(candidates);
            parser.validate();
            Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);
            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<String>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                  alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        if (sbr != null) {
            if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
                sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
            }
        }
        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }
复制代码



而通过不断重复调试确定获得注册Bean的列表应该发生在配置的“剖析阶段”,也就是parser.parse(candidates);这个方法的内部,到了这里基本问题的答案已经要浮出水面了,我也不再粘贴无用的代码,如果你真的对这个问题比骄傲好奇可以自己跟踪并练习调试的源码技巧!


当然在ConfigurationClassParser这个类中parse方法也是不少,只要静下心来逐渐分析,马上就能准确的找到Override的parse方法。

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
                    if (configClass.equals(it.next())) {
                        it.remove();
                    }
                }
            }
        }
        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);//处理定义的配置类
        }
        while (sourceClass != null);
        this.configurationClasses.put(configClass, configClass);
    }
复制代码
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass);
        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }
        // Process any @ComponentScan annotations        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(
                            holder.getBeanDefinition(), this.metadataReaderFactory)) {
                        parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);//处理注解导入的类型
        // Process any @ImportResource annotations
        if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
            AnnotationAttributes importResource =
                    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }
        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }
        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);
        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }
        // No superclass -> processing is complete
        return null;
    }
复制代码



以上两个方法中标红的就是关键点。而且spring的大师们也把注释写的十分明显:”//Process any @Import annotations“,到这里已经彻底豁然开朗!


spring会先去处理scan,将你程序内部的所有要注册的Bean全部获得(自然包括那些configuration),这里统称为ConfigurationClass,scan全部整理完毕后才会去处理@Import注解时导入的类!


我们回到最初的问题 DataSourceRegister和A两个类为什么A成为了Bean但DataSourceRegister却未成为Bean呢?


在processImports方法中,很明显candidate.isAssignable(ImportBeanDefinitionRegistrar.class)时操作为:configClass.addImportBeanDefinitionRegistrar(registrar,


currentSourceClass.getMetadata());而普通的类通过processConfigurationClass(candidate.asConfigClass(configClass));方法,最终会被放在ConfigurationClassParser类的成员变量configurationClasses中,最终被初始化为Bean。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }
复制代码


后置执行上下文操作

this.afterRefresh(context, applicationArguments);
复制代码


完成监听器的后置完成处理操作

listeners.finished(context, (Throwable)null);
复制代码



至此,总体的mportBeanDefinitionRegistrar的对象注入体系就基本介绍完了




相关文章
|
11天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
18小时前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
17 9
|
15天前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
64 6
|
19天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
51 2
|
21天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
52 0
|
Java 应用服务中间件 数据库连接
Spring全家桶之Spring篇深度分析(一)
Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring全家桶之Spring篇深度分析(一)
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
27天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
141 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
27天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
46 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块