Spring6--基于注解管理Bean / 手写IOC

1. 基于注入管理Bean概念

Java 5 引入了注解(Annotation)这一特性,它允许程序员在源代码中插入元数据,这些元数据以标签形式存在,可以被编译器、类加载器或运行时环境所识别和处理。注解可以帮助开发者在不修改业务逻辑的前提下,向代码中添加额外的描述性信息,比如标记服务、组件、属性或其他需要特定处理的部分。

注解(Annotation)是Java编程语言中的一种强大特性,它允许开发者在源代码级别上附加一种类似于注释的元数据(meta-data)。这种元数据并不影响程序本身的执行逻辑,但它能携带关于代码元素(如类、方法、变量等)的附加信息,这些信息可以被编译器、开发工具或运行时环境所解读和利用。

注解以@符号开头,后面跟注解的名称,有时还会包含一些参数值。例如,在Java中,@Override就是一个内建的注解,它用来标记一个方法是重写了父类或接口中的方法,编译器会根据这个注解检查是否真的存在这样的重写关系,如果不存在,则会产生编译错误。

除了像@Override这类编译时就发挥作用的注解,还有很多注解在程序运行时才起作用。例如,在Spring框架中,@Component、@Service、@Repository和@Controller等注解用于标记类为Spring容器管理的组件,而@Autowired注解则指示Spring自动进行依赖注入。

Spring Framework 自从 2.5 版本开始增强了对注解功能的支持,其中一个重要应用就是通过注解实现自动装配(Auto-wiring),从而大大简化了基于 XML 的配置方式。

举个例子:

想象一下你正在经营一家咖啡店,每个员工都有不同的职责,如吧台员负责制作咖啡,收银员负责结账等。在传统的管理模式中,你需要详细地编写一份工作手册,说明谁做什么工作,如何与其他角色配合。

现在,我们把这家咖啡店比作一个Java应用程序,每个员工看作是一个类或者对象,他们的职责则是类的方法。在没有注解的情况下,你需要类似XML配置文件这样的“工作手册”,明确指出哪类对象应该扮演哪个角色(如Barista类是吧台员,Cashier类是收银员)以及他们如何协同工作(如吧台员需要一个磨豆机实例来进行工作)。

引入注解后,就像在员工的衣服上贴标签一样,可以直接在代码里标明:“我是吧台员” (@Component + @Role("Barista")) 或者 "我需要磨豆机" (@Autowired private CoffeeGrinder grinder)。这样一来,系统(Spring框架)在运行时就能自动读取这些标签(注解),并依据注解信息完成相应的工作分配和协调,无需再查看那份详细的“工作手册”。

这样,注解使得我们的代码更加简洁清晰,同时大大减少了配置工作量,提高了开发效率和可维护性。


以下是使用注解实现 Spring 自动装配的基本步骤:

  1. 引入依赖
    在构建项目时,确保包含 Spring 相关注解处理器的依赖项,如 spring-contextspring-boot-starter,这将使 Spring 能够识别和处理注解。
  2. 开启组件扫描
    在 Spring 配置类或 XML 配置文件中启用组件扫描(Component Scan),这样 Spring 容器启动时会自动检测指定包及其子包下的类,寻找带有特定注解(如 @Component, @Service, @Repository, @Controller)的类并将它们作为 Bean 进行注册。
  3. 使用注解定义 Bean
    在需要由 Spring 管理的类上使用上述注解,表明它们是 Spring 容器中的 Bean。例如,通过在业务类上标注 @Service 注解,告诉 Spring 这是一个服务层的 Bean。
  4. 依赖注入
    利用注解进行依赖注入,如使用 @Autowired 注解来指示 Spring 自动查找并注入相应类型的 Bean。例如,在一个类的字段、构造器或方法参数上使用 @Autowired,Spring 将负责找到符合条件的 Bean 实例并注入到对应位置。

总结:

通过注解技术,Spring 可以自动发现、实例化和组装对象,减少手动编写配置的工作量,提高了代码的可读性和维护性。


2. 搭建子模块Spring6-ioc-annotation

创建一个子模块--Spring6-ioc-annotation

ta的父工程是之前的Spring6,这里不细说~

因为父工程里已经添加了相关依赖

如:Junit测试单元、log4j2日志、spring-context之类

所以这里子模块Spring6-ioc-annotation的pom文件中不需要添加

在resource资源文件夹下创建spring配置文件、log4j2日志文件

log4j2.xml文件注意这两个本地日志存储位置,根据实际情况来

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                TRACE < DEBUG < INFO < WARN < ERROR < FATAL
                trace:追踪,是最低的日志级别,相当于追踪程序的执行
                debug:调试,一般在开发中,都将其设置为最低的日志级别
                info:信息,输出重要的信息,使用较多
                warn:警告,输出警告的信息
                error:错误,输出错误信息
                fatal:严重错误
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="log"/>
        </root>
    </loggers>
    
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
        
        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="log" fileName="F:/Program/Spring6Log/test.log" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>
        
        <!-- 这个会打印出所有的信息,
            每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档-->
        <RollingFile name="RollingFile" fileName="F:/Program/Spring6Log/app.log"
                     filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="50MB"/>
            <!-- DefaultRolloverStrategy属性如不设置,
            则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </appenders>
</configuration>

结构如图


3. 开启组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

所以,我们需要配置bean.xml文件,来开启组件扫描


3.1. 配置bean.xml

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

</beans>

添加context命名空间后的样式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/beans/contexts.xsd " >

</beans>

给个建议

在Spring的bean.xml配置文件中添加新命名空间时,可以遵循以下步骤进行操作:

  1. 命名空间声明:
    在XML文档头部,使用xmlns:prefix="http://www.springframework.org/schema/namespace"格式声明新的命名空间,其中prefix是一个自定义的、用于引用该命名空间的别名,而namespace代表具体的Spring功能模块,例如“context”。
    例如:
xmlns:context="http://www.springframework.org/schema/context"

  1. schemaLocation属性配置:
    xsi:schemaLocation属性中,为新添加的命名空间指定对应的xsd文件位置,格式为http://www.springframework.org/schema/namespace location="xsd文件路径"
    例如:
xsi:schemaLocation="...
                   http://www.springframework.org/schema/context
                   http://www.springframework.org/schema/context/spring-context.xsd"

综上所述,在bean.xml中添加Spring Context命名空间的完整示例为:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置内容 -->
</beans>

通过这种方式,你可以逐步熟悉并掌握在Spring配置文件中引入不同功能模块所需的命名空间,提高理解和实践能力。

个人建议是以后多手动练练,当然你要是觉得懒也可以直接复制下面的,给个建议哈

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context ">

</beans>

3.2. 开启组件扫描

在使用 context:component-scan 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 <beans> 中添加 context 相关的约束。

也就是我上面刚刚说到的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--
    开启组件扫描功能
    参数:base-package 指定需要扫描的包路径
    返回值:无
    功能描述:通过指定包路径,自动发现和注册符合特定注解(如@Component、@Service、@Repository等)的类,完成依赖注入。
    -->
    <context:component-scan base-package="com.sakurapaid.spring6">
        
    </context:component-scan>

</beans>


组件扫描的几种设定情况

在Spring框架中,为了让Spring能够识别并自动管理那些使用注解标记的Bean,我们需在Spring的XML配置文件中通过 <context:component-scan> 标签启用组件扫描功能。具体来说:

  1. 情况一:最基本的扫描方式
<!-- 扫描指定包下的所有组件,自动注册为Spring Bean -->
<context:component-scan base-package="com.sakurapaid.spring6">
  
</context:component-scan>
  • 第一个配置扫描com.sakurapaid.spring6包下所有的组件,没有任何过滤条件,所以会注册包下所有被Spring管理的组件。
  • 在这最基本的形式中,Spring会扫描指定的包"com.Sakurapaid.spring6"及其所有子包,查找标注了@Component、@Service、@Repository、@Controller等Spring注解的类,并将它们作为Bean注册到IoC容器中。

  1. 情况二:指定要排除的组件
<!-- 扫描指定包下除了注解为@Controller的组件外的所有组件 -->
<context:component-scan base-package="com.sakurapaid.spring6">
    
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  
</context:component-scan>
  • 第二个配置同样扫描com.sakurapaid.spring6包,但排除了所有被@Controller注解标记的组件。
  • 在此情况下,除了基础扫描外,我们还指定了一个排除规则,即排除所有标注了@Controller注解的类。这意味着在扫描过程中,Spring会忽略掉所有被@Controller注解的类,不会将其作为Bean注册到IoC容器中。

  1. 情况三:仅扫描指定组件
<!-- 扫描指定包下仅包含注解为@Controller的组件 -->
<context:component-scan base-package="com.sakurapaid" use-default-filters="false">
    
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  
</context:component-scan>
  • 第三个配置扫描com.sakurapaid包,但只包含被@Controller注解标记的组件,且通过设置use-default-filters="false"禁用了默认的过滤器,只保留了显式定义的包含过滤器。
  • 在这一场景中,Spring默认的扫描行为被关闭(use-default-filters="false"),即不再扫描指定包及子包下的所有类。然后通过<context:include-filter>标签设置了自定义的扫描规则,只包含标注了@Controller注解的类。这样,只有满足此条件的类才会被扫描并注册为Spring容器中的Bean。

3.3. 使用注解定义Bean

在Spring框架中,当我们开发一个应用程序时,我们会有很多类,比如处理业务逻辑的类、连接数据库的类以及处理用户请求的类等等。为了让Spring框架能够管理和使用这些类,我们需要将它们注册到Spring的容器(IoC容器)中。就像我们要把各种工具都放在工具箱里以便随时取用一样,Spring也需要一个地方存放和管理这些类,这个地方就是IoC容器

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean:

注解

说明

@Component

该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。

@Repository

该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Service

该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Controller

该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

  • @Component这个就像是一个通用标签,贴在任何一个你想让Spring管理的类上,告诉Spring:“嘿,这是一个重要的部分,你要关注它。”无论这个类是处理业务逻辑、数据库交互还是其他功能,只要贴上这个标签,Spring就会把它当作一个Bean(也就是一个可以在容器中被管理和使用的对象)来对待。
  • @Repository@Service @Controller:这三个注解其实是@Component的细分和增强,它们分别针对不同的应用场景:
    • @Repository专门用于标注数据访问层(DAO层)的类,比如数据库操作类。当Spring看到这个注解时,就知道这个类是用来处理数据库相关工作的。
    • @Service用于标注业务逻辑层(Service层)的类,这类类一般封装了复杂的业务处理逻辑。
    • @Controller在Spring MVC环境下使用,标注的是控制器层的类,这类类主要负责接收用户的HTTP请求,执行相应的业务逻辑,并返回响应结果。

尽管它们各自有特定的用途和语境,但从功能上讲,它们和@Component一样,都能使一个类成为Spring IoC容器中的Bean。这样做不仅简化了配置,同时也提高了代码的可读性和结构化程度。


举个代码栗子:

// 数据访问层(DAO层)
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    // 假设这是一个简单的方法,用于从数据库获取用户信息
    public User getUserById(Long id) {
        // 实现数据库查询逻辑
        return new User(id, "username", "password");
    }
}

// 业务逻辑层(Service层)
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    // 构造器注入 UserRepository
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 业务方法,调用DAO层方法完成用户信息获取
    public User findUserById(Long id) {
        return userRepository.getUserById(id);
    }
}

// 控制层(Spring MVC中的Controller)
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    private final UserService userService;

    // 构造器注入 UserService
    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 处理HTTP GET请求,根据用户ID获取用户信息
    @GetMapping("/users/{id}")
    @ResponseBody
    public User getUser(@PathVariable Long id) {
        return userService.findUserById(id);
    }
}

