第五十三章 Spring之假如让你来写Boot——环境篇

news2025/4/2 8:01:56

Spring源码阅读目录

第一部分——IOC篇

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇


文章目录

  • Spring源码阅读目录
    • 第一部分——IOC篇
    • 第二部分——AOP篇
    • 第三部分——事务篇
    • 第四部分——MVC篇
    • 第五部分——Boot篇
  • 前言
  • 尝试动手写IOC容器
      • 第四十四版 环境
        • 属性源
        • 多个属性文件
        • 操作属性
        • 环境配置文件
        • 环境
        • 测试
  • 总结


前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


     书接上回,在上篇 第五十二章 Spring之再进一步——Boot 中,A君 已经对 Boot 进行了相关调研,并对相关实现进行了一番分析。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大 要求 A君在一周内开发个简单的 IOC容器

    前情提要:A君 已经对 Boot 进行了相关调研,并对相关实现进行了一番分析 。。。

第四十四版 环境

    A君 今天刚到公司就迫不及待的去找 老大 了,将昨天的调研成果一五一十的说出来。。。

    老大 静静地听 A君 说完后,开口道:“不错,大概的思路确实是这么回事。不过呢,我昨天稍微看了下,目前你弄的 IOC容器 还不具备进入 Boot 的条件,需要进行一些补充。”

    “啊?是有什么缺陷吗?” A君 惊讶道

    “也不算吧,只是功能有些不足。” 老大 说道

    “那今天需要做什么呢?” A君 追问道

    “今天,你就先把 环境 搞定吧!”

    “环境?啥意思,没明白。” A君 疑惑道

    “比如说:你的jar程序运行在操作系统上,这时候就会有系统的相关配置,如果是运行在Web环境上,则会有Web相关的配置。总的来说,就是用来获取不同环境的配置、信息的。” 老大 缓缓说道

    “好吧,那我明白了。”

    “那去吧,做做看。”

属性源

    从 老大 办公室出来后,A君 回到了自己的工位上,开始琢磨如何实现所谓的 环境。听 老大 的意思,环境 有包含一堆的配置,比如说:环境变量、系统属性、命令行参数、配置文件一堆东西,这些东西基本都是以key-value的形式进行存储的,只是来源不同,这部分内容可以单独抽取出来。A君 先定义个PropertySource类,用以区分不同的来源。代码如下:

/**
 * 配置源
 *
 * @param <T>
 */
public abstract class PropertySource<T> {

    protected final String name;

    protected final T source;

    public abstract Object getProperty(String name);
    
    //省略其他代码。。。

    /**
     * 有些时候,配置无法直接初始化,需要延迟初始化,这时候需要存根
     * 例如:Web环境下需要等ServletContext初始化后才能有值
     */
    public static class StubPropertySource extends PropertySource<Object> {

        @Override
        public String getProperty(String name) {
            return null;
        }
    }

    /**
     * 集合比较
     */
    static class ComparisonPropertySource extends StubPropertySource {

        @Override
        public String getProperty(String name) {
            throw new UnsupportedOperationException(USAGE_ERROR);
        }
    }
}

这些属性一般都是集合,可以遍历的,所以可以在提取一个抽象类。A君 定义EnumerablePropertySource类,代码如下:

/**
 * 可以遍历的属性
 *
 * @param <T>
 */
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {

    public abstract String[] getPropertyNames();

}

除却可遍历之外,这些属性一般也都是key-value结构。所以可以再次提取一个抽象类。A君 现在已经快魔怔了,划分的这么细。新增MapPropertySource类,代码如下:

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {

    public MapPropertySource(String name, Map<String, Object> source) {
        super(name, source);
    }

    @Override
    public Object getProperty(String name) {
        return this.source.get(name);
    }

    @Override
    public boolean containsProperty(String name) {
        return this.source.containsKey(name);
    }

    @Override
    public String[] getPropertyNames() {
        return StringUtils.toStringArray(this.source.keySet());
    }

}

剩下的几个具体实现基本都差不多了,A君 就以配置文件为例。新增PropertiesPropertySource类,代码如下:

