Excel Module: Iteration #1 EasyExcel生成下拉列表模版时传入动态参数查询下拉数据

系列文章

  1. EasyExcel生成带下拉列表或多级级联列表的Excel模版+自定义校验导入数据(修订)

目录

  • 系列文章
  • 前言
  • 仓库
  • 一、实现
    • 1.1 下拉元数据对象
    • 1.2 构建下拉元数据的映射关系
    • 1.3 框架方式
      • 1.3.1 框架实现
      • 1.3.2 框架用例
        • 模版类
        • 加载下拉业务
        • 导出接口
    • 1.4 EasyExcel方式
      • 1.4.1 EasyExcel用例
        • 导出接口
  • 二、效果
    • 2.1 下拉sheet
    • 2.2 业务sheet
  • 附录
    • SpELHelper


前言

有同好提过这问题, 有一定的使用场景, 最近设计了一下并落地了, 支持直接使用框架方式或直接使用EasyExcel方式.

该迭代仅限使用SpEL能力最新版本代码, 历史版本忽略.


仓库

仓库: excel-common-spring-boot-starter (请参考最新代码, 文档内容更新不会很勤快, 没License看得上就随便用)


一、实现

核心逻辑: 创建下拉元数据对象时需要通过解析SpEL表达式获取下拉数据, 该能力提供的上下文对象EvaluationContext支持传入自定义参数, 并可以根据表达式指定的参数名从上下文中获取到指定参数并传给指定的调用方法.

调用方式: 使用框架和直接使用EasyExcel的两种方式, 在传入动态参数的方式上有所不同, 用例 部分会通过代码说明.