在以上代码中:

  • UserRepository类通过@Repository注解被标识为Spring容器中的一个Bean,Spring知道它是处理数据库相关工作的类。
  • UserService类通过@Service注解被标识为Spring容器中的一个Bean,它封装了业务逻辑,依赖于UserRepository
  • UserController类通过@Controller注解被标识为Spring容器中的一个Bean,它在Spring MVC环境中处理HTTP请求,并通过调用UserService完成业务逻辑,最终返回响应结果。

同时,由于我们在bean.xml或其他Spring配置文件中启用了组件扫描(如之前讨论的<context:component-scan>配置),Spring会自动发现并管理这些带有注解的类,无需手动在XML配置文件中逐一定义Bean。这样既简化了配置,也使得代码结构更为清晰和易于理解。


3.4. 实验一:@Autowired

@Autowired 是 Spring 框架提供的一种自动装配机制,它告诉 Spring 容器,某个 Bean(类实例)需要依赖其他的 Bean,并希望 Spring 能够自动找到并注入这些依赖。

查看源码:

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

第一部分

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)

在源码中的 @Target 注解列出了 @Autowired 可以使用的场景:

  1. 构造函数: 当你在一个类的构造函数上添加 @Autowired 时,Spring 会在创建这个类的实例时,根据构造函数参数的类型自动找寻并注入相应的 Bean。
  2. 方法: 如果你在某个方法上使用 @Autowired,Spring 会在 Bean 初始化后调用此方法,并给其参数注入对应的 Bean。
  3. 方法参数: 在方法的参数上使用 @Autowired,Spring 在调用该方法前会自动根据参数类型注入相应的 Bean。
  4. 类成员变量: 直接在类的成员变量(字段)上使用 @Autowired,Spring 会自动将匹配类型的 Bean 注入到这个字段中。
  5. 自定义注解: @Autowired 也可以应用在注解类型上,以便自定义具有类似自动装配功能的注解。

第二部分

public @interface Autowired {
    boolean required() default true;
}

@Autowired 注解有一个 required 属性,它的默认值是 true,意味着:

  • required=true:Spring 容器在处理自动装配时,必须找到一个与要注入的字段或方法参数类型匹配的 Bean。如果没有找到匹配的 Bean,Spring 会抛出异常,因为它认为这是必需的依赖。
  • required=false:在这种情况下,Spring 仍然会尝试找到匹配的 Bean 来进行注入,但如果没有找到合适的 Bean,它不会抛出异常,而是允许注入的字段或参数保持未注入的状态。这就意味着这个依赖项不是强制必须的。

所以,简单来讲,@Autowired 是 Spring 帮助我们自动连接不同组件(Bean)的一种方式,而 required 参数则用来决定是否对这种依赖关系做强制要求。


3.4.1. 属性注入

这前提是要有spring配置文件开启组件扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--
    开启组件扫描,让Spring容器自动发现和装配标注了相应注解的Bean。
    参数:
    base-package:指定需要扫描的包路径,Spring会在此路径及其子包下查找组件。
    -->
    <context:component-scan base-package="com.sakurapaid.spring6"/>
    
</beans>

创建UserDao接口

package com.sakurapaid.spring6.autowired.dao;

public interface UserDao {
    public void print();
}

创建UserDaoImpl实现

package com.sakurapaid.spring6.autowired.dao;

import org.springframework.stereotype.Repository;

@Repository // 标示一个数据库访问层的实现类
public class UserDaoImpl implements UserDao {
    /**
     * 打印信息,表示Dao层操作已经结束。
     * 此方法没有参数。
     * 也没有返回值。
     */
    @Override
    public void print() {
        System.out.println("Dao层执行结束...");
    }
}

创建UserService接口

package com.sakurapaid.spring6.autowired.service;

public interface UserService {
    public void out();
}

创建UserServiceImpl实现类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

    @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

创建UserController类

package com.sakurapaid.spring6.autowired.controller;

import com.sakurapaid.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserController {

    @Autowired // 自动注入UserService实例,以便在控制器中使用
    private UserService userService;

    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

测试输出,代码结构如图

package com.sakurapaid.spring6.autowired;

import com.sakurapaid.spring6.autowired.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * UserTest 类用于通过单元测试方法测试 UserController 类的功能。
 */
public class UserTest {
    
    /**
     * test 方法用于测试 UserController 类的实例是否能通过 Spring 上下文正确获取,并调用其方法。
     * 该方法没有参数。
     * 该方法没有返回值。
     */
    @Test
    public void test() {
        // 创建一个 ClassPathXmlApplicationContext 实例,用来加载并使用 "bean.xml" 配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        
        // 通过上下文获取 UserController 类的实例
        UserController userController = context.getBean(UserController.class);
        
        // 调用 UserController 实例的 out 方法,进行测试
        userController.out();
    }
}


这是一个简单的Spring项目,其中包含三个主要部分:

  1. 服务层(Service Layer):
    UserServiceImpl类中,有一个UserDao类型的私有成员变量userDao,并使用了@Autowired注解。这意味着Spring框架会自动帮我们找到并设置合适的UserDao实例给这个变量。
  2. 控制层(Controller Layer):
    类似地,在UserController类中,有一个UserService类型的私有成员变量userService,同样使用了@Autowired注解。这样,Spring会自动把已创建好的UserService实例(即UserServiceImpl的一个实例)注入到这里。
  3. Spring容器初始化与注入:
    当通过ClassPathXmlApplicationContext加载Spring配置文件"bean.xml"时,Spring容器开始启动并管理这些Bean。它会识别出带有@Service@Controller注解的类,并创建相应的实例。同时,对于带有@Autowired注解的成员变量,Spring会根据类型自动寻找并注入相应的Bean。
  4. 测试过程:
    在测试类UserTest中,我们从Spring容器中获取UserController实例,并调用其out()方法。由于Spring已经完成了依赖注入,UserController里的userService已经有实际的UserService实现,所以可以顺利调用其方法。而在UserServiceout()方法里,又能顺利调用到已注入的UserDao的方法。

总结来说,属性注入就是Spring框架帮助我们将相互依赖的对象关联起来的过程,无需我们在代码中手动创建和组装这些依赖关系,降低了耦合度,提高了程序的灵活性和可维护性。


3.4.2. set注入

把@Autowired注解用在对应的set方法上,而不是在成员属性上

修改UserServiceImpl类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

   /* @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;*/
    
    private UserDao userDao; // 用户数据访问对象

    /**
     * 通过自动装配设置UserDao对象。
     * @param userDao 用户数据访问对象,用于进行用户数据的CRUD操作。
     */
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }


    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

