(一)Spring 核心之控制反转(IoC)—— 配置及使用

news2024/11/16 19:50:05

目录

一. 前言

二. IoC 基础

2.1. IoC 是什么

2.2. IoC 能做什么

2.3. IoC 和 DI 是什么关系

三. IoC 配置的三种方式

3.1. XML 配置

3.2. Java 配置

3.3. 注解配置

四. 依赖注入的三种方式

4.1. 属性注入(setter 注入)

4.2. 构造方法注入(Construct 注入)

4.3. 注解注入

五. IoC 和 DI 使用问题总结

5.1. 为什么推荐构造器注入方式?

5.2. 在使用构造器注入方式时注入了太多的类导致 Bad Smell 怎么办?

5.3. @Autowired、@Resource、@Inject 等注解注入有何区别?

5.3.1. @Autowired 注解

5.3.2. @Resource 注解

5.3.3. @Inject 注解

5.4. 小结


一. 前言

    IoC(Inversion of Control),即控制反转,不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。IoC Container 管理的是 Spring Bean, 那么 Spring Bean 是什么呢?Spring 里面的 Bean 就类似于定义的一个组件,而这个组件的作用就是实现某个功能的,这里所定义的 Bean 就相当于给了你一个更为简便的方法来调用这个组件去实现你要完成的功能。

    如果你有精力看英文,首推 Martin Fowler 大师的 《Inversion of Control Containers and the Dependency Injection pattern》;其次 IoC 作为一种设计思想,不要过度解读,而是应该简化理解。

    在《通过一个 Spring 的 HelloWorld 引入 Spring 要点》中向你展示了 IoC 的基础含义,同时以此发散了一些 IoC 相关知识点;本节将在此基础上进一步解读 IoC 的含义以及 IoC 的使用方式。

二. IoC 基础

2.1. IoC 是什么

    IoC(Inversion of Control),即控制反转,不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

我们来深入分析一下:

1. 谁控制谁,控制什么?

    传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象,包括比如文件等)。

2. 为何是反转,哪些方面反转了?

    有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

图例说明:

传统程序设计下,都是主动去创建相关对象然后再组合起来:

当有了 IoC/DI 的容器后,在客户端类中不再主动去创建这些对象了,如图:

2.2. IoC 能做什么

    IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。

    传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试。有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

    其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了主从换位的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。

    IoC 很好的体现了面向对象设计法则之一 —— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。关于好莱坞法则可参见《面向对象的五大基本原则(SOLID)及扩展原则》。

2.3. IoC 和 DI 是什么关系

    控制反转(IoC)是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是 IoC 是设计思想,DI 是实现方式

    DI(Dependency Injection)即依赖注入:组件之间依赖关系由容器在运行期决定,形象地说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

我们来深入分析一下:

1. 谁依赖于谁?

    当然是应用程序依赖于 IoC 容器。

2. 为什么需要依赖?

    应用程序需要 IoC 容器来提供对象需要的外部资源。

3. 谁注入谁?

    很明显是 IoC 容器注入应用程序某个对象,应用程序依赖的对象。

4. 注入了什么?

    就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

5. IoC 和 DI 有什么关系呢?

    其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物 Martin Fowler 又给出了一个新的名字:依赖注入。相对 IoC  而言,依赖注入明确描述了被注入对象依赖 IoC 容器配置依赖对象。通俗来说就是 IoC 是设计思想,DI 是实现方式

三. IoC 配置的三种方式

    在《通过一个 Spring 的 HelloWorld 引入 Spring 要点》已经给出了三种配置方式,这里再总结下;总体上目前的主流方式是 注解 + Java 配置。

3.1. XML 配置

    顾名思义,就是将 Bean 的信息配置在 xml 文件里,通过 Spring 加载文件为我们创建 Bean。这种方式出现在很多早前的 SSM 项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持 Spring 注解。

  • 优点: 可以使用于任何场景,结构清晰,通俗易懂。
  • 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差。

举例

配置 xx.xml 文件,声明命名空间和配置 Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="userService" class="com.lm.it.springframework.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->
</beans>

3.2. Java 配置

    将类的创建交给我们配置的 JavaConfig 类来完成,Spring 只负责维护和管理,采用纯 Java 创建方式。其本质上就是把在 XML 上的配置声明转移到 Java 配置类中。

  • 优点:适用于任何场景,配置方便,因为是纯 Java 代码,扩展性高,十分灵活。
  • 缺点:由于是采用 Java 类的方式,声明不明显,如果大量配置,可读性比较差。

