Spring高手之路——深入理解与实现IOC依赖查找与依赖注入

news2024/11/23 23:54:49

本文从xml开始讲解,注解后面给出

文章目录

  • 1. 一个最基本的 IOC 依赖查找实例
  • 2. IOC 的两种实现方式
    • 2.1 依赖查找(Dependency Lookup)
    • 2.2 依赖注入(Dependency Injection)
  • 3. 在三层架构中的 service 层与 dao 层体会依赖查找与依赖注入的使用
  • 4. 使用注解时,依赖查找在哪里查找?依赖注入在哪里注入?
  • 5. @Autowired 进行自动注入时,如果存在多个同类型的 bean该如何解决?
  • 6. 【面试题】依赖查找与依赖注入的对比

1. 一个最基本的 IOC 依赖查找实例

  首先,我们需要明白什么是IOC(控制反转)和依赖查找。在Spring Framework中,控制反转是一种设计模式,可以帮助我们解耦模块间的关系,这样我们就可以把注意力更多地集中在核心的业务逻辑上,而不是在对象的创建和管理上。

  依赖查找(Dependency Lookup)是一种技术手段,使得我们的对象可以从一个管理它们的容器(例如SpringApplicationContext)中获得它所需要的资源或者依赖。这种查找过程通常是通过类型、名称或者其他的标识进行的。

  我们来看一个简单的例子,通过这个例子,你可以理解在Spring中如何实现IOC依赖查找。这个例子的目标是创建一个简单的"Hello World"应用,通过依赖查找,我们将从Spring的容器中获取一个打印"Hello, World!"的Bean

  第一步,我们需要创建一个简单的Java类,我们将其命名为"HelloWorld":

public class HelloWorld {
    public void sayHello() {
        System.out.println("Hello, World!");
    }
}

  接下来,我们需要在Spring的配置文件中定义这个Bean。这个配置文件通常命名为applicationContext.xml,并放在项目的src/main/resources目录下:

<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">

    <!-- 定义一个名为helloWorld的Bean -->
    <bean id="helloWorld" class="com.example.demo.HelloWorld" />

</beans>

  然后,我们就可以在我们的主程序中通过SpringApplicationContext来获取并使用这个Bean

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // 创建一个ApplicationContext,加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 通过id从ApplicationContext中获取Bean
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");

        // 调用方法
        obj.sayHello();
    }
}

可以在控制台上看到"Hello, World!"的输出。


2. IOC 的两种实现方式

  首先,让我们开始了解什么是控制反转(IoC)。在传统的程序设计中,我们常常会在需要的地方创建对象,然后使用这些对象来完成一些工作。这种方式存在一个问题,那就是对象之间的耦合度太高。如果我们要改变对象的创建方式或者使用不同的对象来完成同样的工作,我们可能需要修改大量的代码。

  为了解决这个问题,IoC的概念被引入。在IoC的设计模式中,对象的创建和维护不再由使用它们的代码来控制,而是由一个容器来控制。这个容器会负责对象的创建、配置和生命周期管理,使得我们的代码可以专注于核心的业务逻辑,而不需要关心对象是如何创建和管理的。这样,当我们需要改变对象的创建方式或者替换对象时,我们只需要修改容器的配置,而不需要修改使用对象的代码。

  接下来,让我们来看看IoC的两种实现方式:依赖查找和依赖注入。

2.1 依赖查找(Dependency Lookup)

  在这种方式中,当一个对象需要一个依赖(比如另一个对象)时,它会主动从容器中查找这个依赖。通常,这个查找的过程是通过类型、名称或者其他的标识来进行的。

  1. 根据名称查找

  在这种方式中,你需要知道你要查找的beanID,然后使用ApplicationContext.getBean(String name)方法查找bean

  例如,假设我们有一个Printer类,它需要一个Ink对象来打印信息。在没有使用Spring框架的情况下,Printer可能会直接创建一个Ink对象:

public class Printer {
    private Ink ink = new Ink();
    //...
}

  但是在Spring框架中,我们可以将Ink对象的创建交给Spring的容器,然后在Printer需要Ink对象时,从容器中查找获取:

public class Ink {

    private String color;