修改UserController类

package com.sakurapaid.spring6.autowired.controller;

import com.sakurapaid.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserController {

    /*@Autowired // 自动注入UserService实例,以便在控制器中使用
    private UserService userService;*/

    // UserService的引用,用于进行用户相关的操作
    private UserService userService;
    
    /**
     * 通过@Autowired注解自动注入UserService实例。
     * 
     * @param userService 要注入的UserService对象。
     */
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }


    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

测试输出

注意:我把上一个代码测试的对象注释掉了,但重新运行测试代码能达到一样的效果,证明set起到了效果

package com.sakurapaid.spring6.autowired;

import com.sakurapaid.spring6.autowired.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * UserTest 类用于通过单元测试方法测试 UserController 类的功能。
 */
public class UserTest {

    /**
     * test 方法用于测试 UserController 类的实例是否能通过 Spring 上下文正确获取,并调用其方法。
     * 该方法没有参数。
     * 该方法没有返回值。
     */
    @Test
    public void test() {
        // 创建一个 ClassPathXmlApplicationContext 实例,用来加载并使用 "bean.xml" 配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        // 通过上下文获取 UserController 类的实例
        UserController userController = context.getBean(UserController.class);

        // 调用 UserController 实例的 out 方法,进行测试
        userController.out();
    }
}

@Autowired注解从成员变量移到了对应的setter方法上,这种做法称为setter注入。Spring框架依然利用依赖注入(Dependency Injection, DI)的机制来管理Bean之间的依赖关系。

下面是setter注入的原理过程:

  1. Spring容器初始化
    当创建ClassPathXmlApplicationContext并加载"bean.xml"配置文件时,Spring容器开始初始化并管理所有被标记为@Service@Controller等注解的Bean。
  2. 扫描Bean及其依赖
    Spring容器会识别出UserServiceImplUserController类上的注解,并准备创建这两个Bean的实例。同时,Spring会检查这些类中是否存在带@Autowired注解的setter方法。
  3. 依赖注入
    对于UserServiceImpl,Spring发现setUserDao方法上有@Autowired注解,就会从容器中查找类型匹配的UserDao Bean,找到后调用setUserDao方法,将UserDao实例注入给UserServiceImpl
    同理,对于UserController,Spring会找到并调用setUserService方法,将已创建的UserService实例(也就是UserServiceImpl实例)注入给UserController
  4. 测试阶段
    在测试类UserTest中,我们从Spring容器获取UserController实例。由于Spring已经在容器初始化阶段完成了对UserControllerUserService的依赖注入,所以在调用userController.out()时,userService已经具备了完整的功能,可以正常调用UserServiceImpl中的out()方法,并进一步调用UserDao的方法。

总的来说,无论是成员变量注入还是setter方法注入,Spring都遵循同样的依赖注入原则,即由容器负责管理Bean的生命周期和依赖关系,从而降低模块间的耦合度,提高系统的可测试性和可维护性。在本例中,通过setter方法注入的方式,仍然实现了相同的效果。


3.4.3. 构造方法注入

将@Autowired放在了构造方法上

修改UserServiceImpl类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

   /* @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;*/

    /*private UserDao userDao; // 用户数据访问对象

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    private UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

修改UserController类

package com.sakurapaid.spring6.autowired.controller;

import com.sakurapaid.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserController {

    /*@Autowired // 自动注入UserService实例,以便在控制器中使用
    private UserService userService;*/

    // UserService的引用,用于进行用户相关的操作
    /*private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }*/

    private UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

测试输出

测试代码是一样的,这里就不重复了,直接放结果


构造方法注入是Spring框架依赖注入的另一种方式,它通过在类的构造函数上使用@Autowired注解来完成依赖对象的注入。以下是构造方法注入的原理过程:

  1. Spring容器初始化
    当创建ClassPathXmlApplicationContext并加载配置文件时,Spring容器开始扫描并管理所有的Bean。
  2. 识别构造器注入
    Spring容器在创建UserServiceImpl Bean时,会发现其构造函数上有@Autowired注解,这意味着Spring需要通过此构造函数来初始化UserServiceImpl实例,并注入所需的UserDao依赖。
  3. 注入依赖
    Spring会在IoC容器中查找类型匹配的UserDao Bean,找到后将其实例作为参数传入UserServiceImpl的构造函数,从而完成对UserDao依赖的注入。
    同样地,在创建UserController Bean时,Spring发现其构造函数上有@Autowired注解,此时Spring会查找并注入已经初始化好的UserService Bean。
  4. 测试阶段
    测试类UserTest从Spring容器获取UserController实例时,由于Spring已在初始化阶段通过构造方法注入的方式完成了对UserControllerUserService的依赖设置,所以调用userController.out()时,userService能够正确地执行业务逻辑。

总结来说,构造方法注入是在Bean实例化阶段通过构造函数一次性注入所有必需的依赖,相比于成员变量注入和setter方法注入,构造方法注入确保了对象在实例化后即可拥有完整的功能,增强了对象的即时可用性和一致性。在上述代码中,无论是在UserServiceImpl还是UserController类中,都是通过构造方法注入的方式来完成依赖注入的。


3.4.4. 形参上注入

@Autowired 也可以用在形参上

修改UserServiceImpl类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

   /* @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;*/

    /*private UserDao userDao; // 用户数据访问对象

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    private UserDao userDao;
    
    public UserServiceImpl(@Autowired UserDao userDao) {
        this.userDao = userDao;
    }

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

修改UserController类

package com.sakurapaid.spring6.autowired.controller;

import com.sakurapaid.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserController {

    /*@Autowired // 自动注入UserService实例,以便在控制器中使用
    private UserService userService;*/

    // UserService的引用,用于进行用户相关的操作
    /*private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }*/

    private UserService userService;
    
    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }

    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