举例

1. 创建一个配置类, 添加 @Configuration 注解声明为配置类。

2. 创建方法,方法上加上 @Bean,该方法用于创建实例并返回,该实例创建后会交给 Spring 管理,方法名建议与实例名相同(首字母小写)。注:实例类不需要加任何注解:

/**
 * BeansConfig
 */
@Configuration
public class BeansConfig {
    /**
     * @return user dao
     */
    @Bean("userDao")
    public UserDaoImpl userDao() {
        return new UserDaoImpl();
    }

    /**
     * @return user service
     */
    @Bean("userService")
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }
}

3.3. 注解配置

    通过在类上加注解的方式,来声明一个类交给 Spring 管理,Spring 会自动扫描带有@Component、@Controller、@Service、@Repository 这四个注解的类,然后帮我们创建并管理,前提是需要先配置 Spring 的注解扫描器。

  • 优点:开发便捷,通俗易懂,方便维护。
  • 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用 XML 或 JavaConfig 的方式配置。

举例

1. 对类添加 @Component 相关的注解,比如@Controller、@Service、@Repository。

2. 设置 ComponentScan 的 basePackage,比如 <context:component-scan base-package="com.lm.it.springframework"> 或者 @ComponentScan("com.lm.it.springframework") 注解,或者 new AnnotationConfigApplicationContext("com.lm.it.springframework") 指定扫描的basePackage:

/**
 * UserService
 */
@Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }
}

四. 依赖注入的三种方式

    常用的注入方式主要有三种:属性注入setter 注入)构造方法注入(Construct 注入)基于注解的注入(接口注入)

4.1. 属性注入(setter 注入)

在 XML 配置方式中,property 都是 setter 方式注入,比如下面的 xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="userService" class="com.lm.it.springframework.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->
</beans>

本质上包含两步:

  1. 第一步,需要 new UserServiceImpl() 创建对象,所以需要默认构造函数。
  2. 第二步,调用 setUserDao() 函数注入 userDao 的值,所以需要 setUserDao() 函数。
     

所以对应的 Service 类是这样的:

/**
 * UserService
 */
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private UserDaoImpl userDao;

    /**
     * init.
     */
    public UserServiceImpl() {
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }

    /**
     * set dao.
     *
     * @param userDao user dao
     */
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}

在 Java 配置和注解方式下:

/**
 * UserService
 */
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }

    /**
     * set dao.
     *
     * @param userDao user dao
     */
    @Autowired
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}

在 Spring3.x 刚推出的时候,推荐使用注入的就是这种,但是这种方式比较麻烦,所以在 Spring4.x 版本中推荐构造函数注入。

4.2. 构造方法注入(Construct 注入)

在 XML 配置方式中,<constructor-arg> 是通过构造函数参数注入,比如下面的 xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="userService" class="com.lm.it.springframework.service.UserServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->
</beans>

本质上是 new UserServiceImpl(userDao) 创建对象, 所以对应的 Service 类是这样的:

/**
 * UserService
 */
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private final UserDaoImpl userDao;

    /**
     * init.
     * @param userDaoImpl user dao impl
     */
    public UserServiceImpl(UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }
}

在 Java 配置和注解方式下:

/**
 * UserService
 */
 @Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private final UserDaoImpl userDao;

    /**
     * init.
     * @param userDaoImpl user dao impl
     */
    @Autowired // 这里@Autowired也可以省略
    public UserServiceImpl(final UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }
}

在 Spring4.x 版本中推荐的注入方式就是这种。

4.3. 注解注入

    以 @Autowired(自动注入)注解注入为例,修饰符有三个属性:byType、byName、Constructor。默认按照 byType 注入。

  1. byType:查找所有的 set 方法,将符合参数类型的 Bean 注入。
  2. byName:被注入 Bean 的 id 名必须与 set 方法后半截匹配,并且 id 名称的第一个单词首字母必须小写,这一点与手动 set 注入有点不同。
  3. constructor:通过构造方法进行自动注入,Spring 会匹配与构造方法参数类型一致的 Bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的 Bean,那么 Spring 会优先将 Bean 注入到多参数的构造方法中。

示例:

/**
 * UserService
 */
@Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }
}

五. IoC 和 DI 使用问题总结

5.1. 为什么推荐构造器注入方式?

先来看看 Spring 在文档里怎么说:

The Spring team generally advocates constructor injection as it enables one to 
implement application components as immutable objects and to ensure that required 
dependencies are not null. Furthermore constructor-injected components are 
always returned to client (calling) code in a fully initialized state.

    简单的翻译一下:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。

下面来简单的解释一下:

  • 依赖不可变:其实说的就是 final 关键字。
  • 依赖不为空(省去了我们对其检查):当要实例化 UserServiceImpl 的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要 Spring 容器传入所需要的参数,所以就两种情况:1. 有该类型的参数 --> 传入,OK ;2. 无该类型的参数 --> 报错。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在 Java 类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。

所以通常是这样的:

/**
 * UserService
 */
 @Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private final UserDaoImpl userDao;

    /**
     * init.
     * @param userDaoImpl user dao impl
     */
    public UserServiceImpl(final UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }
}

    如果使用 setter 注入,缺点显而易见,对于 IoC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现 NPE 的存在。

// 这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。
UserServiceImpl userService = new UserServiceImpl();
userService.findUserList(); // -> NullPointerException, 潜在的隐患

循环依赖的问题:使用 field 注入可能会导致循环依赖,即 A 里面注入 B,B 里面又注入 A:

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

    如果使用构造器注入,在 Spring 项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是 field 注入的话,启动的时候不会报错,在使用那个 Bean 的时候才会报错。

5.2. 在使用构造器注入方式时注入了太多的类导致 Bad Smell 怎么办?

    比如当你一个 Controller 中注入了太多的 Service 类,Sonar 会给你提示相关告警:

    对于这个问题,说明你的类当中有太多的责任,那么你要好好想一想是不是自己违反了类的单一性职责原则,从而导致有这么多的依赖要注入。

5.3. @Autowired、@Resource、@Inject 等注解注入有何区别?

5.3.1. @Autowired 注解

在 Spring 2.5 引入了 @Autowired 注解:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
  boolean required() default true;
}

从 Autowired 注解源码上看,可以使用在下面这些地方:

@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.PARAMETER) #方法参数
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) #注解

还有一个 required 属性,默认是 true。

总结:

  1. @Autowired 是 Spring 自带的注解,通过 AutowiredAnnotationBeanPostProcessor 类实现的依赖注入;
  2. @Autowired 可以作用在 CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE;
  3. @Autowired 默认是根据类型(byType )进行自动装配的;
  4. 如果有多个类型一样的 Bean 候选者,需要指定按照名称(byName )进行装配,则需要配合 @Qualifier。

    指定名称后,如果 Spring IoC 容器中没有对应的组件,Bean 抛出NoSuchBeanDefinitionException。也可以将 @Autowired 中 required 配置为 false,如果配置为false 之后,当没有找到相应 Bean 的时候,系统不会抛异常。

代码示例

在字段属性上

@Autowired
private HelloDao helloDao;

或者

private HelloDao helloDao;

public HelloDao getHelloDao() {
    return helloDao;
}

@Autowired
public void setHelloDao(HelloDao helloDao) {
    this.helloDao = helloDao;
}

或者

private HelloDao helloDao;

// @Autowired
public HelloServiceImpl(@Autowired HelloDao helloDao) {
    this.helloDao = helloDao;
}
// 构造器注入也可不写@Autowired,也可以注入成功。

将 @Autowired 写在被注入的成员变量上,setter 或者构造器上,就不用在 xml 文件中配置了。

    如果有多个类型一样的 Bean 候选者,则默认根据设定的属性名称进行获取。如 HelloDao 在Spring 中有 helloWorldDao 和 helloDao 两个 Bean 候选者。

@Autowired
private HelloDao helloDao;

    首先根据类型获取,发现多个 HelloDao,然后根据 helloDao 进行获取,如果要获取限定的其中一个候选者,结合 @Qualifier 进行注入。

@Autowired
@Qualifier("helloWorldDao")
private HelloDao helloDao;

    注入名称为 helloWorldDao 的 Bean组件。@Qualifier("XXX") 中的 XX 是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。

    多个类型一样的 Bean 候选者,也可以 @Primary 进行使用,设置首选的组件,也就是默认优先使用哪一个。

注意:使用 @Qualifier 时候,如果设置的指定名称的 Bean 不存在,则会抛出异常,如果防止抛出异常,可以使用:

@Qualifier("xxxxyyyy")
@Autowired(required = false)
private HelloDao helloDao;

在 SpringBoot 中也可以使用 @Bean+@Autowired 进行组件注入,将 @Autowired 加到参数上,其实也可以省略。