    public void useInk() {
        System.out.println("Using ink...");
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
public class Printer {
    private Ink ink;

    public void setInk(Ink ink) {
        this.ink = ink;
    }
    
    public void print() {
        ink.useInk();
        System.out.println("Printing...");
    }
}
    <bean id="inkId" class="com.example.demo.Ink" />
    <bean id="printer" class="com.example.demo.Printer">
        <property name="ink" ref="inkId" />
    </bean>

  在这个配置中,<bean> 元素定义了一个beanid属性给这个bean定义了一个唯一的名字,class属性则指定了这个bean的完全限定类名。这里“printer”这个bean依赖于"ink" bean。当Spring容器初始化“printer”这个bean的时候,它需要查找到正确名称的"ink" bean,并将它们注入到“printer” bean中。

  这里ref按照名称inkId注入bean,当调用(Printer) context.getBean("printer")时,属于按名称依赖查找,Spring检查xml配置根据名称"printer"和"inkId"注入对象,关于注入,我们后面再说。

   public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Printer printer = (Printer) context.getBean("printer");
    }

  依赖查找过程是隐含在Spring框架的工作机制中的,开发者在编写XML配置的时候,并不需要显式进行查找操作。

  1. 根据类型查找

  在这种方式中,你不需要知道beanID,只需要知道它的类型。然后,你可以使用ApplicationContext.getBean(Class<T> requiredType)方法查找bean

  还是刚刚的Printer类和Ink类,还需要在XML配置文件中定义你的bean。以下面的方式定义InkPrinter类:

这里有多个相同类型的bean,如果按照类型注入,则编译器会报错,如下:

IOC

  我们得按照名称注入,autowire="byType"会直接编译报错,就像有多个一样类型的bean,使用@Autowired注解就会报错。所以这里在printer bean里面指明ink bean

	<bean id="ink1" class="com.example.demo.Ink">
	    <property name="color" value="Black"/>
	</bean>
	
	<bean id="ink2" class="com.example.demo.Ink">
	    <property name="color" value="Blue"/>
	</bean>
	<bean id="printer" class="com.example.demo.Printer">
		<property name="ink" ref="ink1"/>
	</bean>

  这里,我们定义了两个类型为Inkbeanink1ink2,他们的颜色属性分别为“Black”和“Blue”。另外,我们还定义了一个类型为Printerbean,注入ink1依赖。

  然后在Java代码中,可以按照类型获取Ink类型的bean

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

// 按类型获取bean
// 注意,这里存在多个Ink类型的bean,这行代码会抛出异常
// Ink ink = context.getBean(Ink.class); 

// 如果存在多个Ink类型的bean,你可以这样获取所有的Ink bean
Map<String, Ink> beans = context.getBeansOfType(Ink.class);
for (String id : beans.keySet()) {
    System.out.println("Found ink with id: " + id);
    Ink inkBean = beans.get(id);
    // do something with inkBean...
}
IOC

  在这个例子中,context.getBean(Ink.class)会按照类型查找Inkbean。但是这里有多个Ink类型的bean(如本例所示),这种方式会抛出异常。对于这种情况,你可以使用context.getBeansOfType(Ink.class)方法获取所有类型为Inkbean,然后自行决定如何使用这些bean

2.2 依赖注入(Dependency Injection)

  1. 通过类型进行依赖注入

还是和之前一样的PrinterInk类:

public class Ink {

    private String color;