测试输出


形参注入的优势在于,它可以在创建对象的同时确保依赖对象的有效性,有助于保持对象的完整性,并且可以通过构造函数强制要求必须提供所需依赖,增强了代码的清晰性和安全性。

这种方式下,@Autowired注解直接放在了构造函数的参数上。以下是形参注入的原理过程:

  1. Spring容器初始化
    当Spring容器加载配置并准备创建UserController Bean时,它会检测到类的构造函数中有带有@Autowired注解的参数。
  2. 依赖查找与注入
    Spring会查找IoC容器中类型匹配的UserService Bean。一旦找到匹配的Bean,Spring会将该Bean实例作为参数传递给UserController的构造函数,进而完成对UserService依赖的注入。
  3. 对象实例化
    通过构造函数注入依赖,Spring容器在创建UserController实例的同时完成了所有必要的依赖初始化。因此,新创建的UserController实例就已经具有了完整功能,可以直接调用UserService的方法。
  4. 测试阶段
    在测试类中,从Spring容器获取到的UserController实例,其内部的userService成员变量已经被正确注入,因此可以成功执行out()方法,依次调用UserServiceUserDao的相关方法,并最终在控制台输出预期的结果。

3.4.5. 只有一个构造函数,无注解

当有参数的构造方法只有一个时,@Autowired注解可以省略

修改UserServiceImpl类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

   /* @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;*/

    /*private UserDao userDao; // 用户数据访问对象

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    /*private UserDao userDao;

    public UserServiceImpl(@Autowired UserDao userDao) {
        this.userDao = userDao;
    }*/

    private UserDao userDao;

    public UserServiceImpl( UserDao userDao) {
        this.userDao = userDao;
    }
    
    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

修改UserController类

package com.sakurapaid.spring6.autowired.controller;