例如: SpEL表达式为 @xxxBean.findAllByTypeAndCode(#type, #code)

  • @开头指定选择SpringBean的名称
  • .findAllByTypeAndCode指定调用该bean的方法
  • #type指定从表达式上下文中解析名为 type 和 code 的 变量, 并传入findAllByTypeAndCode()方法.

1.1 下拉元数据对象

解析表达式时, 通过Visitor将参数设置到上下文中.

  • SpELHelper 见 附录 部分
/**
 * @author hp
 */
@Slf4j
@Getter
@Setter
public abstract class AbstractExcelSelectModel<T> {

    protected int headLayerCount;

    protected T options;

    protected String columnName;

    protected int columnIndex;

    protected String parentColumnName;

    protected int parentColumnIndex;

    protected int firstRow;

    protected int lastRow;

    public AbstractExcelSelectModel(@Nonnull Field field, @Nonnull ExcelSelect excelSelect, @Nullable ExcelProperty excelProperty, int defaultSort, @Nullable Map<String, Object> parameters) {
        final Optional<ExcelProperty> excelPropertyOpt = Optional.ofNullable(excelProperty);
        this.headLayerCount = excelPropertyOpt.map(property -> property.value().length).orElse(1);
        this.firstRow = Math.max(excelSelect.firstRow(), this.headLayerCount);
        this.lastRow = excelSelect.lastRow();

        this.parentColumnName = excelSelect.parentColumnName();
        this.columnName = excelPropertyOpt.map(property -> property.value()[this.headLayerCount - 1]).orElse(field.getName());
        this.columnIndex = excelPropertyOpt.map(property -> property.index() > -1 ? property.index() : defaultSort).orElse(defaultSort);

        this.options = resolveOptions(excelSelect, parameters);
    }

    public boolean hasParentColumn() {
        return StrUtil.isNotEmpty(this.parentColumnName);
    }

    @SuppressWarnings("unchecked")
    @Nullable
    protected T resolveOptions(@Nonnull ExcelSelect excelSelect, @Nullable Map<String, Object> parameters) {
        final ExcelOptions excelOptions = excelSelect.options();
        if (StrUtil.isEmpty(excelOptions.expression())) {
            log.warn("The ExcelSelect on {} has no options whatsoever.", this.columnName);
            return null;
        }
        final SpELHelper spELHelper = SpringUtil.getBean(SpELHelper.class);
        return (T) spELHelper.newGetterInstance(excelOptions.expression()).apply(
                null,
                 // 在这里向上下文中设置自定义变量.
                (evaluationContext -> Optional.ofNullable(parameters).ifPresent(map -> map.forEach(evaluationContext::setVariable)))
        );
    }
}

1.2 构建下拉元数据的映射关系

Convention: @Nullable Map<String, Object> parameters 参数定义为Map集合类型

提交动态数据的入口, 同时根据实际列注解情况构建下拉元数据的映射关系


/**
 * @author hp
 */
@Slf4j
@UtilityClass
public class ExcelSelectHelper {

    @Nullable
    public static <T> Map<Integer, ? extends AbstractExcelSelectModel<?>> createSelectionMapping(@Nonnull Class<T> dataClass) {
        return createSelectionMapping(dataClass, null);
    }

    @Nullable
    public static <T> Map<Integer, ? extends AbstractExcelSelectModel<?>> createSelectionMapping(@Nonnull Class<T> dataClass, @Nullable Map<String, Object> parameters) {
        final Field[] fields = ReflectUtil.getFields(dataClass);
        final AtomicInteger fieldIndex = new AtomicInteger(0);
        final Map<Integer, ? extends AbstractExcelSelectModel<?>> selectionMapping = Arrays.stream(fields)
                .map(field -> {
                    final ExcelSelect excelSelect = AnnotatedElementUtils.getMergedAnnotation(field, ExcelSelect.class);
                    if (Objects.isNull(excelSelect)) {
                        log.debug("No ExcelSelect annotated on {}, skip processing", field.getName());
                        fieldIndex.getAndIncrement();
                        return null;
                    }
                    final ExcelProperty excelProperty = AnnotatedElementUtils.getMergedAnnotation(field, ExcelProperty.class);
                    AbstractExcelSelectModel<?> excelSelectModel;
                    if (StrUtil.isNotEmpty(excelSelect.parentColumnName())) {
                        excelSelectModel = new ExcelCascadeModel(field, excelSelect, excelProperty, fieldIndex.getAndIncrement(), parameters);
                    } else {
                        excelSelectModel = new ExcelSelectModel(field, excelSelect, excelProperty, fieldIndex.getAndIncrement(), parameters);
                    }
                    return excelSelectModel;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(AbstractExcelSelectModel::getColumnIndex, Function.identity(), (a, b) -> a));

        if (MapUtil.isEmpty(selectionMapping)) {
            return null;
        }

        // 设置父列索引
        final Map<String, Integer> columnNamedMapping = selectionMapping.values()
                .stream()
                .collect(Collectors.toMap(AbstractExcelSelectModel::getColumnName, AbstractExcelSelectModel::getColumnIndex));
        selectionMapping.forEach((k, v) -> {
            if (v.hasParentColumn() && columnNamedMapping.containsKey(v.getParentColumnName())) {
                v.setParentColumnIndex(columnNamedMapping.get(v.getParentColumnName()));
            }
        });

        return selectionMapping;
    }
}

1.3 框架方式

框架方式基于SpringAOP能力, 纯静态配置方式导出模版或数据, 所以无法通过人为调用的方式提交动态参数.

1.3.1 框架实现

同动态指定导出文件名称的方式相同, 在导出时, 通过向HttpServletRequest对象中设置指定Key(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY)的Attributes, 根据约定, 框架将通过该指定Key查询是否存在参数, 有则使用.

框架通过Enhance类配置ExcelWriterBuilderExcelWriterSheetBuilder, 并且框架考虑自动导出多sheet情况, 所以在enhanceExcel()中获取动态参数并处理.

/**
 * @author hp
 */
@Slf4j
public class ExcelSelectExcelWriterBuilderEnhance implements ExcelWriterBuilderEnhance {

    protected final AtomicInteger selectionColumnIndex = new AtomicInteger(0);
    protected Map<Class<?>, Map<Integer, ? extends AbstractExcelSelectModel<?>>> selectionMapMapping = Maps.newHashMap();

    @SuppressWarnings("unchecked")
    @Override
    public ExcelWriterBuilder enhanceExcel(
            ExcelWriterBuilder writerBuilder,
            ResponseExcel responseExcel,
            Collection<? extends Class<?>> dataClasses,
            HttpServletRequest request,
            HttpServletResponse response
    ) {
        final Object attribute = Objects.requireNonNull(request).getAttribute(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY);
        final Map<String, Object> parameters = Optional.ofNullable(attribute)
                .map(attr -> {
                    Preconditions.checkArgument(attr instanceof Map<?, ?>);
                    return (Map<String, Object>) attribute;
                }).orElse(null);
        dataClasses.forEach(dataClass -> selectionMapMapping.put(dataClass, ExcelSelectHelper.createSelectionMapping(dataClass, parameters)));
        return writerBuilder.registerWriteHandler(new SelectDataWorkbookWriteHandler());
    }

    @Override
    public ExcelWriterSheetBuilder enhanceSheet(
            ExcelWriterSheetBuilder writerSheetBuilder,
            Integer sheetNo,
            String sheetName,
            Class<?> dataClass,
            Class<? extends HeadGenerator> headEnhancerClass,
            String templatePath) {
        if (selectionMapMapping.containsKey(dataClass)) {
            final Map<Integer, ? extends AbstractExcelSelectModel<?>> selectionMapping = selectionMapMapping.get(dataClass);
            writerSheetBuilder.registerWriteHandler(new SelectDataSheetWriteHandler(selectionColumnIndex, selectionMapping));
        }
        return writerSheetBuilder;
    }
}

1.3.2 框架用例

模版类
/**
 * @author hp
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class DynamicParametersExcelTemplate extends ExcelTemplate {

    @ExcelSelect(
            options = @ExcelOptions(expression = "@excelSelectDynamicDataHandler.findAll()")
    )
    @ExcelProperty("动态单列下拉列表")
    private LocalDate dynamicSelectColumn;

    @ExcelProperty("非下拉列No3")
    private String noneDropdownNo3;

    @ExcelSelect(
            options = @ExcelOptions(expression = "@excelSelectDynamicParameterDataHandler.findAllForParentByType(#type)")
    )
    @ExcelProperty("动态参数父列")
    private Integer dynamicParameterParentColumn;

    @ExcelProperty("非下拉列No1")
    private String noneDropdownNo1;

    @ExcelProperty("非下拉列No2")
    private String noneDropdownNo2;

    @ExcelSelect(
            parentColumnName = "动态参数父列",
            options = @ExcelOptions(expression = "@excelSelectDynamicParameterDataHandler.findAllForChildrenByValueGt(#value)")
    )
    @ExcelProperty("动态参数子列")
    private Integer dynamicParameterChildColumn;
}
加载下拉业务
/**
 * @author hp
 */
@Component
public class ExcelSelectDynamicParameterDataHandler {

    public List<String> findAllForParentByType(String type) {

        final Map<String, List<String>> map = Maps.newHashMap();

        map.put("TYPE-A", List.of("1", "2", "3", "4"));
        map.put("TYPE-B", List.of("一", "二", "三", "四"));

        return map.getOrDefault(type, Collections.emptyList());
    }

    public Map<String, List<Integer>> findAllForChildrenByValueGt(Integer limitation) {

        final Map<String, List<Integer>> map = Maps.newHashMap();

        map.put("1", Stream.of(10, 20, 30, 40, 50, 60).filter(i-> i> limitation).toList());
        map.put("2",  Stream.of(30, 40, 50, 60, 70, 80, 90).filter(i-> i> limitation).toList());

        return map;
    }
}
导出接口
@ResponseExcel(
        name = "DynamicParametersExcelTemplate",
        sheets = {
                @Sheet(sheetName = "sheet", sheetNo = 0),
        },
        enhancement = {ExcelSelectExcelWriterBuilderEnhance.class}
)
@PostMapping("template/sheet/single/DynamicParametersExcelTemplate")
public List<DynamicParametersExcelTemplate> singleSheet5(HttpServletRequest request) {
    final Map<String, Object> map = Maps.newHashMap();
    map.put("type","TYPE-A");
    map.put("value",40);
    request.setAttribute(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY, map);

    return Collections.singletonList(new DynamicParametersExcelTemplate());
}

1.4 EasyExcel方式

原生方式相当于很多框架做的配置工作都给人来完成, 那自然很容易拿到提交动态参数的入口, 可以直接提交参数.

1.4.1 EasyExcel用例

导出接口

在设置handler时, 直接将下拉元数据映射提交给 SelectDataSheetWriteHandler 即可, 此时可以提交动态参数, 用例里偷懒写了null.

  • new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo1ExcelTemplate.class, null))