    public void useInk() {
        System.out.println("Using ink...");
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
public class Printer {
    private Ink ink;

    public void setInk(Ink ink) {
        this.ink = ink;
    }
    
    public void print() {
        ink.useInk();
        System.out.println("Printing...");
    }
}

  在这个例子中,Printer类有一个Ink类型的属性ink,并且有一个setter方法setInk用于设置这个属性的值。这就是我们通常说的依赖注入的setter方法。

  这里我们没有使用 Spring@Autowired 注解,而是使用了 Javasetter 方法。你可以通过在 Spring 配置文件中使用 <bean><property> 标签来配置这种依赖关系

  在SpringXML配置文件中,我们可以如下配置:

	<bean id="ink" class="com.example.demo.Ink">
	    <property name="color" value="Blue"/>
	</bean>
	<bean id="printer" class="com.example.demo.Printer" autowire="byType">
	</bean>

  注意,我们这里加上属性autowire="byType",这样Spring就会自动根据类型来进行依赖注入。虽然autowire="byType"会启用按类型的自动注入,但如果显式配置了ink属性的值,Spring会使用你的配置,而不是按类型自动注入。

  当我们需要使用Printer对象时,我们可以从Spring容器中获取:

	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
	Printer printer = (Printer) context.getBean("printer");
	printer.print();

  当我们调用context.getBean("printer")时,会查找idprinterbean对象,Printer对象会使用被Spring容器注入的Ink对象来打印信息。

Using ink...
Printing...

在这个例子中,依赖查找实际上应用于两个地方:

  1. 当使用context.getBean("printer")时,实际上正在查找名为"printer"的Bean对象,即Printer对象,这是一个明显的依赖查找的例子。

  2. Spring框架将Ink对象注入Printer对象时,Printer对象在内部是通过依赖查找方式来找到Ink对象的。在这种情况下,Spring框架控制了这个查找过程,开发者并没有直接进行依赖查找。在这个过程中,Spring框架根据配置文件中定义的依赖关系(这里是类型依赖),自动找到Ink对象,并将它注入到Printer对象中。这个查找过程是隐式的,对开发者是透明的。

所以总的来说,依赖查找可以发生在我们手动从容器中获取Bean的时候,也可以发生在Spring自动注入依赖的过程中。

  通过这种方式,我们不再需要在Printer类中直接创建Ink对象,而是让Spring容器来管理Ink对象的创建和注入,从而实现了PrinterInk之间的解耦。

  1. 通过名称进行依赖注入

首先,我们修改Ink类,添加构造方法,使其可以有多种颜色:

public class Ink {
    private String color;

    public Ink(String color) {
        this.color = color;
    }

    public void useInk() {
        System.out.println("Using " + color + " ink...");
    }
}

然后,在Spring的XML配置文件中,我们定义两种颜色的墨水,以及一个打印机:

<bean id="blackInk" class="com.example.demo.Ink">
    <constructor-arg value="black" />
</bean>

<bean id="blueInk" class="com.example.demo.Ink">
    <constructor-arg value="blue" />
</bean>

<bean id="printer" class="com.example.demo.Printer">
    <property name="ink" ref="blackInk" />
</bean>

  在这个配置中,我们有两种颜色的墨水:黑色和蓝色。在printer bean的定义中,我们通过ref属性指定我们想要注入的墨水的id为"blackInk"。

<property> 元素中,nameref 属性有不同的作用:

  name 属性指的是要注入的目标 bean(这里是 “printer”)的属性名。这个属性名应该在目标 bean 的类(在这个例子中是 com.example.demo.Printer 类)中存在,且应该有相应的 setter 方法。比如,如果 name="ink",那么 com.example.demo.Printer 类中需要有一个名为 ink 的属性,且有一个名为 setInk(...) 的方法。

  ref 属性指的是要注入的源 beanID。这个 ID 应该在同一份 Spring 配置文件中定义过。比如,如果 ref="blackInk",那么应该在配置文件中有一个 <bean id="blackInk" ... /> 的定义。

  因此,当你看到 <property name="ink" ref="blackInk" /> 这样的配置时,你可以理解为:Spring 容器会找到 ID 为 “blackInk” 的 bean(在这个例子中是 com.example.demo.Ink 类的一个实例),然后调用 com.example.demo.Printer 类的 setInk(...) 方法,将这个 Ink 实例注入到 Printer 实例的 ink 属性中。

最后,我们可以在一个Java类中获取printer bean并调用其print方法:

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Printer printer = context.getBean("printer", Printer.class);
printer.print();

这将输出以下结果:

Using black ink...
Printing...

  这里体现的通过名称进行依赖注入的含义是:我们通过指定beanid(在这里是"blackInk"),来明确地告诉Spring我们想要注入哪一个bean。这是与通过类型进行依赖注入的一个主要区别:通过类型进行依赖注入时,Spring会自动选择一个与目标属性类型匹配的bean进行注入,而不需要我们明确指定beanid


3. 在三层架构中的 service 层与 dao 层体会依赖查找与依赖注入的使用

在三层架构中,我们通常会有以下三层:

  • 表示层(Presentation Layer):与用户进行交互的层次,如前端页面、命令行等,比如Controller类里的逻辑。
  • 业务逻辑层(Business Logic Layer),也叫作 Service 层:实现业务逻辑的地方。
  • 数据访问层(Data Access Layer),也叫作 DAO 层:直接操作数据库或者调用API来获取数据。

在本例中,我们将只关注 Service 层与 DAO 层,并且会使用到 Spring 框架。

首先,让我们定义一个业务对象,比如一个用户(User):

public class User {
    private String id;
    private String name;
    private String email;
    // getters and setters omitted for brevity
}

  接着,我们定义 DAO 层。在 DAO 层中,我们通常会有一些方法来操作数据库,如创建用户,获取用户,更新用户等。在本例中,我们简化为一个接口:

public interface UserDao {
    User getUser(String id);
}

  在实际项目中,我们可能有多种 UserDao 的实现,比如 MySQLUserDaoMongoDBUserDao 等。在这里,我们简化为一个模拟实现:

public class UserDaoImpl implements UserDao {
    public User getUser(String id) {
        // 为了简化,我们在这里直接返回一个静态的 User 对象
        User user = new User();
        user.setId(id);
        user.setName("Test User");
        user.setEmail("test@example.com");
        return user;
    }
}

  然后,我们定义 Service 层。在 Service 层中,我们会调用 DAO 层的方法来完成业务逻辑:

public class UserService {
    private UserDao userDao;

    public User getUser(String id) {
        return userDao.getUser(id);
    }
}

  这里的问题是,UserService 需要一个 UserDao 的实例,但是 UserService 并不知道如何获取 UserDao 的实例。这就是我们要解决的问题,我们可以使用依赖查找或者依赖注入来解决。

1.依赖查找:

我们可以修改 UserService 类,让它从 Spring 容器中获取 UserDao 的实例:

public class UserService {
    private UserDao userDao;

    public UserService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        this.userDao = (UserDao) context.getBean("userDao");
    }

    public User getUser(String id) {
        return userDao.getUser(id);
    }
}

这样,当我们创建 UserService 的实例时,它会自动从 Spring 容器中获取 UserDao 的实例。

2.依赖注入:

  我们可以利用 Spring 框架的依赖注入特性,让 Spring 容器自动将 UserDao 的实例注入到 UserService 中。为此,我们需要在 UserService 中添加一个 setter 方法,并在 Spring 的配置文件中配置这个依赖关系:

UserService 类:

public class UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public User getUser(String id) {
        return userDao.getUser(id);
    }
}

Spring 配置文件(applicationContext.xml):

<bean id="userDao" class="com.example.UserDaoImpl" />

<bean id="userService" class="com.example.UserService">
    <property name="userDao" ref="userDao" />
</bean>

  这样,当我们从 Spring 容器中获取 UserService 的实例时,Spring 容器会自动将 UserDao 的实例注入到 UserService 中:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
User user = userService.getUser("1");

如果你想在代码中获取这个bean实例,你需要做两件事情:

  1. 创建一个 Spring 应用上下文(ApplicationContext)对象。这个对象会读取你的 Spring 配置文件,然后根据配置文件的内容创建和管理bean实例。在你的代码中,new ClassPathXmlApplicationContext("applicationContext.xml") 就是在创建一个 Spring 应用上下文对象。“applicationContext.xml” 是你的 Spring 配置文件的路径。

  2. 调用 context.getBean("userDao") 来获取 “userDao” 这个bean的实例。Spring 会根据你的配置创建一个 UserDaoImpl 类的实例,并返回给你。


4. 使用注解时,依赖查找在哪里查找?依赖注入在哪里注入?

当我们全部用注解,不用xml来实现的时候,会写成如下形式:

Ink.java

@Component
public class Ink {
    public void useInk() {
        System.out.println("Using ink...");
    }
}

Printer.java

@Component
public class Printer {

    @Resource
    private Ink ink;

    public void print() {
        ink.useInk();
        System.out.println("Printing...");
    }
}

  @Resource注解告诉Spring,请查找一个名为"ink"的Bean并注入到这个字段中。注意,@Resource的名称是大小写敏感的,因此"ink"和"Ink"是两个不同的名字。

  @Resource 注解的 name 属性应与 @Component 注解中指定的名称相匹配,如果@Component 没有指定name属性,那么bean的名称默认是类名的小写形式。

  如果省略了@Resource注解的name属性,则默认的注入规则是根据字段的名称进行推断,并尝试注入同名的资源,如果Ink类上注解为@Component("inkComponent"),那这个bean的名称就是inkComponent,只能写成如下形式

@Resource(name = "inkComponent")
private Ink ink;
或者
@Resource
private Ink inkComponent;

  为了使这些注解起作用,我们需要开启注解扫描@ComponentScan("com.example")

@Configuration
@ComponentScan("com.example")
public class AppConfig {
    // 可以在这里定义其他的配置或进行其他的注解配置
}