import com.sakurapaid.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserController {

    /*@Autowired // 自动注入UserService实例,以便在控制器中使用
    private UserService userService;*/

    // UserService的引用,用于进行用户相关的操作
    /*private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }*/

    /*private UserService userService;

    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }*/

    private UserService userService;

    public UserController( UserService userService) {
        this.userService = userService;
    }

    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

测试输出


上面的操作是移除了构造函数参数上的@Autowired注解。尽管如此,Spring依然能够进行依赖注入,这是因为:

  1. Spring容器初始化
    当Spring容器加载配置并初始化Bean时,它会查找所有带有@Controller等注解的类以创建Bean实例。
  2. 自动装配
    即使没有在构造函数参数上明确使用@Autowired注解,Spring也能基于类型匹配进行自动装配。当Spring容器创建UserController Bean时,它会发现UserController构造函数需要一个UserService类型的参数。此时,Spring会在IoC容器中寻找类型匹配的UserService Bean。
  3. 依赖注入
    若Spring找到了唯一的UserService Bean,则会将该Bean实例注入到UserController的构造函数中,从而完成依赖注入。
  4. 对象实例化与测试
    构造函数注入依然有效,创建出来的UserController实例已经包含了完整的UserService依赖。因此,在测试阶段,可以从Spring容器获取到具有完全功能的UserController实例,并能成功调用UserServiceout()方法。

需要注意的是,虽然在本例中移除@Autowired注解不影响依赖注入,但如果存在多个同类型候选Bean时,Spring无法确定具体要注入哪个Bean,这时就需要恢复使用@Autowired注解配合其他策略(如@Qualifier注解)来指定确切的Bean。在只有一个匹配Bean的情况下,默认的类型匹配足以完成注入任务。

说明:有多个构造方法时呢?大家可以测试(再添加一个无参构造函数),测试报错


3.4.6. @Autowired@Qualifier联合注解

添加dao层实现--UserDaoRedisImpl

package com.sakurapaid.spring6.autowired.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoRedisImpl implements UserDao {
    @Override
    public void print() {
        System.out.println("Redis Dao层执行结束");
    }
}

此时测试输出会报错,报错提示很长我截取关键的

因为@Autowired 默认是按照ByType,按照类型进行装配

所以这里需要byName,根据名称进行装配了。

修改UserServiceImpl类

package com.sakurapaid.spring6.autowired.service;

import com.sakurapaid.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

   /* @Autowired // 自动注入UserDao,以便于执行数据库操作
    private UserDao userDao;*/

    /*private UserDao userDao; // 用户数据访问对象

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    /*private UserDao userDao;

    public UserServiceImpl(@Autowired UserDao userDao) {
        this.userDao = userDao;
    }*/

    @Autowired

    //value值默认小写
    @Qualifier(value = "userDaoRedisImpl")
    private UserDao userDao;
    
    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

测试输出


通过@Autowired@Qualifier注解组合使用,我们可以更精确地控制依赖注入的行为,使其不仅依据类型匹配,还能根据Bean的名称进行精准注入,解决了可能存在多个同类型候选Bean时的注入问题。在本例中,Spring会根据名称"userDaoRedisImpl"找到并注入对应的UserDao实现类。

使用了@Autowired结合@Qualifier注解来进行按名称装配(ByName)的依赖注入。原理过程如下:

  1. Spring容器初始化
    当Spring容器加载配置并初始化Bean时,它会扫描带有@Service等注解的类,准备创建UserServiceImpl Bean。
  2. 按名称装配
    UserServiceImpl类中,UserDao成员变量同时使用了@Autowired@Qualifier注解。@Autowired注解告诉Spring需要自动装配一个UserDao类型的依赖,而@Qualifier(value = "userDaoRedisImpl")则指定了具体的Bean名称,要求Spring根据名称去查找并注入对应的UserDao实现类。
  3. 依赖查找与注入
    Spring容器在IoC容器中查找名称为"userDaoRedisImpl"的UserDao Bean。如果找到,则将该Bean实例注入到UserServiceImpluserDao属性上。
  4. 测试阶段
    当从Spring容器获取UserServiceImpl实例时,其内部的userDao属性已经被注入了指定名称的UserDao实现类实例。因此,在调用out()方法时,会调用到指定名称所对应的具体实现类的方法。

3.4.7. 总结

场景一:属性注入

  • 在类的成员变量上直接使用@Autowired注解,Spring会自动查找并注入与该成员变量类型匹配的Bean。
@Autowired
private UserDao userDao;

场景二:set注入

  • 在setter方法上使用@Autowired注解,Spring会在Bean实例化后调用该方法,注入匹配类型的Bean。
@Autowired
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

场景三:构造方法注入

  • 在类的构造函数上使用@Autowired注解,Spring会在创建Bean实例时通过构造函数注入依赖。
@Autowired
public UserServiceImpl(UserDao userDao) {
    this.userDao = userDao;
}

场景四:形参上注入

  • 这与场景三类似,只是注解直接放在构造函数的参数上,Spring会根据类型自动注入。
public UserController(@Autowired UserService userService) {
    this.userService = userService;
}

场景五:只有一个构造函数,无注解

  • 如果类中只有一个无参或有参构造函数,且无任何注解,Spring在某些情况下仍能通过类型匹配自动注入依赖(前提是IoC容器中只有一个匹配类型的Bean)。

场景六:@Autowired注解和@Qualifier注解联合

  • 当存在多个同类型候选Bean时,单独使用@Autowired可能会导致不确定注入哪一个Bean。此时配合@Qualifier注解,通过指定Bean的名称来精准注入。
@Autowired
@Qualifier(value = "userDaoRedisImpl")
private UserDao userDao;

通过以上场景,我们可以看出@Autowired注解在不同位置和场景下的作用,主要是帮助Spring容器自动管理和注入Bean的依赖关系,实现低耦合、高内聚的设计目标。同时,结合@Qualifier注解能够更精细地控制注入行为,解决多实例选择问题。


3.5. 实验二:@Resource注入

  1. @Resource注解 :
    • 来源于Java EE规范(JSR-250),在Java EE环境中通常无需额外引入依赖就能使用,但在非Java EE环境如Java SE中,需添加如上所述的依赖包。
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>
    • 默认注入策略为按名称(byName)。若在注解中未指定name属性,它会尝试匹配字段名或setter方法名作为Bean的名称进行注入;若无法按名称找到匹配的Bean,则会尝试按类型(byType)进行注入。
    • 通常应用于字段或setter方法上,例如:
@Resource(name = "userDaoRedisImpl")
private UserDao userDao;

  1. @Autowired注解 :
    • 是Spring框架提供的注解,专门用于Spring环境下的依赖注入。
    • 默认注入策略为按类型(byType),即Spring容器会查找并注入与被注解字段或方法参数类型相匹配的Bean。若存在多个相同类型的候选Bean,则需要配合@Qualifier注解明确指定Bean的名称。
    • 可以应用于字段、setter方法、构造方法以及构造方法参数上,例如:
@Autowired
private UserService userService;

// 或者配合@Qualifier
@Autowired
@Qualifier("userServiceDBImpl")
private UserService userService;

简而言之,@Resource@Autowired都能实现依赖注入,但默认的注入策略不同。@Resource倾向于按名称注入,而@Autowired倾向于按类型注入。在具体使用时,开发者可以根据项目的实际情况和需求来选择合适的注入方式。


3.5.1. 根据name注入

项目结构

修改UserDaoImpl类

package com.sakurapaid.spring6.resource.dao;

import org.springframework.stereotype.Repository;

@Repository("myUserDao") // 标示一个数据库访问层的实现类
public class UserDaoImpl implements UserDao {
    /**
     * 打印信息,表示Dao层操作已经结束。
     * 此方法没有参数。
     * 也没有返回值。
     */
    @Override
    public void print() {
        System.out.println("Dao层执行结束...");
    }
}

修改UserServiceImpl类

package com.sakurapaid.spring6.resource.service;

import com.sakurapaid.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("myService") // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

    @Resource(name = "myUserDao")
    private UserDao userDao;

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        userDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

测试输出

package com.sakurapaid.spring6.resource;

import com.sakurapaid.spring6.resource.controller.UserControllers;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 用户相关测试类
 */
public class UserTest {
    
    /**
     * 测试方法,用于验证 UserController 的功能。
     * 该方法不接受参数,也不返回任何值。
     */
    @Test
    public void test() {
        // 创建 Spring 应用上下文,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        // 从上下文中获取 UserController 的实例
        UserControllers userControllers = context.getBean(UserControllers.class, "myController");

        // 调用 UserController 的方法进行测试
        userControllers.out();
    }
}


在这个例子中,我们看到UserServiceImpl类中的UserDao依赖是通过@Resource注解并指定name="myUserDao"进行注入的。

以下是根据名称注入的原理过程:

  1. Spring容器初始化
    当通过ClassPathXmlApplicationContext创建Spring应用上下文并加载配置文件bean.xml时,Spring开始扫描并注册所有符合Spring Bean定义规则的类。
  2. 识别资源注入
    Spring在初始化UserServiceImpl Bean时,会发现UserDao成员变量上使用了@Resource注解,并指定了名称为myUserDao
  3. 依赖查找
    根据@Resource注解中指定的名称myUserDao,Spring容器会在已注册的Bean定义中查找名称匹配的Bean。若在bean.xml或其他自动扫描的组件中有一个Bean定义的id或name为myUserDao,并且它的类型与UserDao一致或兼容,那么Spring将会找到这个Bean。
  4. 依赖注入
    找到匹配的Bean后,Spring容器将其实例注入到UserServiceImpluserDao属性中,完成依赖注入。
  5. 测试阶段
    在测试类UserTest中,我们创建了一个Spring应用上下文,并通过getBean方法根据名称"myController"获取UserControllers实例。假设UserControllers内部也通过@Resource@Autowired注解正确注入了UserService实例,那么当调用userControllers.out()方法时,UserService中的out方法会被执行,其中包含从UserDao注入的对象执行的数据库操作。

综上所述,根据名称注入的过程就是Spring容器根据注解中指定的名称在IoC容器中查找匹配的Bean实例,并将其注入到相应属性的过程。在这里,UserServiceImpl中的UserDao依赖就是通过名称myUserDao成功注入的。


3.5.2. name未知注入

当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。

修改UserDaoImpl类

package com.sakurapaid.spring6.resource.dao;

import org.springframework.stereotype.Repository;

@Repository("myuserDao") // 标示一个数据库访问层的实现类
public class UserDaoImpl implements UserDao {
    /**
     * 打印信息,表示Dao层操作已经结束。
     * 此方法没有参数。
     * 也没有返回值。
     */
    @Override
    public void print() {
        System.out.println("Dao层执行结束...");
    }
}

修改UserServiceImpl类

这里并没有给@Resource进行name指定

package com.sakurapaid.spring6.resource.service;

import com.sakurapaid.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("myService") // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao myuserDao;

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        myuserDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

但测试输出一样能成功

当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。


3.5.3. 其他情况

如果上面两种情况都不是,没有指定name,而且根据属性名也找不到

那么就会根据类名ByType进行注入

修改UserControllers类

package com.sakurapaid.spring6.resource.controller;

import com.sakurapaid.spring6.resource.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;

@Controller("myController") // 表示这是一个控制器类,用于处理用户相关的HTTP请求
public class UserControllers {

    //根据名字进行注入
    /*@Resource(name = "myService")
    private UserService userService;*/

    //根据类型进行注入
    @Resource
    private UserService userService;

    public UserControllers(UserService userService) {
        this.userService = userService;
    }

    /**
     * 调用UserService中的out方法,然后在控制台打印结束信息。
     * 这个方法没有参数和返回值,主要用于演示。
     */
    public void out() {
        userService.out(); // 调用UserService的out方法
        System.out.println("Controller层执行结束...");
    }

}

修改UserServiceImpl类

package com.sakurapaid.spring6.resource.service;

import com.sakurapaid.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("myService") // 标示一个Spring框架的服务组件
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao myuserDao;

    /**
     * 执行数据库操作,并在操作完成后输出提示信息。
     */
    @Override
    public void out() {
        myuserDao.print(); // 执行UserDao的print方法,通常用于打印数据库信息或其他操作
        System.out.println("Service层执行结束..."); // 输出服务层执行结束的提示信息
    }
}

