程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Spring

    • Spring6 - 概述
    • Spring6 - 入门
    • Spring6 - IOC(基于XML)
    • Spring6 - IOC(基于注解)
      • spring6 - FactoryBean
      • Spring6 - Bean的作用域
      • Spring6 - Bean生命周期
      • Spring6 - Bean循环依赖
      • Spring6 - 手写IOC容器
      • Spring6 - AOP
      • Spring6 - 自定义注解
      • Spring6 - Junit
      • Spring6 - 事务
      • Spring6 - Resource
      • Spring6 - 国际化
      • Spring6 - 数据校验
      • Spring6 - Cache
      • Spring集成Swagger2
    • Spring生态
    • Spring
    scholar
    2023-10-27
    目录

    Spring6 - IOC(基于注解)

    从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

    Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。

    Spring 通过注解实现自动装配的步骤如下:

    1. 引入依赖
    2. 开启组件扫描
    3. 使用注解定义 Bean
    4. 依赖注入

    # 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>
    
    1
    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>
    
    1
    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>
    
    1
    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>
    
    1
    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>
    
    1
    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;
    }
    
    1
    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();
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    2. 业务逻辑层(Service)

    UserService 接口:定义了业务逻辑层的操作接口。

    public interface UserService {
        // 执行输出操作,模拟业务逻辑层的操作
        public void out();
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束。");
        }
    
    }
    
    1
    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("执行成功");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    测试结果:

    image-20221101153556681

    以上构造方法和 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层执行结束");
        }
    }
    
    1
    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层执行结束。");
        }
    
    }
    
    1
    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("执行成功");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束。");
        }
    }
    
    1
    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("执行成功");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束。");
        }
    }
    
    1
    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("执行成功");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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>
    
    1
    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() {
                // 构造函数
            }
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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层执行结束");
        }
    }
    
    1
    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配置代码
    }
    
    1
    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("执行成功");
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    总结

    全注解开发方式使得Spring应用的配置更加灵活和类型安全,通过使用@Configuration和@ComponentScan注解,开发者可以轻松地定义配置类并指定需要扫描的包路径,从而实现自动的bean注册和管理。这种方式不仅减少了配置的复杂性,而且也提高了代码的整洁性和可维护性,是现代Spring应用开发的推荐做法。

    编辑此页 (opens new window)
    上次更新: 2024/12/28, 18:32:08
    Spring6 - IOC(基于XML)
    spring6 - FactoryBean

    ← Spring6 - IOC(基于XML) spring6 - FactoryBean→

    Theme by Vdoing | Copyright © 2019-2025 程序员scholar
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式