  然后,你可以在你的Java代码中通过AnnotationConfigApplicationContext类获取PrinterBean并调用它的print方法:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Printer printer = context.getBean(Printer.class);
printer.print();

输出以下结果:

Using ink...
Printing...

  这个例子中,我们使用了完全基于Java的配置,没有使用任何XML。通过@Configuration@ComponentScan注解,我们告诉了Spring在哪里找到我们的Bean

这个例子中

  • context.getBean(Printer.class);按照类型进行了依赖查找
  • @Resource按照名称进行了依赖注入
  • @Resource在按名称依赖注入的时候会隐式按名称依赖查找

  有没有人发现这次Printer不用强制转换了?之前还是(Printer) context.getBean("printer");呢。这里把类型都传进去了,ApplicationContext 查找的时候当然按照这个类型查找啊

  依赖注入中的按名称和按类型两种方式,主要体现在注入时如何选择合适的bean进行注入。

  • 按名称进行依赖注入: 是指在进行依赖注入时,根据名称来查找合适的bean。比如在Java代码中使用@Resource(name = "beanName")。这种方式的优点是明确指定了注入的bean

  • 按类型进行依赖注入: 是指在进行依赖注入时,根据类型来查找合适的bean。比如在Java代码中使用@Autowired。缺点是当有多个相同类型的bean存在时,可能会导致选择错误的bean

  至于context.getBean()方法,这是依赖查找的方式,而不是依赖注入。它也分为按名称和按类型两种方式,与依赖注入的按名称和按类型是类似的。

  • 使用context.getBean("beanName"),是按名称进行依赖查找。

  • 使用context.getBean(ClassType)或者context.getBean("beanName", ClassType),是按类型进行依赖查找。如果有多个同类型的bean,则会抛出异常。

  对于依赖查找,除了可以使用 context.getBean 进行显示的查找外,Spring 容器在实例化 Bean 的过程中也会进行隐式的依赖查找。此外,Spring 还支持使用 @Autowired@Resource@Inject 等注解在依赖注入之前隐式依赖查找。

  对于依赖注入,通过 XML 配置文件中的 <property><constructor-arg>进行属性和构造器注入,以及通过 @Autowired@Resource 注解进行注入。但还要注意,Spring 还支持通过 @Value 注解对简单类型的值进行注入,以及通过 @Qualifier 注解对同一类型的不同实例进行精确选择。


5. @Autowired 进行自动注入时,如果存在多个同类型的 bean该如何解决?

  在使用 @Autowired 进行自动装配时,如果存在多个同类型的 beanSpring 会抛出一个 NoUniqueBeanDefinitionException 异常,表示无法确定要注入哪个 bean。为了避免这种错误,可以使用以下方法之一:

  • 使用 @Qualifier 注解:

  @Qualifier 注解可以与 @Autowired 一起使用,通过指定 bean 的名称来解决歧义。

  下面的例子中创建了两个Ink Bean,分别是blueInkredInk。然后在Printer类中,我们通过@Qualifier注解指定需要注入的Bean的名称。

@Component
public class BlueInk implements Ink {
    //...
}

@Component
public class RedInk implements Ink {
    //...
}

@Component
public class Printer {

    @Autowired
    @Qualifier("blueInk")
    private Ink ink;

    public void setInk(Ink ink) {
        this.ink = ink;
    }
    
    public void print() {
        ink.useInk();
        System.out.println("Printing...");
    }
}

注意:@Autowired@Qualifier("blueInk")写在setInk方法上和写在ink字段定义上是一样的。

  • 使用 @Primary 注解:

  可以使用@Primary注解来标识优先注入的Bean。如果在容器中存在多个同类型的BeanSpring会优先注入被@Primary注解标记的Bean

@Component
@Primary
public class BlueInk implements Ink {
    //...
}

@Component
public class RedInk implements Ink {
    //...
}

@Component
public class Printer {
    private Ink ink;

    @Autowired
    public void setInk(Ink ink) {
        this.ink = ink;
    }
    
    public void print() {
        ink.useInk();
        System.out.println("Printing...");
    }
}

  在上面的例子中,我们使用@Primary注解标记了BlueInk,所以在注入Ink类型的Bean时,Spring会优先选择BlueInk

  需要注意的是,@Qualifier注解的优先级高于@Primary注解,也就是说如果同时使用了@Qualifier@Primary注解,Spring会优先考虑@Qualifier注解。


6. 【面试题】依赖查找与依赖注入的对比

