《手写Spring渐进式源码实践》实践笔记(第十七章 数据类型转换)

news2024/11/13 4:46:06

文章目录

  • 第十七章 数据类型转换工厂设计实现
    • 背景
      • 技术背景
        • Spring数据转换实现方式
        • 类型转换器(Converter)接口设计实现
      • 业务背景
    • 目标
    • 设计
    • 实现
      • 代码结构
      • 类图
      • 实现步骤
    • 测试
      • 事先准备
      • 属性配置文件
      • 转换器工厂Bean
      • 测试用例
      • 测试结果:
    • 总结


第十七章 数据类型转换工厂设计实现

背景

技术背景

Spring数据转换实现方式

Spring提供了多种机制来实现数据转换,以下是一些主要的方式:

  1. 类型转换器(Converter)接口: Spring提供了Converter<S, T>接口,允许开发者自定义转换逻辑。通过实现这个接口,你可以定义如何将源类型S转换为目标类型T。例如:

    @Component
    public class StringToIntegerConverter implements Converter<String, Integer> {
        @Override
        public Integer convert(String source) {
            return Integer.valueOf(source);
        }
    }
    

    然后,你可以在配置中注册这个转换器,Spring会自动在需要的时候使用它。

  2. 属性编辑器(PropertyEditor)PropertyEditor是Java的一个老机制,Spring也支持它。通过实现PropertyEditor接口,你可以定义如何将字符串转换为其他类型。不过,从Spring 5开始,推荐使用Converter接口,因为PropertyEditor将在未来版本中被弃用。

  3. 格式化程序(Formatter)Formatter是Java的一个接口,用于格式化和解析对象。Spring通过FormattingConversionService支持Formatter,可以在Spring MVC中使用它来格式化和解析数据。

  4. 自动应用的转换器(Global Converters): 在Spring MVC中,可以通过实现WebMvcConfigurer接口的addFormatters方法来添加全局的转换器,这些转换器会对所有的控制器方法参数和返回值进行转换。

    java

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIntegerConverter());
    }
    
  5. 局部转换器(Local Converters): 除了全局转换器,你还可以在方法级别使用@InitBinder注解来添加局部转换器,这些转换器只对特定的控制器或请求参数有效。

  6. Spring Expression Language(SpEL): SpEL也支持类型转换,当你在表达式中使用不同类型的值时,SpEL会尝试进行适当的类型转换。

  7. Java标准转换: Spring还会利用Java的自动装箱和拆箱特性,以及Number类的intValuelongValue等方法来进行基本的类型转换。

  8. 自定义转换服务: 你可以通过编程方式创建ConversionService的实例,并注册自定义的转换器,然后在你的应用程序中使用这个服务来进行类型转换。

类型转换器(Converter)接口设计实现

Spring 中的类型转换器(Converter)接口的底层生效思路主要涉及以下几个方面:

  1. 接口定义Converter<S, T> 是一个函数式接口,用于将类型 S 的对象转换为类型 T 的对象。这个接口定义了一个 convert 方法,该方法接受一个类型为 S 的源对象,并返回一个类型为 T 的目标对象。

  2. 转换器注册: 转换器可以通过实现 Converter 接口并注册到 ConversionService 中来生效。ConversionService 是 Spring 中的一个核心接口,负责管理所有的转换器,并根据需要调用适当的转换器来执行类型转换。

  3. 条件转换器ConditionalConverter 接口允许在转换过程中添加条件判断,以决定是否使用特定的转换器。这提供了更细粒度的控制,允许在运行时根据具体情况选择是否应用某个转换器。

  4. 转换器工厂ConverterFactory<S, R> 接口用于创建一系列相关转换器,它允许从类型 S 转换到类型 R 的子类型。这种设计模式支持了范围转换,即从一种类型到多种可能的子类型的转换。

  5. 泛型转换器GenericConverter 接口提供了在两个或多个类型之间进行转换的能力,它是最灵活但也是最为复杂的转换器SPI接口。GenericConverter 可以处理更复杂的转换场景,比如集合到集合的转换。

  6. 默认转换服务DefaultConversionServiceGenericConversionService 的一个实现,它提供了一个默认的转换服务,包含了许多内置的转换器,覆盖了大多数常用的类型转换场景。如果需要自定义转换逻辑,可以向 DefaultConversionService 添加自定义的转换器。

  7. 转换器查找和缓存: Spring 会根据源类型和目标类型查找合适的转换器。如果找到了匹配的转换器,Spring 会将其缓存起来以提高性能。如果缓存中没有找到匹配的转换器,Spring 会尝试查找并添加新的转换器,然后再次尝试转换。

  8. 转换器的链式调用Converter 接口中的 andThen 方法支持转换器的链式调用,允许将多个转换步骤串联起来,先应用一个转换器,然后将结果传递给下一个转换器。

