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 自动装配的基本步骤:
- 引入依赖:
在构建项目时,确保包含 Spring 相关注解处理器的依赖项,如spring-context
或spring-boot-starter
,这将使 Spring 能够识别和处理注解。 - 开启组件扫描:
在 Spring 配置类或 XML 配置文件中启用组件扫描(Component Scan),这样 Spring 容器启动时会自动检测指定包及其子包下的类,寻找带有特定注解(如@Component
,@Service
,@Repository
,@Controller
)的类并将它们作为 Bean 进行注册。 - 使用注解定义 Bean:
在需要由 Spring 管理的类上使用上述注解,表明它们是 Spring 容器中的 Bean。例如,通过在业务类上标注@Service
注解,告诉 Spring 这是一个服务层的 Bean。 - 依赖注入:
利用注解进行依赖注入,如使用@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
配置文件中添加新命名空间时,可以遵循以下步骤进行操作:
- 命名空间声明:
在XML文档头部,使用xmlns:prefix="http://www.springframework.org/schema/namespace"
格式声明新的命名空间,其中prefix
是一个自定义的、用于引用该命名空间的别名,而namespace
代表具体的Spring功能模块,例如“context”。
例如:
xmlns:context="http://www.springframework.org/schema/context"
- 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>
标签启用组件扫描功能。具体来说:
- 情况一:最基本的扫描方式
<!-- 扫描指定包下的所有组件,自动注册为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容器中。
- 情况二:指定要排除的组件
<!-- 扫描指定包下除了注解为@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容器中。
- 情况三:仅扫描指定组件
<!-- 扫描指定包下仅包含注解为@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
可以使用的场景:
- 构造函数: 当你在一个类的构造函数上添加
@Autowired
时,Spring 会在创建这个类的实例时,根据构造函数参数的类型自动找寻并注入相应的 Bean。 - 方法: 如果你在某个方法上使用
@Autowired
,Spring 会在 Bean 初始化后调用此方法,并给其参数注入对应的 Bean。 - 方法参数: 在方法的参数上使用
@Autowired
,Spring 在调用该方法前会自动根据参数类型注入相应的 Bean。 - 类成员变量: 直接在类的成员变量(字段)上使用
@Autowired
,Spring 会自动将匹配类型的 Bean 注入到这个字段中。 - 自定义注解:
@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项目,其中包含三个主要部分:
- 服务层(Service Layer):
在UserServiceImpl
类中,有一个UserDao
类型的私有成员变量userDao
,并使用了@Autowired
注解。这意味着Spring框架会自动帮我们找到并设置合适的UserDao
实例给这个变量。 - 控制层(Controller Layer):
类似地,在UserController
类中,有一个UserService
类型的私有成员变量userService
,同样使用了@Autowired
注解。这样,Spring会自动把已创建好的UserService
实例(即UserServiceImpl
的一个实例)注入到这里。 - Spring容器初始化与注入:
当通过ClassPathXmlApplicationContext
加载Spring配置文件"bean.xml"时,Spring容器开始启动并管理这些Bean。它会识别出带有@Service
和@Controller
注解的类,并创建相应的实例。同时,对于带有@Autowired
注解的成员变量,Spring会根据类型自动寻找并注入相应的Bean。 - 测试过程:
在测试类UserTest
中,我们从Spring容器中获取UserController
实例,并调用其out()
方法。由于Spring已经完成了依赖注入,UserController
里的userService
已经有实际的UserService
实现,所以可以顺利调用其方法。而在UserService
的out()
方法里,又能顺利调用到已注入的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注入的原理过程:
- Spring容器初始化:
当创建ClassPathXmlApplicationContext
并加载"bean.xml"配置文件时,Spring容器开始初始化并管理所有被标记为@Service
、@Controller
等注解的Bean。 - 扫描Bean及其依赖:
Spring容器会识别出UserServiceImpl
和UserController
类上的注解,并准备创建这两个Bean的实例。同时,Spring会检查这些类中是否存在带@Autowired
注解的setter方法。 - 依赖注入:
对于UserServiceImpl
,Spring发现setUserDao
方法上有@Autowired
注解,就会从容器中查找类型匹配的UserDao
Bean,找到后调用setUserDao
方法,将UserDao
实例注入给UserServiceImpl
。
同理,对于UserController
,Spring会找到并调用setUserService
方法,将已创建的UserService
实例(也就是UserServiceImpl
实例)注入给UserController
。 - 测试阶段:
在测试类UserTest
中,我们从Spring容器获取UserController
实例。由于Spring已经在容器初始化阶段完成了对UserController
和UserService
的依赖注入,所以在调用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
注解来完成依赖对象的注入。以下是构造方法注入的原理过程:
- Spring容器初始化:
当创建ClassPathXmlApplicationContext
并加载配置文件时,Spring容器开始扫描并管理所有的Bean。 - 识别构造器注入:
Spring容器在创建UserServiceImpl
Bean时,会发现其构造函数上有@Autowired
注解,这意味着Spring需要通过此构造函数来初始化UserServiceImpl
实例,并注入所需的UserDao
依赖。 - 注入依赖:
Spring会在IoC容器中查找类型匹配的UserDao
Bean,找到后将其实例作为参数传入UserServiceImpl
的构造函数,从而完成对UserDao
依赖的注入。
同样地,在创建UserController
Bean时,Spring发现其构造函数上有@Autowired
注解,此时Spring会查找并注入已经初始化好的UserService
Bean。 - 测试阶段:
测试类UserTest
从Spring容器获取UserController
实例时,由于Spring已在初始化阶段通过构造方法注入的方式完成了对UserController
和UserService
的依赖设置,所以调用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
注解直接放在了构造函数的参数上。以下是形参注入的原理过程:
- Spring容器初始化:
当Spring容器加载配置并准备创建UserController
Bean时,它会检测到类的构造函数中有带有@Autowired
注解的参数。 - 依赖查找与注入:
Spring会查找IoC容器中类型匹配的UserService
Bean。一旦找到匹配的Bean,Spring会将该Bean实例作为参数传递给UserController
的构造函数,进而完成对UserService
依赖的注入。 - 对象实例化:
通过构造函数注入依赖,Spring容器在创建UserController
实例的同时完成了所有必要的依赖初始化。因此,新创建的UserController
实例就已经具有了完整功能,可以直接调用UserService
的方法。 - 测试阶段:
在测试类中,从Spring容器获取到的UserController
实例,其内部的userService
成员变量已经被正确注入,因此可以成功执行out()
方法,依次调用UserService
和UserDao
的相关方法,并最终在控制台输出预期的结果。
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依然能够进行依赖注入,这是因为:
- Spring容器初始化:
当Spring容器加载配置并初始化Bean时,它会查找所有带有@Controller
等注解的类以创建Bean实例。 - 自动装配:
即使没有在构造函数参数上明确使用@Autowired
注解,Spring也能基于类型匹配进行自动装配。当Spring容器创建UserController
Bean时,它会发现UserController
构造函数需要一个UserService
类型的参数。此时,Spring会在IoC容器中寻找类型匹配的UserService
Bean。 - 依赖注入:
若Spring找到了唯一的UserService
Bean,则会将该Bean实例注入到UserController
的构造函数中,从而完成依赖注入。 - 对象实例化与测试:
构造函数注入依然有效,创建出来的UserController
实例已经包含了完整的UserService
依赖。因此,在测试阶段,可以从Spring容器获取到具有完全功能的UserController
实例,并能成功调用UserService
的out()
方法。
需要注意的是,虽然在本例中移除@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)的依赖注入。原理过程如下:
- Spring容器初始化:
当Spring容器加载配置并初始化Bean时,它会扫描带有@Service
等注解的类,准备创建UserServiceImpl
Bean。 - 按名称装配:
在UserServiceImpl
类中,UserDao
成员变量同时使用了@Autowired
和@Qualifier
注解。@Autowired
注解告诉Spring需要自动装配一个UserDao
类型的依赖,而@Qualifier(value = "userDaoRedisImpl")
则指定了具体的Bean名称,要求Spring根据名称去查找并注入对应的UserDao
实现类。 - 依赖查找与注入:
Spring容器在IoC容器中查找名称为"userDaoRedisImpl"的UserDao
Bean。如果找到,则将该Bean实例注入到UserServiceImpl
的userDao
属性上。 - 测试阶段:
当从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注入
- @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方法上,例如:
- 默认注入策略为按名称(byName)。若在注解中未指定
@Resource(name = "userDaoRedisImpl")
private UserDao userDao;
- @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"
进行注入的。
以下是根据名称注入的原理过程:
- Spring容器初始化:
当通过ClassPathXmlApplicationContext
创建Spring应用上下文并加载配置文件bean.xml
时,Spring开始扫描并注册所有符合Spring Bean定义规则的类。 - 识别资源注入:
Spring在初始化UserServiceImpl
Bean时,会发现UserDao
成员变量上使用了@Resource
注解,并指定了名称为myUserDao
。 - 依赖查找:
根据@Resource
注解中指定的名称myUserDao
,Spring容器会在已注册的Bean定义中查找名称匹配的Bean。若在bean.xml
或其他自动扫描的组件中有一个Bean定义的id或name为myUserDao
,并且它的类型与UserDao
一致或兼容,那么Spring将会找到这个Bean。 - 依赖注入:
找到匹配的Bean后,Spring容器将其实例注入到UserServiceImpl
的userDao
属性中,完成依赖注入。 - 测试阶段:
在测试类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();
}
}