  依赖查找(Dependency Lookup)和依赖注入(Dependency Injection)都是在控制反转(Inversion of ControlIoC)的背景下,解决对象间依赖关系的方式,但它们在实现方式上有所不同。

  1. 依赖查找(Dependency Lookup

  依赖查找是一种主动的依赖解决方式。在实际的编程中,我们需要显式地调用API(如ApplicationContext.getBean())来获取所需要的依赖。如果查找的对象里面还有其他对象,则会进行隐式依赖查找,也就是说,查找的对象里面的依赖也会被Spring自动注入。

  依赖查找的一个缺点是它会使代码和Spring框架紧密耦合。这意味着如果我们想要改变依赖解决方式或者更换其他的IoC容器,我们可能需要改动大量的代码,比如改变要查找的bean名称。

  1. 依赖注入(Dependency Injection

  依赖注入是一种被动的依赖解决方式。与依赖查找相比,依赖注入不需要我们显式地调用API,而是由Spring容器负责将依赖注入到需要它的Bean中。

  依赖注入的主要优点是它能够减少代码和Spring框架的耦合度,使我们的代码更加易于测试和维护。同时,它也支持更加复杂的依赖关系,包括循环依赖和自动装配等。

  依赖注入通常分为基于构造器的依赖注入和基于setter的依赖注入,也可以通过使用注解(如@Autowired, @Resource等)来实现。

  总结来说,依赖查找和依赖注入各有优缺点。选择使用哪种方式,主要取决于实际的需求和场景。在实际的开发中,我们通常会更倾向于使用依赖注入,因为它能够提供更高的灵活性和可维护性。比如使用对象时全都是加上@Autowired@Resource注解后直接使用,调用被注入对象的方法,不会再去用API查找对象。

如果看完本篇还有疑问,下篇:Spring高手之路——深入理解注解驱动配置与XML配置的融合与区别



欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

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

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

相关文章

Opencv(图像处理)-基于Python-绘图功能

1.介绍2. line()3.rectangle()4.circle()5. ellipse()6.polylines()7.fillPoly()8. putText()代码示例9.用鼠标在图片上作图 1.介绍 OpenCV为开发者还提供了绘图功能&#xff0c;我们可以通过函数来实现在图片上作图。 2. line() 画线 cv2.line(img&#xff0c;开始点&#x…

G0第23章:GORM基本示例、GORM Model定义、主键、表名、列名的约定

04 GORM基本示例 注意: 本文以MySQL数据库为例&#xff0c;讲解GORM各项功能的主要使用方法。 往下阅读本文前&#xff0c;你需要有一个能够成功连接上的MySQL数据库实例。 Docker快速创建MySQL实例 很多同学如果不会安装MySQL或者懒得安装MySQL&#xff0c;可以使用一下命令…

STL好难(3):vector的使用

目录 1.vector的介绍和使用 2.vector的常见构造&#xff1a; 3.vector的遍历方式 &#x1f349;[ ] 下标 &#x1f349;通过迭代器进行访问&#xff1a; &#x1f349;范围for&#xff1a; 4.vector的迭代器 &#x1f349;begin 和 end &#x1f349;rbegin 和 rend …

【论文阅读】Densenet:Densely Connected Convolutional Networks 密集连接的卷积网络

文章目录 前言一、摘要二、网络架构2.1. densenet2.2. dense block2.3 与resnet对比2.4 pytorch代码 三.实验结果四.结论 前言 从今天开始总结一下之前看的一些深度学习相关的论文。 今天的这篇还是比较经典的论文&#xff1a;密集连接网络。在很多国内的硕士毕业论文里都出现…

XDP入门--eBPF程序实现网桥/二层交换机转发功能

本文目录 1、试验环境2、eBPF字节码源代码实现3、用户态应用层管理与控制程序的源代码实现4、编译与运行5、测试结果 我们在此文的进阶部分 或者 此文中已经描述了如何设置Linux网桥&#xff0c;并将多个以太接口加入网桥后实现一个最基本的二层交换机的二层交换转发功能。Linu…

如何在华为OD机试B卷中获得满分?Java实现【食堂供餐】一文详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

【剑指offer】数据结构——链表

目录 数据结构——字符串直接解【剑指offer】06. 从尾到头打印链表牛客力扣 【剑指offer】24. 反转链表【剑指offer】25. 合并两个排序的链表【剑指offer】35. 复杂链表的复制【剑指offer】52. 两个链表的第一个公共结点 特殊解——双指针【剑指offer】18. 删除链表的节点【剑指…

六级备考23天|CET-6|翻译技巧4|2013年官方样题|新年|9:45~11:00

目录 1 PRACTICE ANSWER 2 PRACTICE ANSWER 3 ​ PRACTICE ANSWER 4 PRACTICE ANSWER 5 PRACTICE ANSWER 6 ​ PRACTICE ANSWER ​​​​​​​ 答案整合​​​​​​​ 1 PRACTICE Chinese new year is the Chinese most important traditional festival, wh…

2023上半年软考系统分析师科目一整理-02

2023上半年软考系统分析师科目一整理-02 1. 安全2. 知识产权 1. 安全 对称加密算法中&#xff0c;由于加密解密都使用同样的密钥&#xff0c;所以密钥需要进行共享&#xff0c;故也被称共享密钥算法。 三重DES加密是使用2个DES密钥&#xff0c;进行多次操作来完成的&#xff…

Redis相关

Redis基本概念 一、Redis的持久化方式二、Redis的单机、主从、哨兵、集群Redis主从复制的原理 三、Redis分布式锁的实现四、缓存穿透 击穿 雪崩 一、Redis的持久化方式 1&#xff09;RDB方式 2&#xff09;AOF方式 二、Redis的单机、主从、哨兵、集群 单机的问题&#xf…

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…

django组件552

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

618京东预售一般便宜多少?跟直接买有啥区别?

618京东预售一般便宜多少?跟直接买有啥区别? 京东作为消费者比较喜欢的电商购物平台之一&#xff0c;经常会推出促销打折的活动&#xff0c;以吸引用户到平台上购物。在这些大促活动中&#xff0c;平台会在预售环节设置专属的优惠&#xff0c;让消费者下单提前锁定这些折扣&a…

一、stable diffusion的发展史

一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#xff0c;一起学习和交流使用过程中的技巧和心得。 帮助新手 帮助没有尝试过SD但又对它感兴趣的同学快速入门&#xff0c;并且能够独立生成以上效果图。 1.发展史介绍&#xff1a; 2015年的时候…

RepGhost 解析

paper&#xff1a;RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization official implementation&#xff1a;https://github.com/chengpengchen/repghost 存在的问题 特征重用feature reuse是轻量网络设计中常用的一种技术&#xff0c;现有的方法通常使…

[元带你学: eMMC协议详解 10] Device 识别流程 与 中断模式

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 全文2700字&#xff0c;重点需掌握设备识别过程&#xff08;CMD1 -> CMD2 -> CMD3&#xff09;, 这很常用&#xff0c; 也是最容易出现异常的地方。其他…

Git进阶之代码回滚、合并代码、从A分支选择N次提交,合并到B分支【revert、merge、rebase、cherry-pick】

B站视频地址&#xff1a; https://www.bilibili.com/video/BV1KX4y1a7N9 Git学习文档&#xff1a;https://d9bp4nr5ye.feishu.cn/wiki/PeDPw3mm3iFA36k9td9cVeignsZ 在很长一段时间里&#xff0c;我对Git的操作只限于&#xff1a;提交代码&#xff0c;拉取代码&#xff0c;合…

研报精选230528

目录 【行业230528华金证券】传媒行业深度研究&#xff1a;AIGC最新应用与场景研究 【行业230528国海证券】电动船舶行业深度报告&#xff1a;绿色智能大势已至&#xff0c;驶向电化百亿蓝海 【行业230528华西证券】纺织服装行业周报&#xff1a;5月增长放缓无碍中长期出清逻辑…

Linux下的yum和vim

目录 一、Linux软件包管理器yum1.1 何为软件包&#xff1f;1.2 rzsz工具1.3 如何安装和卸载软件&#xff1f;1.4 Linux的软件生态 二、vim文本编辑器 一、Linux软件包管理器yum 1.1 何为软件包&#xff1f; 软件包可以理解成是windows下别人提前编译好的安装包程序&#xff0…

任务7 课程信息管理系统

系列文章 任务7 课程信息管理系统 已知课程的信息包括&#xff1a;课程编号&#xff0c;课程名称&#xff0c;课程性质&#xff08;必修、选修&#xff09;&#xff0c;课时&#xff0c;学分&#xff0c;考核方式&#xff08;考试、考查课&#xff09;&#xff0c;开课学期&a…