业务背景

  1. 其实实现到本章节,关于IOC、AOP在日常使用和面试中高频出现的技术点都该涵盖了。那么为了补全整个框架内容的结构,方便大家后续在阅读 Spring 时不至于对类型转换的知识体系陌生,这里添加数据类型转换中关于类型转换器的实现。
  2. 类型转换也叫做数据转换,比如从String到Integer、从String到Date、从Double到Long等等,但这些操作不能在已经使用框架的情况下还需要手动处理,所以我们要把这样的功能扩展到Spring框架中。

目标

基于当前实现的 Spring 框架,扩展类型转换功能,并熟悉spring组件服务的开发思路。

设计

如果我们来把只是看上去一个简单的类型转换操作抽象成框架,那么它就需要一个标准的接口,谁实现这个接口就具备类型转换的具体实现,提供类型转换的能力。

那么在有了这样接口后,还需要类型转换服务的注册、工厂等内容,才可以把类型转换抽象成一个组件服务。整体设计结构如下图:

image-20241107163620150
  • 首先从工厂出发我们需要实现一个 ConversionServiceFactoryBean 来对类型转换服务进行操作。

  • 而实现类型转换的服务,需要定义 Converter 转换类型、ConverterRegistry 注册类型转换功能,另外转换类型的操作较多,所以这里也会需要定义一个类型转换工厂 ConverterFactory 各个具体的转换操作来实现这个工厂接口。

实现

代码结构

image-20241107163702901

源码实现:https://github.com/swg209/spring-study/tree/main/step17-type-converter-factory

类图

image-20241107163812431

实现步骤

  1. 类型转换接口

    a. Converter 转换器,提供类型转换方法。

    public interface Converter<S, T> {
    
        /**
         * 将source从 S类型转换为T类型.
         */
        T convert(S source);
    }
    
    

    b. ConverterFactory 转换工厂,用于获取转换器。

    public interface ConverterFactory<S, R> {
    
        /**
         * 获取转换器,将source从S类型转换为T类型,其中T也是R的实例。
         *
         * @param targetType
         * @param <T>
         * @return
         */
    
        <T extends R> Converter<S, T> getConverter(Class<T> targetType);
    
    }
    

    c. ConverterRegistry 转换注册中心,用于服务注册转换器、转换工厂。

    public interface ConverterRegistry {
    
    
        void addConverter(Converter<?, ?> converter);
    
        void addConverter(GenericConverter converter);
    
        void addConverterFactory(ConverterFactory<?, ?> converterFactory);
    
    
    }
    
    

    d. ConverterService 转换服务,提供 判断类型能否转换以及转换方法。

    public interface ConversionService {
    
        /**
         * 是否可以转换.
         *
         * @param sourceType
         * @param targetType
         * @return
         */
        boolean canConvert(Class<?> sourceType, Class<?> targetType);
    
        /**
         * 转换.
         *
         * @param source
         * @param targetType
         * @param <T>
         * @return
         */
        <T> T convert(Object source, Class<T> targetType);
    }
    

    e. GenericConverter 通用转换器,门面接口,并有一个内部类 ConvertiblePair,包装 源类型 与 目标类型。

    public interface GenericConverter {
    
        /**
         * Return the source and target types that this converter can convert between.
         * 返回此转换器可以在其之间转换的源和目标类型.
         */
        Set<ConvertiblePair> getConvertibleTypes();
    
        /**
         * Convert the source object to the targetType described by the {@code TypeDescriptor}.
         * 将源对象转换为由{@code TypeDescriptor}描述的targetType.
         *
         * @param source
         * @param sourceType
         * @param targetType
         * @return
         */
        Object convert(Object source, Class sourceType, Class targetType);
    
    
        /**
         * Holder for a source-to-target class pair.
         * 源类型到目标类型的持有者.
         */
        final class ConvertiblePair {
    
            private final Class<?> sourceType;
    
            private final Class<?> targetType;
    
    
            public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
                Assert.notNull(sourceType, "source type must not be null");
                Assert.notNull(targetType, "target type must not be null");
                this.sourceType = sourceType;
                this.targetType = targetType;
            }
    
            public Class<?> getSourceType() {
                return this.sourceType;
            }
    
            public Class<?> getTargetType() {
                return this.targetType;
            }
    
            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null || obj.getClass() != ConvertiblePair.class) {
                    return false;
                }
                ConvertiblePair other = (ConvertiblePair) obj;
                return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);
            }
    
            @Override
            public int hashCode() {
                return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
            }
        }
    }
    
    
  2. 类型转换服务