这里的@Resource没有指定name,并且属性名userService和myService也对不上

所以这里就根据UserService这个类进行注入

得到的结果是一样的


3.6. Spring全注解开发

全注解开发是指在Spring框架中,我们不再使用传统的XML配置文件来定义Bean和配置组件扫描等,而是通过编写Java配置类来替代。


比如以前写的spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--
    开启组件扫描,让Spring容器自动发现和装配标注了相应注解的Bean。
    参数:
    base-package:指定需要扫描的包路径,Spring会在此路径及其子包下查找组件。
    -->
    <context:component-scan base-package="com.sakurapaid.spring6"/>
    
</beans>

现在用一个类文件完成,这是一样的效果

package com.sakurapaid.spring6.resource.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.sakurapaid.spring6.resource")
public class ResourceConfig {
    
}

测试输出

package com.sakurapaid.spring6.resource;

import com.sakurapaid.spring6.resource.config.ResourceConfig;
import com.sakurapaid.spring6.resource.controller.UserControllers;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 用户相关测试类
 */
public class UserTest {

    /**
     * 测试方法,用于验证 UserController 的功能。
     * 该方法不接受参数,也不返回任何值。
     */
    @Test
    public void test() {
        // 创建 Spring 应用上下文,加载配置文件--用xml文件方式
        //ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        
        // 创建 Spring 应用上下文,加载配置文件--用注解类方式
        ApplicationContext context = new AnnotationConfigApplicationContext(ResourceConfig.class);
        
        // 从上下文中获取 UserController 的实例
        UserControllers userControllers = context.getBean(UserControllers.class);

        // 调用 UserController 的方法进行测试
        userControllers.out();
    }
}


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

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

相关文章

flex属性详解

flex布局&#xff0c;父元素属性可参考&#xff1a;flex布局 &#xff0c;本文主要介绍flex添加到子元素的属性。 <div class"father"><div class"left"></div><div class"middle"></div><div class"midd…

【01】htmlcssgit网络基础知识

一、html&css 防脱发神器 一图胜千言 使用border-box控制尺寸更加直观,因此,很多网站都会加入下面的代码 * {margin: 0;padding: 0;box-sizing: border-box; }颜色的 alpha 通道 颜色的 alpha 通道标识了色彩的透明度,它是一个 0~1 之间的取值,0 标识完全透明,1…

香港科技大学(广州)先进材料学域智能制造学域可持续能源与环境学域博士招生宣讲会——北京航空航天大学专场(暨全额奖学金政策)

香港科技大学&#xff08;广州&#xff09;先进材料学域&智能制造学域&可持续能源与环境学域博士招生宣讲会——北京航空航天大学专场&#xff08;暨全额奖学金政策&#xff09; 三个学域代表教授亲临现场&#xff0c;面对面答疑解惑助攻申请&#xff01;可带简历现场…

恒驰喜讯 | 亮相华为中国合作伙伴大会2024,荣膺最佳服务一致性奖等3大奖项

3月14日至15日&#xff0c;华为中国合作伙伴大会2024在深圳隆重召开。大会以“因聚而生&#xff0c;数智有为”为主题&#xff0c;面向数智化转型的浪潮&#xff0c;华为携手伙伴共同探讨如何通过强化“伙伴华为”体系&#xff0c;帮助客户抓住数智化转型的巨大机遇&#xff0c…

python-pandas基础学习

可参考&#xff1a; pandas&#xff1a;http://pandas.pydata.org/docs/user_guide/10min.html 一、基础知识 DataFrame 方法&#xff0c;可以将一组数据&#xff08;ndarray、series, map, list, dict 等类型&#xff09;转化为表格型数据 import pandas as pd data {name: …