@PostMapping("/easyexcel/template")
public void template(HttpServletRequest request, HttpServletResponse response) {
    String filename = "文件名称";

    final AtomicInteger index = new AtomicInteger(0);

    String userAgent = request.getHeader("User-Agent");
    if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
        // 针对IE或者以IE为内核的浏览器:
        filename = java.net.URLEncoder.encode(filename, StandardCharsets.UTF_8);
    } else {
        // 非IE浏览器的处理:
        filename = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", filename + ".xlsx"));
    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", -1);
    response.setCharacterEncoding("UTF-8");
    final ExcelWriterBuilder excelWriterBuilder;
    try {
        excelWriterBuilder = EasyExcel.write(response.getOutputStream());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    try (
            ExcelWriter excelWriter = excelWriterBuilder
                    .registerWriteHandler(
                            new SelectDataWorkbookWriteHandler()
                    )
                    .build()
    ) {

        WriteSheet writeSheet = EasyExcel
                .writerSheet(0, "sheet名称")
                .head(MultipleSheetNo1ExcelTemplate.class)
                .registerWriteHandler(new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo1ExcelTemplate.class, null)))
                .build();
        excelWriter.write(new ArrayList<String>(), writeSheet);

        WriteSheet writeSheet2 = EasyExcel
                .writerSheet(1, "sheet名称2")
                .head(MultipleSheetNo2ExcelTemplate.class)
                .registerWriteHandler(new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo2ExcelTemplate.class, null)))
                .build();
        excelWriter.write(new ArrayList<String>(), writeSheet2);

        excelWriter.finish();
    } catch (Exception e) {
        log.error("导出Excel文件异常", e);
    }
}