a. GenericConversionService , 实现 ConversionService ,ConverterRegistry, 并添加 根据 转换器或工厂 生成对应类型的 ConvertiblePair 以及 遍历源和目标一系列超类的所有组合,获取转换器 的方法辅助,设有 内部类 ConverterFactoryAdapter ,ConverterAdapter 实现 GenericConverter,用于 适配 通用转换器,方便注册及调用。

public class GenericConversionService implements ConversionService, ConverterRegistry {

    // 转换器映射表.
    private Map<ConvertiblePair, GenericConverter> converters = new HashMap<>();


    @Override
    public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
        GenericConverter converter = getConvert(sourceType, targetType);
        return converter != null;
    }


    @Override
    public <T> T convert(Object source, Class<T> targetType) {
        Class<?> sourceType = source.getClass();
        GenericConverter converter = getConvert(sourceType, targetType);
        return (T) converter.convert(source, sourceType, targetType);
    }

    @Override
    public void addConverter(Converter<?, ?> converter) {
        GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter);
        ConverterAdapter converterAdapter = new ConverterAdapter(typeInfo, converter);
        for (GenericConverter.ConvertiblePair convertibleType : converterAdapter.getConvertibleTypes()) {
            converters.put(convertibleType, converterAdapter);
        }
    }

    @Override
    public void addConverter(GenericConverter converter) {
        // 遍历转换器支持的类型,将其添加到转换器映射表中.
        for (GenericConverter.ConvertiblePair convertibleType : converter.getConvertibleTypes()) {
            converters.put(convertibleType, converter);
        }
    }

    @Override
    public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
        ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory);
        ConverterFactoryAdapter converterFactoryAdapter = new ConverterFactoryAdapter(typeInfo, converterFactory);
        for (ConvertiblePair convertibleType : converterFactoryAdapter.getConvertibleTypes()) {
            converters.put(convertibleType, converterFactoryAdapter);
        }
    }


    private GenericConverter getConvert(Class<?> sourceType, Class<?> targetType) {
        List<Class<?>> sourceCandidates = getClassHierarchy(sourceType);
        List<Class<?>> targetCandidates = getClassHierarchy(targetType);
        for (Class<?> sourceCandidate : sourceCandidates) {
            for (Class<?> targetCandidate : targetCandidates) {
                ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
                GenericConverter converter = converters.get(convertiblePair);
                if (converter != null) {
                    return converter;
                }
            }
        }
        return null;
    }

    private List<Class<?>> getClassHierarchy(Class<?> clazz) {
        List<Class<?>> hierarchy = new ArrayList<>();
        while (clazz != null) {
            hierarchy.add(clazz);
            clazz = clazz.getSuperclass();
        }
        return hierarchy;
    }


    //因为要同时兼容 Converter、ConverterFactory,所以这里传参为Object.
    private ConvertiblePair getRequiredTypeInfo(Object object) {
        Type[] types = object.getClass().getGenericInterfaces();
        ParameterizedType parameterizedType = (ParameterizedType) types[0];
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        Class sourceType = (Class) actualTypeArguments[0];
        Class targetType = (Class) actualTypeArguments[1];
        return new ConvertiblePair(sourceType, targetType);
    }


    private final class ConverterAdapter implements GenericConverter {
        private final ConvertiblePair typeInfo;
        private final Converter<Object, Object> converter;

        public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {
            this.typeInfo = typeInfo;
            this.converter = (Converter<Object, Object>) converter;
        }

        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(typeInfo);
        }

        @Override
        public Object convert(Object source, Class sourceType, Class targetType) {
            return converter.convert(source);
        }
    }


    private final class ConverterFactoryAdapter implements GenericConverter {
        private final ConvertiblePair typeInfo;
        private final ConverterFactory<Object, Object> converterFactory;

        public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {
            this.typeInfo = typeInfo;
            this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;
        }

        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(typeInfo);
        }

        @Override
        public Object convert(Object source, Class sourceType, Class targetType) {
            return converterFactory.getConverter(targetType).convert(source);
        }
    }
}