@Bean
public Person getPerson(@Autowired Car car) {
 return new Person();
}
// @Autowired 其实也可以省略

5.3.2. @Resource 注解

@Resource 注解源码:

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    // 其他省略
}

从 @Resource 注解源码上看,可以使用在下面这些地方:

@Target(ElementType.TYPE) #接口、类、枚举、注解
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.METHOD) #方法

name 指定注入指定名称的组件。

总结:

  1. @Resource 是 JSR250 规范的实现,在 javax.annotation 包下;
  2. @Resource 可以作用 TYPE、FIELD、METHOD 上;
  3. @Resource 是默认根据属性名称进行自动装配的,如果有多个类型一样的 Bean 候选者,则可以通过 name 进行指定进行注入。

代码示例

@Component
public class SuperMan {
    @Resource
    private Car car;
}

    按照属性名称 car 注入容器中的组件。如果容器中除了 BMW 还有 BYD 两种类型组件。指定加入 BMW。如下代码:

@Component
public class SuperMan {
    @Resource(name = "BMW")
    private Car car;
}

name 的作用类似 @Qualifier。

5.3.3. @Inject 注解

@Inject 注解源码:

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

从 @Inject 注解源码上看,可以使用在下面这些地方:

@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.FIELD) #字段、枚举的常量

总结:

  1. @Inject 是 JSR330(Dependency Injection for Java)中的规范,需要导入 javax.inject.Inject jar 包 ,才能实现注入;
  2. @Inject 可以作用 CONSTRUCTOR、METHOD、FIELD 上;
  3. @Inject 是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合 @Named。

代码示例:

@Inject
private Car car;

指定加入 BMW 组件

@Inject
@Named("BMW")
private Car car;

@Named 的作用类似 @Qualifier

5.4. 小结

1. @Autowired 是 Spring 自带的,@Resource 是 JSR250 规范实现的,@Inject 是 JSR330 规范实现的。

2. @Autowired、@Inject 用法基本一样,不同的是 @Inject 没有 required 属性。

3. @Autowired、@Inject 是默认按照类型匹配的,@Resource 是按照名称匹配的。

4. @Autowired 如果需要按照名称匹配需要和 @Qualifier 一起使用,@Inject 和 @Named 一起使用,@Resource 则通过 name 进行指定。

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

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

相关文章

ES Serverless让日志检索更加便捷

前言 在项目中,或者开发过程中,出现bug或者其他线上问题,开发人员可以通过查看日志记录来定位问题。通过日志定位 bug 是一种常见的软件开发和运维技巧,只有观察日志才能追踪到具体代码。在软件开发过程中,开发人员会在代码中添加日志记录,以记录程序的运行情况和异常信…

【蓝桥杯日记】复盘篇二:分支结构

前言 本篇笔记主要进行复盘的内容是分支结构&#xff0c;通过学习分支结构从而更好巩固之前所学的内容。 目录 前言 目录 &#x1f34a;1.数的性质 分析&#xff1a; 知识点&#xff1a; &#x1f345;2.闰年判断 说明/提示 分析&#xff1a; 知识点&#xff1a; &am…

【Linux操作系统】:Linux开发工具编辑器vim

目录 Linux 软件包管理器 yum 什么是软件包 注意事项 查看软件包 如何安装软件 如何卸载软件 Linux 开发工具 Linux编辑器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 插入模式 插入模式切换为命令模式 移动光标 删除文字 复制 替换 撤销 跳至指…

C++——list的使用及其模拟实现

list 文章目录 list1. 基本使用1.1 list对象的定义1.2 增&#xff08;插入数据&#xff09;1.3 删&#xff08;删除数据&#xff09;1.4 遍历访问 2. 模拟实现2.1 节点类ListNode2.2 封装ListNode类&#xff0c;实现list基本功能2.3 实现迭代器iterator2.3.1 实现const迭代器co…

使用Hutool工具包解析、生成XML文件

说明&#xff1a;当我们在工作中需要将数据转为XML文件、或者读取解析XML文件时&#xff0c;使用Hutool工具包中的XMLUtil相关方法是最容易上手的方法&#xff0c;本文介绍如何使用Hutool工具包来解析、生成XML文件。 开始之前&#xff0c;需要导入Hutool工具包的依赖 <de…

力扣hot100 柱状图中最大的矩形 单调栈