第十五届蓝桥杯模拟考试III_物联网设计与开发官方代码分析

目录 前言&#xff1a;显示界面部分&#xff1a;页面切换:数值的轮回调整&#xff1a;传递数据&#xff1a; 前言&#xff1a; 这次模拟的效果很不好。85分&#xff0c;4h的限时我花了两天完成&#xff0c;这个时间是远远超出要求的&#xff0c;而且最后还只拿到56分&#xff0…

Java使用Selenium实现自动化测试以及全功能爬虫

前言 工作中需要抓取一下某音频网站的音频&#xff0c;我就用了两个小时学习弄了一下&#xff0c;竟然弄出来&#xff0c;这里分享记录一下。 springboot项目 Selenium Java使用Selenium实现自动化测试以及全功能爬虫 前言1 自动化测试2 java中集成Selenium3 添加浏览器驱动4…

什么是大型语言模型(LLM)?

大型语言模型 (LLM) 是一种能够理解和生成人类语言文本的机器学习模型。它们通过分析大量语言数据集来工作。 一、什么是大型语言模型 (LLM)&#xff1f; 大型语言模型 (LLM) 是一种人工智能 (AI) 程序&#xff0c;它可以识别和生成文本以及完成其他任务。LLM 经过了庞大的数据…

Xilinx LVDS ISERDESE2

ISERDESE2 7 系列 FPGA 是一款专用的串行到并行转换器,具有特定的时钟和逻辑功能,旨在促进高速源同步应用的实现。该ISERDESE2避免了在FPGA架构中设计解串器时遇到的额外时序复杂性. ISERDESE2功能包括: 1,专用解串器/串行转换器 ISERDESE2解串器可实现高速数据传输,而无需…

ssh免密登陆更换目标主机后无法连接

在进行hadoop分布式环境搭建时&#xff08;三台机&#xff0c;master&#xff0c;slave1&#xff0c;slave2&#xff09;&#xff0c;后期slave2系统出现问题&#xff0c;更换新机后&#xff0c;master与slave2文件传输失败&#xff1a; 以为是秘钥过期的问题&#xff0c;更换…

【好书推荐2】AI提示工程实战:从零开始利用提示工程学习应用大语言模型

【好书推荐2】AI提示工程实战&#xff1a;从零开始利用提示工程学习应用大语言模型 写在最前面AI辅助研发方向一&#xff1a;AI辅助研发的技术进展方向二&#xff1a;行业应用案例方向三&#xff1a;面临的挑战与机遇方向四&#xff1a;未来趋势预测方向五&#xff1a;与法规的…

动态规划(算法竞赛、蓝桥杯)--单调队列优化修建草坪

1、B站视频链接&#xff1a;E44 单调队列优化DP 修剪草坪_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; typedef long long LL; const int N1e510; int n,k,q[N]; LL w[N],f[N],sum;int main(){cin>>n>>k; k; //for(int i1;i<n;i){ci…

爬虫技术实战案例解析

目录 前言 案例背景 案例实现 案例总结 结语 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊爬虫技术实战案例解析&#xff0c;希望大家能觉得实用&#xff01; 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1…

【FPGA】摄像头模块OV5640

本篇文章包含的内容 一、OV5640简介1.1 基本概述1.2 工作时序1.2.1 DVP Timing&#xff08;数据传输时序&#xff09;1.2.2 帧曝光工作模式 1.3 OV5640 闪光灯工作模式1.3.1 Xenon Flash&#xff08;氙灯闪烁&#xff09;模式1.3.2 LED 1&2 模式1.3.3 LED 3模式1.3.4 手动开…

milvus安装

milvus安装 sudo curl -L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose- $ (uname -s)- $ (uname -m)” -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose sudo ln -s /usr/local/bin/docker-compose /usr/bin/…

Leetcode992-K个不同整数的子数组[两种方法] 关键词 滑窗

文章目录 题目方法一&#xff1a;滑窗右端每次1&#xff0c;左端来回滑动方法二&#xff1a;&#xff08;最多K种的子串数&#xff09; - &#xff08;最多K-1种的子串数&#xff09; 恰好K种 题目 1 < nums.length < 20000 1 < nums[i], k < nums.length 方法一…

Pytest 教程:从 0 到 1 搭建 Pytest 接口自动化测试项目

从 0 到 1 搭建 Pytest 接口自动化测试项目 1.创建项目目录 mkdir Pytest-API-Testing-Demo 2.项目初始化 // 进入项目文件夹下cd Pytest-API-Testing-Demo// 创建项目 python 项目虚拟环境python -m venv .env// 启用项目 python 项目虚拟环境source .env/bin/activate 3…

【InternLM 笔记】OpenXLAB浦源的基本操作

OpenXLab网址 网址&#xff1a;OpenXLab浦源 模型 创建模型 页面右上角选择【创建】然后选择【创建模型】 创建模型的页面如下 感觉页面中的提示信息填写相应的内容&#xff0c;全部填完后点页面下方的【立即创建】完成模型的创建 模型上传 安装所需的工具 apt install …

最全参赛指南!2024 年(第 17 届)中国大学生计算机设计大赛大数据主题赛现已开赛

2024 年&#xff08;第 17 届&#xff09;中国大学生计算机设计大赛大数据主题赛“数据解读乡村发展”赛题已于和鲸平台正式开赛&#xff0c;一个月来&#xff0c;全国已有超百所高校近千位优秀本科生积极响应大赛号召完成报名。 为进一步助力广大师生顺利参赛&#xff0c;和鲸…

RabbitMQ问题

如何实现顺序消费&#xff1f; 消息放入到同一个队列中消费 如何解决消息不丢失&#xff1f; 方案&#xff1a; 如上图&#xff1a;消息丢失有三种情况&#xff0c;解决了以上三种情况就解决了丢失的问题 1、丢失1--->消息在到达交换机的时候&#xff1b;解决&#xff1…