b. DefaultConversionService ,继承 GenericConversionService ,通过构造函数注册 内置的 类型转换器或工厂, 并对外作为一个 注册中心使用。

public class DefaultConversionService extends GenericConversionService {

    public DefaultConversionService() {
        addDefaultConverters(this);
    }

    public static void addDefaultConverters(ConverterRegistry converterRegistry) {
        // 添加各类类型转换工厂.
        converterRegistry.addConverterFactory(new StringToNumberConverterFactory());

    }
}

  1. 融入 Bean 的生命周期
    a. 类型转换工厂 ConversionServiceFactoryBean,实现 FactoryBean, InitializingBean,这样 在其初始化时就可以 注册类型转换器、工厂。

    public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
    
        @Nullable
        private Set<?> converters;
    
        @Nullable
        private GenericConversionService conversionService;
    
        @Override
        public ConversionService getObject() throws Exception {
            return conversionService;
        }
    
        @Override
        public Class<?> getObjectType() {
            return conversionService.getClass();
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    
        @Override
        public void afterPropertiesSet() throws BeansException {
            this.conversionService = new DefaultConversionService();
            registerConverters(converters, conversionService);
    
        }
    
        private void registerConverters(Set<?> converters, ConverterRegistry registry) {
            if (converters != null) {
                for (Object converter : converters) {
                    if (converter instanceof GenericConverter) {
                        registry.addConverter((GenericConverter) converter);
                    } else if (converter instanceof Converter<?, ?>) {
                        registry.addConverter((Converter<?, ?>) converter);
                    } else if (converter instanceof ConverterFactory<?, ?>) {
                        registry.addConverterFactory((ConverterFactory<?, ?>) converter);
                    } else {
                        throw new IllegalArgumentException("Each converter object must implement one of the "
                                + "Converter, ConverterFactory, or GenericConverter interfaces"
                                + "Unsupported converter type: " + converter.getClass());
                    }
                }
            }
        }
    
    
        public void setConverters(Set<?> converters) {
            this.converters = converters;
        }
    }
    
    

b. 通过 ConfigurableBeanFactory 添加 设置、获取类型转换服务方法,由 AbstractBeanFactory 实现,使得 在刷新容器 实例化单例对象前,可以优先获取到 ConversionServiceFactoryBean ,并设置 beanfactory 的 类型转换服务;并在 AbstractAutowireCapableBeanFactory 的 createBean 属性填充前 增加 类型转换的操作。