Problem: 84. 柱状图中最大的矩形 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考地址 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public static int largestRectangleArea(int[] height){Stack&l…

疯狂的方块

欢迎来到程序小院 疯狂的方块 玩法&#xff1a;两个以上相同颜色的方块连在一起&#xff0c;点击即可消除&#xff0c;不要让方块到达顶部&#xff0c;消除底部方块哦^^。开始游戏https://www.ormcc.com/play/gameStart/263 html <div id"gameDiv"> <canv…

fiber学习

React原理&#xff1a;通俗易懂的 Fiber - 掘金

nacos启动失败解决

报错信息 Caused by: com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (2,937 > 2,048). You can change this value on the server by setting the ‘max_allowed_packet’ variable. 情景复现 最近使用mac正在运行一个nacos的spri…

treeview

QML自定义一个TreeView&#xff0c;使用ListView递归 在 Qt5 的 QtQuick.Controls 2.x 中还没有 TreeView 这个控件&#xff08;在 Qt6 中出了一个继承自 TableView 的 TreeView&#xff09;&#xff0c;而且 QtQuick.Controls 1.x 中的也需要配合 C model 来自定义&#xff0c…

Win10 双网卡实现同时上内外网

因为需要同时上内网和外网&#xff0c;但公司做了网络隔离&#xff0c;不能同时上内外网&#xff0c;所以多加了块无线网卡&#xff0c;配置双网关实现同时上内外网&#xff0c;互不影响 打开 Windows PowerShell&#xff08;管理员&#xff09;&#xff0c;输入&#xff1a;ro…

Github 2024-01-30 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-30统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目2Jupyter Notebook项目2HTML项目1Rust项目1C项目1 稳定扩散Web UI 创建周期&…

C++核心编程:类和对象 笔记

4.类和对象 C面向对象的三大特性为:封装,继承,多态C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 例如&#xff1a; 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...,行为有走、跑、跳、说话...车可以作为对象&#xff0c;属性有轮胎、方向盘、车灯…

万兆网络数据传输-scp加速

在万兆甚至更高的网络带宽场景下 scp 的传输效率并不如人意。毕竟 scp 是旧时代的产物&#xff0c;那时千兆网络都很罕见。以下通过修改压缩方式的方法提升数据的传输速度。同时也采用 nc &#xff0c; bbcp 和 rsync 进行了对比测试。 目录 scp采用默认方式更改压缩算法为 aes…

seata 分布式

一、下载安装seata 已经下载好的朋友可以跳过这个步骤。这里下载的是seata1.6.1这个版本。 1、进入seata官网 地址&#xff1a; https://seata.io/zh-cn/index.html 2、进入下载 3、点击下载地址 下载地址&#xff1a; https://github.com/seata/seata 二、配置seata 进入c…

vue3项目中让echarts适应div的大小变化,跟随div的大小改变图表大小

目录如下 我的项目环境如下利用element-resize-detector插件监听元素大小变化element-resize-detector插件的用法完整代码如下&#xff1a;结果如下 在做项目的时候&#xff0c;经常会使用到echarts&#xff0c;特别是在做一些大屏项目的时候。有时候我们是需要根据div的大小改…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷9

某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenStack搭建企业内部私有云平台&#xff0c;开源Kubernetes搭建云原生服务平台&#xff0c;选…

redis—Zset有序集合

目录 前言 1.常见命令 2.使用场景 3.渐进式遍历 4.数据库管理 前言 有序集合相对于字符串、列表、哈希、集合来说会有一-些陌生。它保留了集合不能有重复成员的 特点&#xff0c;但与集合不同的是&#xff0c;有序集合中的每个元素都有-个唯- -的浮 点类型的分数(score) …

c语言常量详解 全

c语言常量详解 全 一 常量的基本概念及分类二 常量的存储方式三 c语言常量和变量的具体区别四 字面常量详解4.1 常见类型的字面常量及其示例&#xff1a;4.2 字面常量的使用情况4.3 字面常量的优点 五 const 关键字常量详解5.1 const关键字在C语言中的使用情况&#xff1a;5.2 …

山海鲸可视化大屏:引领企业洞悉市场,提升客户价值的秘密武器

随着大数据时代的到来&#xff0c;企业面临着前所未有的机遇与挑战。如何从海量数据中挖掘出有价值的信息&#xff0c;洞察市场趋势&#xff0c;提升客户价值&#xff0c;成为了企业发展的重要课题。山海鲸可视化企业客户价值分析大屏&#xff0c;为企业提供了一个全新的解决方…