public class PropertiesPropertySource extends MapPropertySource {

    @Override
    public String[] getPropertyNames() {
        synchronized (this.source) {
            return super.getPropertyNames();
        }
    }
}
多个属性文件

    正常情况下,配置文件可能只有一个,也可能存在多个,不能一概而论。但是多个肯定没错,嘿嘿!于是,A君 再定义了一个PropertySources接口。代码如下:

/**
 * 多个配置文件
 */
public interface PropertySources extends Iterable<PropertySource<?>> {

    /**
     * 流式操作
     *
     * @return
     */
    default Stream<PropertySource<?>> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    /**
     * 是否包含某个配置
     *
     * @param name
     * @return
     */
    boolean contains(String name);

    /**
     * 获取指定配置
     *
     * @param name
     * @return
     */
    PropertySource<?> get(String name);
}

再给其个默认实现,A君 新增MutablePropertySources类,代码如下:

public class MutablePropertySources implements PropertySources {

    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

    @Override
    public Stream<PropertySource<?>> stream() {
        return this.propertySourceList.stream();
    }

    @Override
    public boolean contains(String name) {
        for (PropertySource<?> propertySource : this.propertySourceList) {
            if (propertySource.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public PropertySource<?> get(String name) {
        for (PropertySource<?> propertySource : this.propertySourceList) {
            if (propertySource.getName().equals(name)) {
                return propertySource;
            }
        }
        return null;
    }
	
	//省略其他代码。。。

}

基本上就是一些循环操作,没啥好说的

操作属性

    属性源基本已经定义完成了,现在 A君 把重心放在了操作属性上,毕竟直接给属性源不够友好嘛。操作弄来弄去,无非就是CRUD罢了。操作属性甚至不需要这么多,只需要查找就行了,毕竟这些都是无法修改的。好,思量完毕,A君 新增PropertyResolver接口,代码如下:

/**
 * 属性操作
 */
public interface PropertyResolver {

    /**
     * 配置文件是否包含某个key
     *
     * @param key
     * @return
     */
    boolean containsProperty(String key);

    /**
     * 获取某个值
     *
     * @param key
     * @return
     */
    String getProperty(String key);

    /**
     * 获取某个值,没有则用默认值
     *
     * @param key
     * @param defaultValue
     * @return
     */
    String getProperty(String key, String defaultValue);

    /**
     * 获取值并转出某个类型
     *
     * @param key
     * @param targetType
     * @param <T>
     * @return
     */
    <T> T getProperty(String key, Class<T> targetType);

    <T> T getProperty(String key, Class<T> targetType, T defaultValue);

    /**
     * 获取某个值,必须存在,不存在则抛出异常
     *
     * @param key
     * @return
     * @throws IllegalStateException
     */
    String getRequiredProperty(String key) throws IllegalStateException;

    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    /**
     * 包含占位符
     *
     * @param text
     * @return
     */
    String resolvePlaceholders(String text);

    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}

顶级接口已经定义好了,接着需要考虑下拓展了,毕竟涉及到属性操作的,一般都需要进行类型转换,除了类型转换,还有什么?A君 想到了之前工作中经常使用占位符。例如:


@Component
public class ApiService {

    @Value("${api.url}")
    private String apiUrl;
}

这种情况就会涉及到前后缀的问题。为此,A君 新增ConfigurablePropertyResolver接口,代码如下:

public interface ConfigurablePropertyResolver extends PropertyResolver {

    /**
     * 类型转换
     *
     * @return
     */
    ConfigurableConversionService getConversionService();

    void setConversionService(ConfigurableConversionService conversionService);

    /**
     * 设置占位符前缀
     *
     * @param placeholderPrefix
     */
    void setPlaceholderPrefix(String placeholderPrefix);

    /**
     * 占位符后缀
     *
     * @param placeholderSuffix
     */
    void setPlaceholderSuffix(String placeholderSuffix);

    /**
     * 分隔符
     *
     * @param valueSeparator
     */
    void setValueSeparator(String valueSeparator);

    /**
     * 忽略无法处理的属性
     *
     * @param ignoreUnresolvableNestedPlaceholders
     */
    void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

    void setRequiredProperties(String... requiredProperties);

    void validateRequiredProperties() throws MissingRequiredPropertiesException;

}

抽象类和实现类基本就做些校验,类型转换的事情,没啥东西。简单看下PropertySourcesPropertyResolver就行了,代码如下:

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {

    private final PropertySources propertySources;

    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            for (PropertySource<?> propertySource : this.propertySources) {
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    /**
                     * 字符串,且包含占位符
                     */
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    /**
                     * 进行类型转换
                     */
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        return null;
    }

}


环境配置文件

    在日常开发中,A君 在本机开发完成后,自测完成后部署到测试环境给测试人员进行测试,然后再上版验环境,最后上生产环境。这些个环境不可谓不多,而环境之间配置往往是不同的,像一些公共组件:数据库、NacosMQ等各个环境的地址往往是不同的,不可能说部署一次改一次配置文件,这样太麻烦。正常情况下都会为每个环境提供个配置文件,部署时去激活对应环境的配置文件即可。例:

在这里插入图片描述

既然有此需求,那么框架自然也得支持了。A君 新增Profiles接口。代码如下:

@FunctionalInterface
public interface Profiles {

    static Profiles of(String... profiles) {
        return ProfilesParser.parse(profiles);
    }

    /**
     * 匹配环境文件
     *
     * @param activeProfiles
     * @return
     */
    boolean matches(Predicate<String> activeProfiles);

}

接着就是实现了,为了支持更复杂的业务场景,需要支持简单的逻辑表达式:orandnotequals这些,具体的解析方式 A君 就不细说,在 第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式 中有更为详细的说明。ProfilesParser代码如下:

final class ProfilesParser {

    private ProfilesParser() {
    }


    static Profiles parse(String... expressions) {
        Profiles[] parsed = new Profiles[expressions.length];
        for (int i = 0; i < expressions.length; i++) {
            parsed[i] = parseExpression(expressions[i]);
        }
        return new ParsedProfiles(expressions, parsed);
    }

    private static Profiles parseExpression(String expression) {
        StringTokenizer tokens = new StringTokenizer(expression, "()&|!", true);
        return parseTokens(expression, tokens);
    }

    private static Profiles parseTokens(String expression, StringTokenizer tokens) {
        return parseTokens(expression, tokens, Context.NONE);
    }

    private static Profiles parseTokens(String expression, StringTokenizer tokens, Context context) {
        List<Profiles> elements = new ArrayList<>();
        Operator operator = null;
        while (tokens.hasMoreTokens()) {
            String token = tokens.nextToken().trim();
            if (token.isEmpty()) {
                continue;
            }
            switch (token) {
                case "(":
                    Profiles contents = parseTokens(expression, tokens, Context.BRACKET);
                    if (context == Context.INVERT) {
                        return contents;
                    }
                    elements.add(contents);
                    break;
                case "&":
                    assertWellFormed(expression, operator == null || operator == Operator.AND);
                    operator = Operator.AND;
                    break;
                case "|":
                    assertWellFormed(expression, operator == null || operator == Operator.OR);
                    operator = Operator.OR;
                    break;
                case "!":
                    elements.add(not(parseTokens(expression, tokens, Context.INVERT)));
                    break;
                case ")":
                    Profiles merged = merge(expression, elements, operator);
                    if (context == Context.BRACKET) {
                        return merged;
                    }
                    elements.clear();
                    elements.add(merged);
                    operator = null;
                    break;
                default:
                    Profiles value = equals(token);
                    if (context == Context.INVERT) {
                        return value;
                    }
                    elements.add(value);
            }
        }
        return merge(expression, elements, operator);
    }

    private static Profiles merge(String expression, List<Profiles> elements, Operator operator) {
        assertWellFormed(expression, !elements.isEmpty());
        if (elements.size() == 1) {
            return elements.get(0);
        }
        Profiles[] profiles = elements.toArray(new Profiles[0]);
        return (operator == Operator.AND ? and(profiles) : or(profiles));
    }

    private static Profiles or(Profiles... profiles) {
        return activeProfile -> Arrays.stream(profiles).anyMatch(isMatch(activeProfile));
    }

    private static Profiles and(Profiles... profiles) {
        return activeProfile -> Arrays.stream(profiles).allMatch(isMatch(activeProfile));
    }

    private static Profiles not(Profiles profiles) {
        return activeProfile -> !profiles.matches(activeProfile);
    }

    private static Profiles equals(String profile) {
        return activeProfile -> activeProfile.test(profile);
    }

    private static Predicate<Profiles> isMatch(Predicate<String> activeProfile) {
        return profiles -> profiles.matches(activeProfile);
    }


    private enum Operator {AND, OR}


    private enum Context {NONE, INVERT, BRACKET}


    private static class ParsedProfiles implements Profiles {

        private final Set<String> expressions = new LinkedHashSet<>();

        private final Profiles[] parsed;

        ParsedProfiles(String[] expressions, Profiles[] parsed) {
            Collections.addAll(this.expressions, expressions);
            this.parsed = parsed;
        }

        @Override
        public boolean matches(Predicate<String> activeProfiles) {
            for (Profiles candidate : this.parsed) {
                if (candidate.matches(activeProfiles)) {
                    return true;
                }
            }
            return false;
        }

    }

}

环境

    兜兜转转半天,终于可以进入主题了。不过照目前情况来看,所谓的 环境
也就没什么了。无非就是操作ProfilesPropertyResolver罢了。A君 新增Environment接口,代码如下:

public interface Environment extends PropertyResolver {

    /**
     * 获取激活配置
     *
     * @return
     */
    String[] getActiveProfiles();

    /**
     * 获取默认配置
     *
     * @return
     */
    String[] getDefaultProfiles();

    @Deprecated
    boolean acceptsProfiles(String... profiles);

    boolean acceptsProfiles(Profiles profiles);

}

接下来就是让他可编程、可操作了。A君 新增ConfigurableEnvironment接口,代码如下:

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {

    void setActiveProfiles(String... profiles);

    void addActiveProfile(String profile);

    void setDefaultProfiles(String... profiles);

    MutablePropertySources getPropertySources();

    Map<String, Object> getSystemProperties();

    Map<String, Object> getSystemEnvironment();

    void merge(ConfigurableEnvironment parent);

}

好了,接下来就是抽象类,基本就是获取激活的配置文件就行了。A君 新增AbstractEnvironment类,代码如下:

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

    public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";

    public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";

    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

    protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";

    /**
     * 激活的配置
     */
    private final Set<String> activeProfiles = new LinkedHashSet<>();
    /**
     * 默认的配置
     */
    private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
    /**
     * 配置源
     */
    private final MutablePropertySources propertySources;

    private final ConfigurablePropertyResolver propertyResolver;

	 protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                /**
                 * 获取激活的配置
                 */
                String profiles = doGetActiveProfilesProperty();
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

    protected String doGetActiveProfilesProperty() {
        return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
    }

	//省略其他代码。。。
}

在提供个标准实现,基本也没啥事可做。A君 新增StandardEnvironment类。代码如下:

public class StandardEnvironment extends AbstractEnvironment {

    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(
                new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(
                new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

}
测试

    现在全部都定义好,可以进入测试阶段了。这个测试也相当的简单。先新增一个配置文件application.properties。如下:

myapp.name=evnTest
myapp.version=1.0

这就可以编写测试代码了。代码如下:

	@Test
    public void v44() throws Exception {
        System.out.println("############# 第四十四版: 环境篇 #############");
        StandardEnvironment environment = new StandardEnvironment();

        // 创建 Properties 对象并加载配置文件
        Properties properties = new Properties();
        properties.load(Main.class.getResourceAsStream("/v44/application.properties"));

        // 将属性加载到环境中
        environment.getPropertySources().addFirst(new PropertiesPropertySource("myapp", properties));

        // 读取配置属性
        String appName = environment.getProperty("myapp.name");
        String appVersion = environment.getProperty("myapp.version");

        System.out.println("App Name: " + appName);
        System.out.println("App Version: " + appVersion);
    }

测试结果如下:

在这里插入图片描述

OK,今天也比较简单,可以下班啦

在这里插入图片描述


总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

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

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

相关文章

Router [Continuation Settings]

楼上网络CMCC-Wmew&#xff0c;楼下接收不到&#xff0c;可能因为喜好弱&#xff0c;再弄一台路由器中转一下 Router [Continuation Settings] 路由器中续设置 到这里这台K3的路由器设置完成了&#xff0c;作为转发&#xff0c;中续&#xff0c;她还需要设置上游路由器&#…

Zookeeper中的Zxid是如何设计的

想获取更多高质量的Java技术文章&#xff1f;欢迎访问Java技术小馆官网&#xff0c;持续更新优质内容&#xff0c;助力技术成长 Java技术小馆官网https://www.yuque.com/jtostring Zookeeper中的Zxid是如何设计的 如果你们之前学习过 ZooKeeper&#xff0c;你们可能已经了解…

蓝桥云客 岛屿个数

0岛屿个数 - 蓝桥云课 问题描述 小蓝得到了一副大小为 MN 的格子地图&#xff0c;可以将其视作一个只包含字符 0&#xff08;代表海水&#xff09;和 1&#xff08;代表陆地&#xff09;的二维数组&#xff0c;地图之外可以视作全部是海水&#xff0c;每个岛屿由在上/下/左/右…

31天Python入门——第14天:异常处理

你好&#xff0c;我是安然无虞。 文章目录 异常处理1. Python异常2. 异常捕获try-except语句捕获所有的异常信息获取异常对象finally块 3. raise语句4. 自定义异常5. 函数调用里面产生的异常补充练习 异常处理 1. Python异常 Python异常指的是在程序执行过程中发生的错误或异…

浅析Android Jetpack ACC之LiveData

一、Android Jetpack简介 Android官网对Jetpack的介绍如下&#xff1a; Jetpack is a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across Android versions and devices so that develo…

【区块链安全 | 第十五篇】类型之值类型(二)

文章目录 值类型有理数和整数字面量&#xff08;Rational and Integer Literals&#xff09;字符串字面量和类型&#xff08;String Literals and Types&#xff09;Unicode 字面量&#xff08;Unicode Literals&#xff09;十六进制字面量&#xff08;Hexadecimal Literals&am…

Ubuntu修改用户名

修改用户名&#xff1a; 1.CTRL ALT T 快捷键打开终端&#xff0c;输入‘sudo su’ 转为root用户。 2.输入‘ gredit /etc/passwd ’&#xff0c;修改用户名&#xff0c;只修改用户名&#xff0c;后面的全名、目录等不修改。 3.输入 ‘ gedit /etc/shadow ’ 和 ‘ gedit /etc/…

Windows 系统下多功能免费 PDF 编辑工具详解

IceCream PDF Editor是一款极为实用且操作简便的PDF文件编辑工具&#xff0c;它完美适配Windows操作系统。其用户界面设计得十分直观&#xff0c;哪怕是初次接触的用户也能快速上手。更为重要的是&#xff0c;该软件具备丰富多样的强大功能&#xff0c;能全方位满足各类PDF编辑…

UE学习记录part11

第14节 breakable actors 147 destructible meshes a geometry collection is basically a set of static meshes that we get after we fracture a mesh. 几何体集合基本上是我们在断开网格后获得的一组静态网格。 选中要破碎的网格物品&#xff0c;创建集合 可以选择不同的…

Redis-07.Redis常用命令-集合操作命令

一.集合操作命令 SADD key member1 [member2]&#xff1a; sadd set1 a b c d sadd set1 a 0表示没有添加成功&#xff0c;因为集合中已经有了这个元素了&#xff0c;因此无法重复添加。 SMEMBERS key: smembers set1 SCARD key&#xff1a; scard set1 SADD key member1 …

vscode 源代码管理

https://code.visualstudio.com/updates/v1_92#_source-control 您可以通过切换 scm.showHistoryGraph 设置来禁用传入/传出更改的图形可视化。

iOS审核被拒:Missing privacy manifest 第三方库添加隐私声明文件

问题&#xff1a; iOS提交APP审核被拒&#xff0c;苹果开发者网页显示二进制错误&#xff0c;收到的邮件显示的详细信息如下图: 分析&#xff1a; 从上面信息能看出第三方SDK库必须要包含一个隐私文件&#xff0c;去第三方库更新版本。 几经查询资料得知&#xff0c;苹果在…

【LeetCode Solutions】LeetCode 101 ~ 105 题解

CONTENTS LeetCode 101. 对称二叉树&#xff08;简单&#xff09;LeetCode 102. 二叉树的层序遍历&#xff08;中等&#xff09;LeetCode 103. 二叉树的锯齿形层序遍历&#xff08;中等&#xff09;LeetCode 104. 二叉树的最大深度&#xff08;简单&#xff09;LeetCode 105. 从…

Orpheus-TTS 介绍,新一代开源文本转语音

Orpheus-TTS 是由 Canopy Labs 团队于2025年3月19日发布的开源文本转语音&#xff08;TTS&#xff09;模型&#xff0c;其技术突破集中在超低延迟、拟人化情感表达与实时流式生成三大领域。以下从技术架构、核心优势、应用场景、对比分析、开发背景及最新进展等多维度展开深入解…

Java数据结构-栈和队列

目录 1. 栈(Stack) 1.1 概念 1.2 栈的使用 1.3 栈的模拟实现 1.4 栈的应用场景 1. 改变元素的序列 2. 将递归转化为循环 3. 括号匹配 4. 逆波兰表达式求值 5. 出栈入栈次序匹配 6. 最小栈 1.5 概念区分 2. 队列(Queue) 2.1 概念 2.2 队列的使用 2.3 队列模拟实…

权重衰减-笔记

《动手学深度学习》-4.5-笔记 权重衰减就像给模型“勒紧裤腰带”&#xff0c;不让它太贪心、不让它学太多。 你在学英语单词&#xff0c;别背太多冷门单词&#xff0c;只背常见的就行&#xff0c;这样考试时更容易拿分。” —— 这其实就是在“限制你学的内容复杂度”。 在…

Hyperliquid 遇袭「拔网线」、Polymarket 遭治理攻击「不作为」,从双平台危机看去中心化治理的进化阵痛

作者&#xff1a;Techub 热点速递 撰文&#xff1a;Glendon&#xff0c;Techub News 继 3 月 12 日「Hyperliquid 50 倍杠杆巨鲸」引发的 Hyperliquid 清算事件之后&#xff0c;3 月 26 日 晚间&#xff0c;Hyperliquid 再次遭遇了一场针对其流动性和治理模式的「闪电狙击」。…

软考笔记6——结构化开发方法

第六章节——结构化开发方法 结构化开发方法 第六章节——结构化开发方法一、系统分析与设计概述1. 系统分析概述2. 系统设计的基本原理3. 系统总体结构设计 二、结构化分析方法1. 结构化分析方法概述2. 数据流图(DFD)3. 数据字典 三、结构化设计方法&#xff08;了解&#xff…

一种C# Winform的UI处理

效果 圆角 阴影 突出按钮 说明 这是一种另类的处理&#xff0c;不是多层窗口 也不是WPF 。这种方式的特点是比较简单&#xff0c;例如圆角、阴影、按钮等特别容易修改过。其实就是html css DirectXForm。 在VS中如下 圆角和阴影 然后编辑这个窗体的Html模板&#xff0c…

为什么视频文件需要压缩?怎样压缩视频体积即小又清晰?

在日常生活中&#xff0c;无论是为了节省存储空间、便于分享还是提升上传速度&#xff0c;我们常常会遇到需要压缩视频的情况。本文将介绍为什么视频需要压缩&#xff0c;压缩视频的好处与坏处&#xff0c;并教你如何使用简鹿视频格式转换器轻松完成MP4视频文件的压缩。 为什么…