测试

事先准备

多类型属性的类 Husband

Husband

public class Husband {

    private String wifeName;

    private LocalDate marriageDate;

    public String getWifeName() {
        return wifeName;
    }

    public void setWifeName(String wifeName) {
        this.wifeName = wifeName;
    }

    public LocalDate getMarriageDate() {
        return marriageDate;
    }

    public void setMarriageDate(LocalDate marriageDate) {
        this.marriageDate = marriageDate;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "wifeName='" + wifeName + '\'' +
                ", marriageDate=" + marriageDate +
                '}';
    }
}

属性配置文件

spring.xml

  1. 配置husband,类型转换服务工厂Bean, 类型转换服务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="husband" class="cn.suwg.springframework.test.bean.Husband">
        <property name="wifeName" value="老老"/>
        <property name="marriageDate" value="2022-04-14"/>
    </bean>

    <bean id="conversionService" class="cn.suwg.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters" ref="converters"/>
    </bean>

    <bean id="converters" class="cn.suwg.springframework.test.converter.ConvertersFactoryBean"/>
</beans>

转换器工厂Bean

ConvertersFactoryBean

public class ConvertersFactoryBean implements FactoryBean<Set<?>> {
    @Override
    public Set<?> getObject() throws Exception {
        HashSet<Object> converters = new HashSet<>();
        StringToLocalDateConverter stringToLocalDateConverter = new StringToLocalDateConverter("yyyy-MM-dd");
        converters.add(stringToLocalDateConverter);
        return converters;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

StringToIntegerConverter

public class StringToIntegerConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

StringToLocalDateConverter

public class StringToLocalDateConverter implements Converter<String, LocalDate> {

    private final DateTimeFormatter DATE_TIME_FORMATTER;

    public StringToLocalDateConverter(String pattern) {
        DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern);
    }


    @Override
    public LocalDate convert(String source) {
        return LocalDate.parse(source, DATE_TIME_FORMATTER);
    }
}

测试用例

public class ApiTest {


    @Test
    public void testConvert() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        System.out.println("测试结果: " + husband);
    }


    //返回转换好的日期. 年-月-日
    @Test
    public void testStringToLocalDateConverter() {
        StringToLocalDateConverter converter = new StringToLocalDateConverter("yyyy-MM-dd hh:mm:ss");
        LocalDate convert = converter.convert("2024-11-07 10:00:11");
        LocalDate expect = LocalDate.of(2024, 11, 7);
        Assert.assertEquals(expect, convert);
        System.out.println(convert);
    }


    // 测试字符串转换为整数.
    @Test
    public void testStringToIntegerConverter() {
        StringToIntegerConverter converter = new StringToIntegerConverter();
        Integer convert = converter.convert("12345");
        Assert.assertEquals(Integer.valueOf(12345), convert);
        System.out.println(convert);
    }
}

测试结果:

image-20241107170138185

  • 从测试结果可以看到,字符串转日期转换器功能正常。

image-20241107170120413

  • 从测试结果可以看到,字符串转数字转换器功能正常。

image-20241107172248023

  • 从测试结果可以看到,对象属性值可以正常输出到日志。这个测试内容还是比较简单的,可以自行验证结果,虽然最终的结果看上去比较简单,但整个框架结构实现设计还是蛮复杂的,把这么一个转换操作抽象为接口适配、工厂模型等方式,还是很值得借鉴的。

总结

  • 本章节实现的类型转换操作如果只是功能性的开发,就像你自己承接的需求那样,可能只是简单的if判断就搞定了,但放在一个成熟的框架要中要考虑的是可复用性、可扩展性,所以会看到接口的定义、工厂的使用等等设计模式在这里体现。
  • 恭喜你,能够坚持学习到这一章节。如果你在学习过程中,每一章节都是通过阅读文章、编写代码、调试错误以及理解设计来完成的,那么一定会在这个过程中获得很多宝贵的经验和知识。以后再次阅读Spring源码时,就不会觉得那么困难了。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring

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

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