二、效果

2.1 下拉sheet

可见根据框架用例的条件和查询方法, TYPE-A 类型的数据和 大于40的数据被用于创建下拉选项
在这里插入图片描述

2.2 业务sheet

在这里插入图片描述


附录

SpELHelper

package com.luban.common.base.utils;

import cn.hutool.core.util.StrUtil;
import com.luban.common.base.visitor.Visitor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

/**
 * 很多判断是Groovy语法
 * <p>
 * Tips:
 * <ul>
 *   <li>字符串单引号. 可以调用方法或访问属性</li>
 *   <li>属性首字母大小写不敏感</li>
 *   <li>集合元素: Map用 {@code map['key']} 获取元素, Array/List用 {@code 集合名称[index]} 获取元素</li>
 *   <li>定义List: {@code {1,2,3,4} 或 {{'a','b'},{'x','y'}} }</li>
 *   <li>instance of: {@code 'xyz' instanceof T(int)}</li>
 *   <li>正则: {@code '字符串' matches '正则表达式'}</li>
 *   <li>逻辑运算符: {@code !非 and与 or或}</li>
 *   <li>类型: {@code java.lang包下直接用, 其他的要用T(全类名)}</li>
 *   <li>构造器: {@code new 全类名(构造参数)}</li>
 *   <li>变量: StandardEvaluationContext当中的变量 {@code  #变量名称 }</li>
 *   <li>#this: 当前解析的对象</li>
 *   <li>#root: 上下文的根对象</li>
 *   <li>Spring Bean引用: {@code @beanName} </li>
 *   <li>三元表达式和Java一样</li>
 *   <li>Elvis Operator: {@code Names?:'Unknown'} Names为空提供默认值</li>
 *   <li>防NPE操作符: {@code PlaceOfBirth?.City} 如果为NULL 防止出现NPE</li>
 *   <li>筛选集合元素: {@code 集合.?[筛选条件]} 如果是Map集合,Map.Entry为当前判断对象</li>
 *   <li>筛选第一个满足集合元素: {@code 集合.^[筛选条件]}</li>
 *   <li>筛选第一个满足集合元素: {@code 集合.$[筛选条件]}</li>
 *   <li>集合映射,类似StreamAPI的map()再collect(): 使用语法 {@code 集合.![映射规则]}, Map集合类似上述说明</li>
 *   <li>表达式模版: 默认{@code #{} }, 指定解析模版内部的内容</li>
 * </ul>
 *
 * @author hp
 */

