Spring6 - IOC(基于注解)
从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。
Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。
Spring 通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义 Bean
- 依赖注入
# 1. 搭建子模块
搭建方式如:Spring6-XML-xml。
引入配置文件 Spring-XML-xml 模块日志 log4j2.xml。
添加依赖
<dependencies>
<!--Spring context 依赖-->
<!--当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了-->
<dependency>
<groupId>org.Springframework</groupId>
<artifactId>Spring-context</artifactId>
<version>6.0.3</version>
</dependency>
<!--junit5 测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<!--log4j2 的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2. 开启组件扫描
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。
<?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">
<!--开启组件扫描功能-->
<context:component-scan base-package="cn.youngkbt.Spring6"></context:component-scan>
</Beans>
2
3
4
5
6
7
8
9
10
11
注意:在使用 context:component-scan 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 <Beans>
中添加 context 相关的约束。
情况一:最基本的扫描方式
<context:component-scan base-package="cn.youngkbt.Spring6">
</context:component-scan>
2
情况二:指定要排除的组件
<context:component-scan base-package="cn.youngkbt.Spring6">
<!-- context:exclude-filter 标签:指定排除规则 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression 中设置要排除的注解的全类名
type="assignable",根据类型排除,expression 中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation" expression="org.Springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="cn.youngkbt.Spring6.controller.UserController"/>-->
</context:component-scan>
2
3
4
5
6
7
8
9
10
情况三:仅扫描指定组件
<context:component-scan base-package="cn.youngkbt" use-default-filters="false">
<!-- context:include-filter 标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters 属性:取值 false 表示关闭默认扫描规则 -->
<!-- 此时必须设置 use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression 中设置要排除的注解的全类名
type="assignable",根据类型排除,expression 中设置要排除的类型的全类名
-->
<context:include-filter type="annotation" expression="org.Springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable" expression="cn.youngkbt.Spring6.controller.UserController"/>-->
</context:component-scan>
2
3
4
5
6
7
8
9
10
11
12
# 3. 使用注解定义 Bean
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 相同。 |
# 4. @Autowired注入
单独使用 @Autowired
注解,默认根据类型装配。【默认是 byType】
查看源码:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
源码中有两处需要注意:
第一处是
@Autowired
的应用位置
@Autowired
注解可以被应用在多个位置,以便于实现灵活的依赖注入,具体包括:
- 构造函数(Constructor):当标注在构造函数上时,Spring容器创建类的实例的同时,会自动注入构造函数中参数所需的bean。
- 方法(Method):可以标注在任意方法上,Spring容器会在bean的创建过程中,自动调用该方法,方法参数所需的bean会被自动注入。
- 参数(Parameter):当标注在方法的参数上时,指示Spring自动按类型为该参数提供bean。
- 字段(Field):标注在类的字段上,Spring会自动按类型为这些字段赋值,无需通过setter方法。
- 注解(AnnotationType):也可以标注在另一个注解上,这允许创建自定义注解,组合
@Autowired
的功能。
第二处是
@Autowired
的required
属性
@Autowired
注解有一个required
属性,这个属性影响了Spring容器注入依赖时的行为:
required = true
(默认值):表示注入的bean是必需的,如果Spring容器中没有找到匹配的bean,则在启动时会抛出异常。这确保了依赖项的强制性和bean的完整性。required = false
:如果设置为false
,则Spring容器在找不到匹配的bean时,不会抛出异常,相应的字段或参数将不被设置(保持为null
)。这对于某些可选的依赖注入场景非常有用。
# 4.1 属性注入
在这个场景中,我们通过Spring框架的@Autowired
注解实现了层次间的依赖注入,包括数据访问层(DAO)、业务逻辑层(Service)、以及表示层(Controller)。下面是各部分的详细解释和代码注释:
1. 数据访问层(DAO)
UserDao 接口:定义了数据访问层的操作接口。
public interface UserDao {
// 打印操作,模拟数据访问层的操作
public void print();
}
2
3
4
UserDaoImpl 实现类:实现了UserDao
接口,提供了具体的数据访问逻辑。
import cn.youngkbt.Spring6.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository // 标识为数据访问组件,Spring将其纳入管理作为DAO组件
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
2. 业务逻辑层(Service)
UserService 接口:定义了业务逻辑层的操作接口。
public interface UserService {
// 执行输出操作,模拟业务逻辑层的操作
public void out();
}
2
3
4
UserServiceImpl 实现类:实现了UserService
接口,其中通过@Autowired
注解自动注入了UserDao
的实现类UserDaoImpl
。
import cn.youngkbt.Spring6.dao.UserDao;
import cn.youngkbt.Spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 标识为业务逻辑组件,Spring将其纳入管理作为Service组件
public class UserServiceImpl implements UserService {
@Autowired // 自动按类型注入UserDao的实现类UserDaoImpl
private UserDao userDao;
@Override
public void out() {
userDao.print(); // 调用DAO层的方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3. 表现层(Controller)
UserController 类:表示层的组件,其中通过@Autowired
注解自动注入了UserService
的实现类UserServiceImpl
。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller // 标识为表现层组件,Spring将其纳入管理作为Controller组件
public class UserController {
@Autowired // 自动按类型注入UserService的实现类UserServiceImpl
private UserService userService;
public void out() {
userService.out(); // 调用Service层的方法
System.out.println("Controller层执行结束。");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试用例
展示如何在Spring环境下获取UserController
组件并调用其方法,验证了@Autowired
注解在Spring中的依赖注入功能。
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
private Logger logger = LoggerFactory.getLogger(UserTest.class);
@Test
public void testAnnotation() {
// 加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
// 从Spring容器中获取UserController组件
UserController userController = context.getBean("userController", UserController.class);
// 调用UserController的out方法
userController.out();
logger.info("执行成功");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
测试结果:
以上构造方法和 setter 方法都没有提供,经过测试,仍然可以注入成功。
# 4.2 set 注入
通过Spring框架的@Autowired
注解实现了依赖的setter注入,这种方式提供了更多的灵活性,允许在setter方法上使用@Autowired
注解,从而实现依赖的自动注入。这种方法对于想要保持类的封装性,同时又希望通过Spring自动管理依赖注入的场景非常适合。
修改后的UserServiceImpl类
UserServiceImpl类中,我们通过setter方法来注入UserDao的依赖,而不是直接在字段上使用@Autowired
。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 标记为Service层组件,由Spring管理
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired // 通过setter方法注入UserDao
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
修改后的UserController类
UserController类同样采用setter方法来注入UserService的依赖。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller // 标记为Controller层组件,由Spring管理
public class UserController {
private UserService userService;
@Autowired // 通过setter方法注入UserService
public void setUserService(UserService userService) {
this.userService = userService;
}
public void out() {
userService.out(); // 调用UserService的out方法
System.out.println("Controller层执行结束。");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
测试用例
测试用例保持不变,用于验证通过setter注入依赖后的组件是否能正确工作。
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
private Logger logger = LoggerFactory.getLogger(UserTest.class);
@Test
public void testAnnotation() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.out(); // 调用UserController的out方法,触发依赖调用链
logger.info("执行成功");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.3 构造方法注入
采用构造方法注入的方式来实现依赖的自动注入,这是一种更加推荐的依赖注入方法,特别是在需要注入的依赖是必须的情况下。通过构造方法注入,可以在对象创建时就完成所有必要的依赖注入,确保了bean的完整性和不可变性。
UserServiceImpl类 - 构造方法注入
在UserServiceImpl
类中,我们通过构造方法来注入UserDao
的依赖,使用@Autowired
注解来指示Spring自动按类型进行装配。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 标记为Service层组件,由Spring管理
public class UserServiceImpl implements UserService {
private final UserDao userDao;
@Autowired // 构造方法注入UserDao
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao; // 通过构造方法完成依赖的注入
}
@Override
public void out() {
userDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UserController类 - 构造方法注入
在UserController
类中,我们同样通过构造方法来注入UserService
的依赖,并使用@Autowired
注解。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller // 标记为Controller层组件,由Spring管理
public class UserController {
private final UserService userService;
@Autowired // 构造方法注入UserService
public UserController(UserService userService) {
this.userService = userService; // 通过构造方法完成依赖的注入
}
public void out() {
userService.out(); // 调用UserService的out方法
System.out.println("Controller层执行结束。");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
测试用例
测试用例用于验证通过构造方法注入依赖后的组件是否能正确工作,保持不变。
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
private Logger logger = LoggerFactory.getLogger(UserTest.class);
@Test
public void testAnnotation() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.out(); // 调用UserController的out方法,触发依赖调用链
logger.info("执行成功");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
总结
通过构造方法注入的方式,Spring在创建bean的实例时自动按类型装配依赖,这种方式不仅可以保证依赖的及时装配,而且还能保持bean的不可变性和完整性。这是因为一旦通过构造方法创建了对象实例,其依赖就不可更改,从而确保了bean状态的一致性。构造方法注入是实现依赖注入的一种非常优雅且推荐的方式,特别适用于必须的依赖关系。
# 4.4 形参上注入
通过在构造函数的形参上使用@Autowired
注解来实现依赖注入,这种方式允许直接在构造函数的参数上声明依赖关系,使得Spring能够在创建bean实例时自动按类型注入所需的依赖。这种方法结合了构造方法注入的明确性和自动装配的便利性。
修改后的UserServiceImpl类 - 形参注入
在UserServiceImpl
类中,我们通过构造函数的形参上使用@Autowired
注解来注入UserDao
的依赖,这表明当Spring创建UserServiceImpl
实例时,它将自动按类型注入一个UserDao
实例。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 标记为Service层组件,由Spring管理
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 在构造函数的形参上使用@Autowired注解来实现依赖的自动注入
public UserServiceImpl(@Autowired UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
修改后的UserController类 - 形参注入
在UserController
类中,我们采用了相同的方法通过构造函数的形参上使用@Autowired
注解来注入UserService
的依赖。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller // 标记为Controller层组件,由Spring管理
public class UserController {
private UserService userService;
// 在构造函数的形参上使用@Autowired注解来实现依赖的自动注入
public UserController(@Autowired UserService userService) {
this.userService = userService;
}
public void out() {
userService.out(); // 调用UserService的out方法
System.out.println("Controller层执行结束。");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
测试用例
测试用例用于验证通过在构造函数的形参上使用@Autowired
注解来实现依赖注入后的组件是否能正确工作,保持不变。
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
private Logger logger = LoggerFactory.getLogger(UserTest.class);
@Test
public void testAnnotation() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.out(); // 调用UserController的out方法,触发依赖调用链
logger.info("执行成功");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.5 只有一个构造函数,无注解
修改后的UserServiceImpl类
在UserServiceImpl
类中,我们移除了构造函数上的@Autowired
注解。由于UserServiceImpl
只有一个带参数的构造函数,Spring会自动使用这个构造函数进行依赖注入。
import org.springframework.stereotype.Service;
@Service // 将UserServiceImpl标记为Spring管理的bean
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 即使没有@Autowired注解,Spring也会自动注入UserDao的实现
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
测试通过
当有参数的构造方法只有一个时,@Autowired 注解可以省略。
说明:有多个构造方法时呢?大家可以测试(再添加一个无参构造函数),测试报错。
总结
在Spring中,如果一个类只有一个有参数的构造函数,可以省略构造函数上的@Autowired
注解,Spring将自动按类型装配构造函数的参数。这个特性简化了Spring的配置,同时也保持了代码的清晰和简洁。但是,如果存在多个构造函数,为了避免歧义,推荐显式地使用@Autowired
注解来标注用于依赖注入的构造函数。这样可以确保Spring容器正确地理解开发者的意图,并按预期工作。
# 4.6 @Autowired 注解和 @Qualifier 注解联合
在Spring框架中,@Autowired
注解主要用于自动按类型装配依赖,而@Qualifier
注解则用于进一步指定bean的名称,以解决自动按类型装配时可能出现的歧义问题,即当存在多个相同类型的bean时,指明具体装配哪一个bean。下面通过一个示例来展示如何结合使用@Autowired
和@Qualifier
注解来解决自动装配中的歧义问题。
# 场景设定
假设我们的应用中需要访问用户数据,这些数据可以存储在不同的地方,例如MySQL数据库或Redis。我们定义了一个UserDao
接口及其两个实现:UserDaoImpl
(用于MySQL)和UserDaoRedisImpl
(用于Redis)。
添加第二个UserDao实现类
为了模拟从Redis获取用户数据的场景,我们添加了一个新的UserDao
实现类UserDaoRedisImpl
。
import org.springframework.stereotype.Repository;
@Repository // 将UserDaoRedisImpl标记为Spring管理的bean
public class UserDaoRedisImpl implements UserDao {
@Override
public void print() {
System.out.println("Redis Dao层执行结束");
}
}
2
3
4
5
6
7
8
9
10
出现的问题
当Spring尝试自动装配UserDao
依赖到UserServiceImpl
时,由于现在有两个UserDao
的实现,Spring容器无法决定使用哪一个,导致异常。
解决方案:使用@Qualifier
注解
为了解决这个歧义,我们可以使用@Qualifier
注解来指定要装配的具体实现类的bean名称。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service // 将UserServiceImpl标记为Spring管理的bean
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDaoImpl") // 使用@Qualifier注解指定要装配的bean的名称
private UserDao userDao;
@Override
public void out() {
userDao.print();
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关键点总结
@Autowired
注解:默认按类型进行自动装配。如果同一类型有多个bean,需要配合@Qualifier
注解一起使用以避免歧义。@Qualifier
注解:用于指定bean的名称,与@Autowired
注解结合使用,明确指明要装配哪个具体的bean。- 构造方法注入:当类中只有一个带参数的构造方法时,
@Autowired
可以省略。但为了明确表示依赖注入的意图,通常建议保留@Autowired
注解。 - 解决歧义:当存在多个同类型的bean时,通过
@Qualifier
注定明确指定要使用的bean的名称,从而解决自动装配的歧义问题。
# 5. @Resource 注入
@Resource
注解也可以完成属性注入。那它和 @Autowired
注解有什么区别?
@Resource
注解是 JDK 扩展包中的,也就是说属于 JDK 的一部分。所以该注解是标准注解,更加具有通用性(JSR-250 标准中制定的注解类型。JSR 是 Java 规范提案。)@Autowired
是Spring框架自定义的注解,主要用于实现Spring容器中bean的自动装配。@Resource
注解默认根据名称装配 byName,未指定 name 时,使用属性名作为 name。通过 name 找不到的话会自动启动通过类型 byType 装配@Autowired
注解默认根据类型装配 byType,如果想根据名称装配,需要配合@Qualifier
注解一起用@Resource
注解用在属性上、setter 方法上。@Autowired
注解用在属性上、setter 方法上、构造方法上、构造方法参数上。
@Resource
注解属于 JDK 扩展包,所以不在 JDK 当中,需要额外引入以下依赖:【如果是 JDK8 的话不需要额外引入依赖。高于 JDK11 或低于 JDK8 需要引入以下依赖】
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version> <!-- 版本根据需求填写 -->
</dependency>
2
3
4
5
源码:
package jakarta.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定注解可以应用的Java元素类型:类、字段、方法
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
// 指定注解信息在运行时仍然保留,使得可以通过反射机制读取注解信息
@Retention(RetentionPolicy.RUNTIME)
// 允许一个元素上多次使用同一个注解
@Repeatable(Resources.class)
public @interface Resource {
// 指定注入的资源的名称,默认为空字符串
String name() default "";
// 指定查找资源的名称,默认为空字符串
String lookup() default "";
// 指定资源的类型,默认为Object.class
Class<?> type() default Object.class;
// 指定资源的认证类型,默认为CONTAINER
Resource.AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;
// 指定资源是否可以共享,默认为true
boolean shareable() default true;
// 指定资源的映射名称,默认为空字符串
String mappedName() default "";
// 提供资源的描述,默认为空字符串
String description() default "";
// 定义认证类型的枚举,包括CONTAINER和APPLICATION
public static enum AuthenticationType {
CONTAINER, // 容器认证类型
APPLICATION; // 应用程序认证类型
private AuthenticationType() {
// 构造函数
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 5.1 根据 name 注入
修改后的UserDaoImpl类 - 指定Bean名称
在UserDaoImpl
类中,我们通过@Repository
注解的value
属性为这个bean指定了一个名称"myUserDao"
。这个名称将在依赖注入时被引用。
import org.springframework.stereotype.Repository;
@Repository("myUserDao") // 通过value属性为bean指定名称
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
2
3
4
5
6
7
8
9
10
修改后的UserServiceImpl类 - 根据名称注入依赖
在UserServiceImpl
类中,我们通过@Resource
注解的name
属性来指定需要注入的UserDao
实例的名称。这里,我们指定了名称为"myUserDao"
的bean,这使得Spring可以准确地找到并注入指定的UserDao
实现。
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service // 将UserServiceImpl标记为Spring管理的bean
public class UserServiceImpl implements UserService {
@Resource(name = "myUserDao") // 根据名称注入依赖
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试情况
测试显示依赖注入成功完成,UserServiceImpl
能够正常使用myUserDao
进行操作,并输出预期的信息。这证明了@Resource
注解能够根据指定的名称准确地完成依赖注入。
总结
@Resource
注解通过name
属性提供了一种基于名称的依赖注入方式,允许开发者明确指定想要注入的bean的名称。这种方法特别适用于以下情形:
- 当存在多个同类型的bean时,需要精确指定其中的某一个。
- 当希望依赖注入的配置更加清晰和直观时。
通过使用@Resource(name = "beanName")
,我们可以确保Spring容器注入正确的bean实例,从而提高应用程序的准确性和稳定性。
# 5.2 name 未知注入
该示例展示了如何使用@Resource
注解进行依赖注入时不显式指定name
属性的情况。当@Resource
注解未指定name
时,Spring会默认使用被注解的字段的名称作为bean的名称进行查找,这是基于名称的自动装配行为。如果根据字段名作为bean名称能够找到匹配的bean,则注入成功;如果找不到,则会尝试根据类型进行装配。
修改后的UserDaoImpl类
UserDaoImpl
类的定义未发生变化,仍然通过@Repository
注解将其标记为Spring管理的bean,并指定了bean的名称为"myUserDao"
。
import org.springframework.stereotype.Repository;
@Repository("myUserDao") // 指定bean名称为myUserDao
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
2
3
4
5
6
7
8
9
10
修改后的UserServiceImpl类 - 使用属性名作为bean名称注入
在UserServiceImpl
类中,@Resource
注解未指定name
属性,因此Spring将使用字段名myUserDao
作为bean名称进行查找。
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service // 将UserServiceImpl标记为Spring管理的bean
public class UserServiceImpl implements UserService {
@Resource // 未指定name,将使用字段名myUserDao作为bean名称进行查找
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print(); // 调用UserDao的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试情况
测试表明,即使@Resource
注解未显式指定name
属性,依赖注入也能成功完成。这是因为myUserDao
字段名恰好与UserDaoImpl
类通过@Repository("myUserDao")
注解指定的bean名称相匹配,从而使得依赖注入按名称成功进行。
总结
@Resource
注解在未指定name
属性时,会采用被注解的字段的名称作为bean名称进行查找,这提供了一种基于属性名的便捷装配方式。如果根据属性名能够找到对应的bean,则进行注入;如果找不到,则Spring会尝试按类型进行装配。
# 5.3 其他情况
当使用@Resource
注解进行依赖注入时,Spring首先尝试通过名称(byName)来装配依赖。如果未显式指定name
属性,Spring会将字段名作为bean的名称进行查找。如果根据名称无法找到匹配的bean,那么Spring将退回到按类型(byType)进行装配。这个过程中,如果按类型装配发现存在多个同类型的bean,将会导致异常,因为Spring无法决定使用哪个具体的bean实例。
示例:按名称未找到,尝试按类型注入导致的异常
在尝试注入userDao1
时,由于@Resource
注解未指定name
,Spring会使用属性名userDao1
作为bean的名称进行查找。如果找不到名为userDao1
的bean,Spring会尝试按类型进行注入。
修改后的UserServiceImpl类
在UserServiceImpl
类中,尝试通过@Resource
注解注入一个名为userDao1
的UserDao
类型的依赖,此属性名在Spring容器中不存在。
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service // 将UserServiceImpl标记为Spring管理的bean
public class UserServiceImpl implements UserService {
@Resource // 未指定name,将尝试使用属性名userDao1作为bean名称进行查找
private UserDao userDao1;
@Override
public void out() {
userDao1.print(); // 尝试调用userDao1的print方法
System.out.println("Service层执行结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试结果和异常分析
由于UserDao
接口下存在多个实现类,而@Resource
注解在按类型(byType)尝试注入时发现了多个实现,导致Spring无法决定注入哪一个实现,最终测试抛出异常。这种情况通常发生在一个接口有多个实现类的情况下,且在使用@Resource
按类型装配时未能通过名称精确匹配到特定的bean。
总结
@Resource
注解在进行依赖注入时遵循以下原则:
- 默认按名称(byName)进行装配。未显式指定
name
时,使用属性名作为bean名称进行查找。 - 如果按名称找不到对应的bean,将退回按类型(byType)装配。
- 按类型装配时,如果找到多个同类型的bean,将抛出异常,因为Spring无法决定使用哪一个实现。
对于需要按类型装配且存在多个实现的情况,推荐使用@Qualifier
注解与@Autowired
一起使用,或者在@Resource
中显式指定name
属性,以避免歧义和异常。这样可以明确指定需要注入的具体bean,确保依赖注入的准确性和应用程序的稳定性。
# 6. Spring 全注解开发
在Spring框架的现代开发实践中,全注解开发方式越来越受到青睐,因为它能够使配置更加简洁,开发更加灵活。全注解开发意味着不再需要传统的XML配置文件,而是通过注解和Java配置类来完成bean的注册和配置。这种方式不仅提高了配置的可读性,而且也使得配置过程更加自然和类型安全。
# Java配置类
Java配置类通过@Configuration
注解标识,表明该类是一个Spring配置类,用于替代传统的XML配置文件。在这个配置类中,可以使用Java代码定义bean和配置应用上下文。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 表示这是一个Spring配置类
@ComponentScan("cn.youngkbt.Spring6") // 指定Spring容器启动时要扫描的包,用于找到并注册bean
public class Spring6Config {
// 这里可以添加更多的Java配置代码
}
2
3
4
5
6
7
8
# 使用@ComponentScan进行包扫描
@ComponentScan
注解用于指定Spring在初始化时需要扫描的包路径,Spring将会扫描这些指定的包路径下的类,并根据@Component
及其派生注解(如@Service
、@Controller
等)自动注册bean到Spring容器中。这样,就无需手动在配置文件中声明bean了。
# 测试全注解配置
在测试类中,通过AnnotationConfigApplicationContext
类创建应用上下文,这是专门为Java配置设计的应用上下文实现。通过传递配置类Spring6Config.class
给其构造函数,Spring会根据配置类中的注解加载并注册bean。
@Test
public void testAllAnnotation() {
// 使用Java配置类初始化Spring应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
// 从Spring容器中获取userController bean
UserController userController = context.getBean("userController", UserController.class);
// 调用userController的out方法
userController.out();
logger.info("执行成功");
}
2
3
4
5
6
7
8
9
10
总结
全注解开发方式使得Spring应用的配置更加灵活和类型安全,通过使用@Configuration
和@ComponentScan
注解,开发者可以轻松地定义配置类并指定需要扫描的包路径,从而实现自动的bean注册和管理。这种方式不仅减少了配置的复杂性,而且也提高了代码的整洁性和可维护性,是现代Spring应用开发的推荐做法。