相关文章

使用docker形式部署jumpserver

文章目录 前言一、背景二、使用步骤1.基础环境准备2.拉取镜像3.进行部署4.备份记录启动命令 前言 记录一下使用docker形式部署jumpserver服务的 一、背景 搭建一个jumpserver的堡垒机&#xff0c;但是发现之前是二进制文件部署的&#xff0c;会在物理机上部署污染环境&#x…

(62)使用RLS自适应滤波器进行系统辨识的MATLAB仿真

文章目录 前言一、基本概念二、RLS算法原理三、RLS算法的典型应用场景四、MATLAB仿真代码五、仿真结果1.滤波器的输入信号、参考信号、输出信号、误差信号2.对未知系统进行辨识得到的系数 总结与后续 前言 RLS&#xff08;递归最小二乘&#xff09;自适应滤波器是一种用于系统…

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa;…

MySQL数据库的备份与还原

目录 mysql 数据库的备份 生成SQL脚本 1 在控制台使用mysqldump命令可以用来生成指定数据库的脚本 ​编辑2 在数据库图形化界面工具&#xff1a;DateGrip 中操作&#xff1a;导出 mysql 数据库的还原 执行SQL脚本 1 在控制台使用 命令&#xff0c;指定将sql脚本导入到指定…

使用 IDEA 创建 Java 项目(二)

IDEA 创建 Java 项目 一般创建 Java 项目可以创建一个空项目&#xff0c;然后在空项目中添加模块&#xff0c;在模块中编写包&#xff0c;包中包含 Java 类。 如果你的 JDK 没有被 IDEA 自动找到的话&#xff0c;可以手动选择 JDK。我们先来学习 Intellij 构建系统下的 Java …

图论算法:最短路径算法详解【c语言版】(无权最短路径、Dijkstra算法)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 图论算法&#xff1a;最短路径算法详解 在图论中&#xff0c;最短路径问题是寻找图中两点之间具有最小总权重的路径。这个问题在许多实际应用中都有重要的作用&#xff0c;比如网络路由、城市交通规…

vue通过iframe方式嵌套grafana图表

文章目录 前言一、iframe方式实现xxx.xxx.com拒绝连接登录不跳转Cookie 的SameSite问题解决不显示额外区域(kiosk1) 前言 我们的前端是vue实现的&#xff0c;监控图表是在grafana中的&#xff0c;需要在项目web页面直接显示grafana图表 一、iframe方式实现 xxx.xxx.com拒绝连…

苹果系统安装Homebrew时CLT缺失的问题

前言 为了使用brew命令&#xff0c;必须安装Homebrew工具。但是在Howebrew安装的时候&#xff0c;会出现CLT&#xff08;Command Line Tools&#xff09;缺失的问题。本博客就是讨论如何来解决这个问题的。 1、问题的出现 2、解决途径 在命令行终端中输入命令&#xff1a;xcod…

LeetCode Hot100 49.字母异位词分组

题干&#xff1a; 思路&#xff1a; 输入的是一个字符串数组&#xff0c;输出是一个列表&#xff0c;首先我们需要通过遍历数组获得每一个字符串&#xff0c;我们想要判断获得的任意两个字符串是不是字母异位词&#xff0c;所以可以将获得的字符串排序&#xff08;转换为字符数…

小白初入Android_studio所遇到的坑以及怎么解决

1. 安装Android_studio 参考&#xff1a;Android Studio 安装配置教程 - Windows(详细版)-CSDN博客 Android Studio超级详细讲解下载、安装配置教程&#xff08;建议收藏&#xff09;_androidstudio-CSDN博客 想下旧版本的android_studio的地址&#xff08;仅供参考&#xf…

游戏引擎中LOD渲染技术