@Slf4j
@Configuration
public class SpELHelper implements ApplicationContextAware {

    private BeanResolver beanResolver;
    private final ExpressionParser expressionParser = new SpelExpressionParser();
    private final ParserContext parserContext = ParserContext.TEMPLATE_EXPRESSION;

    public <T, R> StandardSpELGetter<T, R> newGetterInstance(String expression) {
        return new StandardSpELGetter<>(expression, new StandardEvaluationContext());
    }

    public <T, R> StandardSpELSetter<T, R> newSetterInstance(Field field) {
        return new StandardSpELSetter<>(field);
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.beanResolver = new BeanFactoryResolver(applicationContext);
    }

    public class StandardSpELGetter<T, R> implements BiFunction<T, Visitor<EvaluationContext>, R> {
        private final Expression expression;
        private final EvaluationContext evaluationContext;

        private StandardSpELGetter(String expression, EvaluationContext evaluationContext) {
            if (StrUtil.isNotEmpty(expression) && expression.startsWith(parserContext.getExpressionPrefix())) {
                this.expression = expressionParser.parseExpression(expression, parserContext);
            } else {
                this.expression = expressionParser.parseExpression(expression);
            }
            this.evaluationContext = Objects.requireNonNull(evaluationContext);
            if (this.evaluationContext instanceof StandardEvaluationContext standardEvaluationContext) {
                standardEvaluationContext.setBeanResolver(beanResolver);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public R apply(T data, Visitor<EvaluationContext> visitor) {
            Optional.ofNullable(visitor).ifPresent(v -> v.visit(evaluationContext));
            return (R) expression.getValue(evaluationContext, data);
        }

        public R apply(T data) {
            return apply(data, Visitor.defaultVisitor());
        }
    }

    public class StandardSpELSetter<T, R> implements BiConsumer<T, Collection<R>> {
        private final String fieldName;
        private final boolean isCollection;
        private final Expression expression;

        private StandardSpELSetter(Field field) {
            this.fieldName = Objects.requireNonNull(field).getName();
            this.expression = expressionParser.parseExpression(fieldName);
            this.isCollection = Collection.class.isAssignableFrom(Objects.requireNonNull(field).getType());
        }

        @Override
        public void accept(T data, Collection<R> result) {
            if (isCollection) {
                this.expression.setValue(data, result);
            } else {
                int size = result.size();
                if (size == 1) {
                    this.expression.setValue(data, result.stream().findFirst().get());
                } else {
                    log.error("write join result to {} error: Too many results, field is {}, data is {}", data, fieldName, result);
                }
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据仓库与数据挖掘实验练习3-4(实验二2024.5.8)

练习3 1.简单文件操作练习 import pandas as pd # 读取文件 pd.read_csv(pokemon.csv) # 读取 CSV 文件的函数调用&#xff0c;它将文件中的数据加载到 DataFrame 中&#xff0c;并指定了 Pokemon 列作为索引列。 pd.read_csv(pokemon.csv,index_colPokemon)#查看类型 type(p…

UE5材质基础(2)——数学节点篇1

UE5材质基础&#xff08;2&#xff09;——数学节点篇1 目录 UE5材质基础&#xff08;2&#xff09;——数学节点篇1 Add节点 Append节点 Abs节点 Subtract节点 Multiply节点 Divide节点 Clamp节点 Time节点 Lerp节点 Add节点 快捷键&#xff1a;A鼠标左键 值相加…

智慧安监中的物联网主机E6000

物联网主机E6000的研发背景主要源于我国对物联网技术在安全生产、环境监测、火灾预警与防控、人员定位与紧急救援等领域的迫切需求。近年来&#xff0c;随着物联网技术的飞速发展&#xff0c;我国政府对智慧安监的重视程度不断提升&#xff0c;相关的政策扶持力度也在加大。在这…

Ansible--Templates 模块 Tags模块 Roles模块

一 Templates 模块 ①Jinja是基于Python的模板引擎。Template类是Jinja的一个重要组件&#xff0c;可看作一个编译过的模 板文件&#xff0c;用来产生目标文本&#xff0c;传递Python的变量给模板去替换模板中的标记。 ②在配置文件中&#xff0c;会有一些数据&#xff08;如…

CCF-Csp算法能力认证, 202212-1现值计算(C++)含解析

前言 推荐书目&#xff0c;在这里推荐那一本《算法笔记》&#xff08;胡明&#xff09;&#xff0c;需要PDF的话&#xff0c;链接如下 「链接&#xff1a;https://pan.xunlei.com/s/VNvz4BUFYqnx8kJ4BI4v1ywPA1?pwd6vdq# 提取码&#xff1a;6vdq”复制这段内容后打开手机迅雷…

前端双语实现方案(VUE版)

一、封装一个lib包 结构如下 en.js use strict;exports.__esModule true; exports.default {sp: {input: {amountError: Incorrect amount format},table: {total: Total:,selected: Selected:,tableNoData: No data,tableNoDataSubtext: Tip: Suggest to recheck your fil…

LeetCode 110. 平衡二叉树

LeetCode 110. 平衡二叉树 1、题目 题目链接&#xff1a;110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2…

AI写作助手:推荐顶级论文写作工具

ChatGPT生成内容需要注意的问题 永远不要直接提交未经修改的AI文本使用工具如Quillbot、Versabot(支持中文论文生成和润色)、Paraphrasing Tool和Jasper来改变文本的措辞亲自修改短语、句子和文本的其他元素提示ChatGPT重新写自己的文本&#xff0c;并通过多个草稿进行修订 Ch…

如何把多个文件(夹)向上移动1层(或多层)(在批量复制前或后进行)

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 假定情况是&#xff0c;我要把下图里的4个文件夹内部的全部文件&#xff0c;合并到04的当前位置来&#xff08;4个文件夹里面各有5个兔兔的图片&#xff09…

【吃透Java手写】2-Spring(下)-AOP-事务及传播原理

【吃透Java手写】Spring&#xff08;下&#xff09;AOP-事务及传播原理 6 AOP模拟实现6.1 AOP工作流程6.2 定义dao接口与实现类6.3 初始化后逻辑6.4 原生Spring的方法6.4.1 实现类6.4.2 定义通知类&#xff0c;定义切入点表达式、配置切面6.4.3 在配置类中进行Spring注解包扫描…

华为ensp中BFD和OSPF联动(原理及配置命令)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 BFD通常指的是双向转发检测。BFD是一个旨在快速检测通信链路故障的网络协议&#xff0c;提供了低开销、短延迟的链路故障检测机制。它主要用于监测两个…

start.spring.io不支持java8,idea使用阿里云又报错

做项目的时候&#xff0c;我们可以发现&#xff0c;访问https://start.spring.io/ 创建脚手架项目的时候&#xff0c;最低是java 17了 但是对于很多项目来说&#xff0c;还是在用java8&#xff0c;这该怎么办呢&#xff1f; 值得庆幸的是&#xff0c;阿里云也同样有相同功能的…

VASP_AIMD+VASPKIT计算含温力学性质

材料的力学性质一直是DFT计算的重要方向&#xff0c;笔者在以往已有针对于静态结构的力学性质诸如弹性常数的相关计算&#xff0c;同时可通过VASPKIT借助相关方程导出力学性能。 bashvaspvaspkit能量应变计算弹性常数 vaspkit计算弹性常数的对称性指定 vaspkit计算弹性常数脚…

visual studio 2017重命名解决方案或项目名称

1.解决方案->右键->重命名->新的名字 2.项目->右键->重命名->新的名字 3.修改程序集和命名空间名称 项目->右键->属性->修改程序集名称和命名空间名称 4.搜索换名 Ctrl-F->输入旧名称->搜索->将所有旧名称改为新名称&#xff08;注意是整…

C++向函数传递对象

C语言中&#xff0c;对象作为函数的参数和返回值的传递方式有 3 种&#xff1a;值传递、指针传递和引用传递。 1. 对象作为函数参数 把实参对象的值复制给形参对象&#xff0c;这种传递是单向的&#xff0c;只从实参到形参。因此&#xff0c;函数对形参值做的改变不会影响到实…

使用Docker安装Whistle Web Debugging Proxy

大家好&#xff0c;继续给大家分享如何使用docker来安装Whistle Web Debugging Proxy&#xff0c;关于Whistle Web Debugging Proxy的介绍和使用&#xff0c;大家可以参考下面文章&#xff0c;希望本文能够给大家的工作带来一定帮助。 Whistle Web Debugging Proxy介绍及使用 …

《二十一》QT QML编程基础

QML概述 QML&#xff08;Qt Meta-Object Language&#xff09;是一种声明性语言&#xff0c;它被用于描述Qt框架中用户界面的结构和行为。QML提供了一种简洁、灵活的方式来创建动态和交互式的界面。 QML基于JavaScript语法&#xff0c;通过使用QML类型和属性来定义界面的元素…

Codeforces Round 941 (Div. 2)(A,B,C,D,E)

比赛链接 这场难度不高&#xff0c;基本没考算法&#xff0c;全是思维题。B是推结论&#xff0c;C是博弈&#xff0c;D是构造&#xff0c;需要对二进制有一定理解&#xff0c;E是思维题&#xff0c;2300分的暴力和模拟。 A. Card Exchange 题意&#xff1a; 您有 n n n 张牌…

【思科战报】2024.5月最新CCNP考试战报

【福利】思科CCNP考试介绍&#xff08;附CCNP题库下载&#xff09;-CSDN博客思科 CCNP&#xff08;企业基础架构&#xff09;&#xff0c;需考 2 门https://blog.csdn.net/XMWS_IT/article/details/138609138?spm1001.2014.3001.5501【福利】思科CCNP考试介绍&#xff08;附CC…

CSS-盒子模型

盒子模型的重要组成部分 内容区域content&#xff1a;width , height 内边距&#xff1a;内边框和内容区域的距离Padding边框线&#xff1a;Border外边距&#xff1a;Margin Border (边框线) 属性&#xff1a;Border 属性值&#xff1a;边框线粗细px 线条样式 颜色(不区分…