一.LOD(Level Of Detail) 为了降低GPU渲染压力,根据摄像机距离模型距离将面数较高的模型替换为面数较低的模型. LOD LOD0(distance<10) LOD1(distance<20) LOD2(distance<30) 故通常引擎中MetaMesh是由一个或多个LOD模型构成. MetaMesh mesh mesh.lod1 mesh.lod…

论文阅读《Structure-from-Motion Revisited》

摘要 增量式地运动结构恢复是从无序图像集合中进行三维重建的一个普遍策略。虽然增量式地重建系统在各个方面上都取得了巨大的进步&#xff0c;但鲁棒性、准确性、完整度和尺度仍然是构建真正通用管道的关键问题。我们提出了一种新的运动结构恢复技术&#xff0c;它改进了目前…

【人工智能训练师】3 集群搭建

开启集群环境 本次环境为单节点伪集群环境&#xff0c;环境中已经安装JDK1.8、Hadoop2.7.7、Mysql5.7、hive2.3.4。— 1.环境中已经安装/root/software/hadoop-2.7.7&#xff0c;格式化HDFS&#xff0c;开启集群&#xff0c;查看集群状态。&#xff08;HDFS端口为9000&#x…

使用 GPT-4V 全面评估泛化情绪识别 (GER)

概述 由于情绪在人机交互中扮演着重要角色&#xff0c;因此情绪识别备受研究人员关注。目前的情感识别研究主要集中在两个方面&#xff1a;一是识别刺激物引起的情感&#xff0c;并预测观众观看这些刺激物后的感受。另一个方面是分析图像和视频中的人类情绪。在本文中&#xf…

8.机器学习--决策树

(⊙﹏⊙)下周有要开组会&#xff0c;不知道该说啥&#xff0c;啊啊啊啊&#x1f62b; 目录 1.基本概念 2.ID3算法 3.C4.5算法 4.CART算法 5.连续与缺失值处理 5.1.连续值处理 5.2.缺失值处理 6.剪枝处理 6.1.预剪枝策略 6.2.后剪枝策略 7.实例代码 1.基本概念 提…

uniApp之uni-file-picker使用踩坑

标题党~也不算坑吧 就是初体验 上传是需要存储一下子的&#xff0c;我以为uniApp是自己免费开的服务给大家中转使用&#xff0c;就没管这个事&#xff0c;但是官网是这么说的&#xff1a; 就我是怎么发现的&#xff0c;使用了一段时间后&#xff0c;上传的图片都裂了&#xff…

22.04Ubuntu---ROS2使用rclcpp编写节点C++

节点需要存在于功能包当中&#xff0c;功能包需要存在于工作空间当中。 所以我们要想创建节点&#xff0c;就要先创建一个工作空间&#xff0c;再创建功能包。 第一步&#xff1a;创建工作空间 mkdir -p chapt2_ws/src/ 第二步&#xff1a;创建example_cpp功能包&#xff0c…

UIStackView使用进阶

01 技术背景 前端的布局方式比较灵活&#xff0c;提供有Flex的布局方式&#xff0c;可以实现不同方向的弹性布局。Flex就像一个容器&#xff0c;可以将其内部的子控件统一进行布局。其包含主轴方向和交叉轴方向&#xff0c;主轴方向表示控件的排布方向&#xff0c;交叉轴方向和…

AMD-OLMo:在 AMD Instinct MI250 GPU 上训练的新一代大型语言模型。

AMD-OLMo是一系列10亿参数语言模型&#xff0c;由AMD公司在AMD Instinct MI250 GPU上进行训练&#xff0c;AMD Instinct MI250 GPU是一个功能强大的图形处理器集群&#xff0c;它利用了OLMo这一公司开发的尖端语言模型。AMD 创建 OLMo 是为了突出其 Instinct GPU 在运行 “具有…

【HarmonyOS】Install Failed: error: failed to install bundle.code:9568289

【HarmonyOS】Install Failed: error: failed to install bundle.code:9568289 一、问题背景&#xff1a; Install Failed: error: failed to install bundle. code:9568289 error: install failed due to grant request permissions failed. View detailed instructions. 11/…