【Spring6】1-12章源码级深入详解 IoC

一、Spring启示录

阅读以下代码:

package com.powernode.oa.controller;

import com.powernode.oa.service.UserService;
import com.powernode.oa.service.impl.UserServiceImpl;

public class UserController {
   
   

    private UserService userService = new UserServiceImpl();

    public void login(){
   
   
        String username = "admin";
        String password = "123456";
        boolean success = userService.login(username, password);
        if (success) {
   
   
            // 登录成功
        } else {
   
   
            // 登录失败
        }
    }
}

package com.powernode.oa.service.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;
import com.powernode.oa.dao.impl.UserDaoImplForMySQL;
import com.powernode.oa.service.UserService;

public class UserServiceImpl implements UserService {
   
   

    private UserDao userDao = new UserDaoImplForMySQL();

    public boolean login(String username, String password) {
   
   
        User user = userDao.selectByUsernameAndPassword(username, password);
        if (user != null) {
   
   
            return true;
        }
        return false;
    }
}

package com.powernode.oa.dao.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;

public class UserDaoImplForMySQL implements UserDao {
   
   
    public User selectByUsernameAndPassword(String username, String password) {
   
   
        // 连接MySQL数据库,根据用户名和密码查询用户信息
        return null;
    }
}

可以看出,UserDaoImplForMySQL中主要是连接MySQL数据库进行操作。如果更换到Oracle数据库上,则需要再提供一个UserDaoImplForOracle,如下:

package com.powernode.oa.dao.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;

public class UserDaoImplForOracle implements UserDao {
   
   
    public User selectByUsernameAndPassword(String username, String password) {
   
   
        // 连接Oracle数据库,根据用户名和密码查询用户信息
        return null;
    }
}

很明显,以上的操作正在进行功能的扩展,添加了一个新的类UserDaoImplForOracle来应付数据库的变化,这里的变化会引起连锁反应吗?当然会,如果想要切换到Oracle数据库上,UserServiceImpl类代码就需要修改,如下:

package com.powernode.oa.service.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;
import com.powernode.oa.dao.impl.UserDaoImplForOracle;
import com.powernode.oa.service.UserService;

public class UserServiceImpl implements UserService {
   
   

    //private UserDao userDao = new UserDaoImplForMySQL();
    private UserDao userDao = new UserDaoImplForOracle();

    public boolean login(String username, String password) {
   
   
        User user = userDao.selectByUsernameAndPassword(username, password);
        if (user != null) {
   
   
            return true;
        }
        return false;
    }
}

1.1 OCP开闭原则

这样一来就违背了开闭原则OCP。开闭原则是这样说的:在软件开发过程中应当对扩展开放,对修改关闭。也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太高。如下图所示:
image.png
可以很明显的看出,上层是依赖下层的。UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySQL,这样就会导致下面只要改动上面必然会受牵连(跟着也会改),所谓牵一发而动全身。这样也就同时违背了另一个开发原则:依赖倒置原则。

1.2 依赖倒置原则DIP

在这里插入图片描述

依赖倒置原则(DIP原则)

依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务
你可能会说,上面的代码已经面向接口编程了呀:
image.png

确实已经面向接口编程了,但对象的创建是:new UserDaoImplForOracle()显然并没有完全面向接口编程,还是使用到了具体的接口实现类。什么叫做完全面向接口编程?什么叫做完全符合依赖倒置原则呢?请看以下代码:
image.png
如果代码是这样编写的,才算是完全面向接口编程,才符合依赖倒置原则。那你可能会问,这样userDao是null,在执行的时候就会出现空指针异常呀。你说的有道理,确实是这样的,所以我们要解决这个问题。解决空指针异常的问题,其实就是解决两个核心的问题:

  • 第一个问题:谁来负责对象的创建。【也就是说谁来:new UserDaoImplForOracle()/new UserDaoImplForMySQL()】
  • 第二个问题:谁来负责把创建的对象赋到这个属性上。【也就是说谁来把上面创建的对象赋给userDao属性】

如果我们把以上两个核心问题解决了,就可以做到既符合OCP开闭原则,又符合依赖倒置原则。
很荣幸的通知你:Spring框架可以做到。
在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。换句话说,Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。比如:
image.png
Spring可以new出来UserDaoImplForMySQL对象,也可以new出来UserDaoImplForOracle对象,并且还可以让new出来的dao对象和service对象产生关系(产生关系其实本质上就是给属性赋值)。
很显然,这种方式是将对象的创建权/管理权交出去了,不再使用硬编码的方式了。同时也把对象关系的管理权交出去了,也不再使用硬编码的方式了。像这种把对象的创建权交出去,把对象关系的管理权交出去,被称为控制反转。

1.3 控制反转IoC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护
控制反转常见的实现方式:依赖注入(Dependency Injection,简称DI)
通常,依赖注入的实现又包括两种方式:

  • set方法注入
  • 构造方法注入

而Spring框架就是一个实现了IoC思想的框架。
IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。(GoF指的是23种设计模式)

对象和对象之间的关系的维护权:即UserServiceImpl中UserDao userDao的具体实现是UserDaoImplForMySQL还是UserDaoImplForOracle

public class UserServiceImpl implements UserService {
   
   
    //private UserDao userDao = new UserDaoImplForMySQL();

    // 修改为
    //private UserDao userDao = new UserDaoImplForOracle();

    private UserDao userDao;

    @Override
    public void deleteUser() {
   
   
        userDao.deleteById();
    }
}

1.4 课堂笔记

1. OCP开闭原则
    * 什么是OCP?
        OCP是软件七大开发原则当中最基本的一个原则:开闭原则,即开放封闭原则(Open Closed Principle)
        对什么开?对扩展开放。
        对什么闭?对修改关闭。
    * OCP原则是最核心的,最基本的,其他的六个原则都是为这个原则服务的。
    * OCP开闭原则的核心是什么?
        只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合OCP原则的。
        反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背OCP原则。
    * 当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试。这是不想看到的,因为非常麻烦。

2. 依赖倒置原则(DIP原则)
    * 什么是依赖倒置原则?
        面向接口编程,面向抽象编程,不要面向具体编程。
    * 依赖倒置原则的目的?
        降低程序的耦合度,提高扩展力。
    * 什么叫做符合依赖倒置?
        上 不依赖 下,就是符合。
    * 什么叫做违背依赖倒置?
        上 依赖 下,就是违背。
        只要“下”一改动,“上”就受到牵连。

3. 当前程序的设计,显然既违背OCP,又违背DIP,怎么办?
    可以采用“控制反转”这种编程思想来解决这个问题。

4. 什么是控制反转?
    控制反转:IoCInversion of Control)
    反转是什么呢?
        反转的是两件事:
            第一件事:我不在程序中采用硬编码的方式来new对象了。(new对象我不管了,new对象的权利交出去了。)
            第二件事:我不在程序中采用硬编码的方式来维护对象的关系了。(对象之间关系的维护权,我也不管了,交出去了。)

    控制反转:是一种编程思想。或者叫做一种新型的设计模式。由于出现的比较新,没有被纳入GoF23种设计模式范围内。

5. Spring框架
    * Spring框架实现了控制反转IoC这种思想
        Spring框架可以帮你new对象。
        Spring框架可以帮你维护对象和对象之间的关系。
    * Spring是一个实现了IoC思想的容器。
    * 控制反转的实现方式有多种,其中比较重要的叫做:依赖注入(Dependency Injection,简称DI)* 控制反转是思想。依赖注入是这种思想的具体实现。
    * 依赖注入DI,又包括常见的两种方式:
        第一种:set注入(执行set方法给属性赋值)
        第二种:构造方法注入(执行构造方法给属性赋值)
    * 依赖注入 中 “依赖”是什么意思? “注入”是什么意思?
        依赖:A对象和B对象的关系。
        注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。
        依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入。
6. 注意术语:
    OCP:开闭原则(开发原则)
    DIP:依赖倒置原则(开发原则)
    IoC:控制反转(一种思想,一种新型的设计模式)
    DI:依赖注入(控制反转思想的具体实现方式)

二、Spring概述

2.1 Spring简介

image.png
来自百度百科

Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。
从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
Spring最初的出现是为了解决EJB臃肿的设计,以及难以测试等问题。
Spring为简化开发而生,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。

2.2 Spring8大模块

注意:Spring5版本之后是8个模块。在Spring5中新增了WebFlux模块。
image.png

  1. Spring Core模块

这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。它使用IoC将应用配置和依赖从实际的应用代码中分离出来。

  1. Spring Context模块

如果说核心模块中的BeanFactory使Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。
这个模块扩展了BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。另外提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持

  1. Spring AOP模块

Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。

  1. Spring DAO模块

提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。

  1. Spring ORM模块

Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

  1. Spring Web MVC模块

Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。

  1. Spring WebFlux模块

Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。
image.png

  1. Spring Web模块

Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。还提供了一些面向服务支持,例如:实现文件上传的multipart请求。

2.3 Spring特点

  1. 轻量
    1. 从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。
    2. Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。(Spring框架的运行不依赖于任何别的框架)
  2. 控制反转
    1. Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
  3. 面向切面
    1. Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
  4. 容器
    1. Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
  5. 框架
    1. Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。

所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。

2.4 本教程软件版本

  • IDEA工具:2022.1.4
  • JDK:Java17 (Spring6要求JDK最低版本是Java17)
  • Maven:3.8.6
  • Spring:6.0.0-M2
  • JUnit:4.13.2

三、Spring的入门程序

3.1 Spring的下载

官网地址:https://spring.io/
image.png
官网地址(中文):http://spring.p2hp.com/
image.png
打开Spring官网后,可以看到Spring Framework,以及通过Spring Framework衍生的其它框架:
image.png
我们即将要学习的就是Spring Framework。
怎么下载呢?

  • 第一步:进入github

image.png

  • 第二步:找到下图位置,点击超链接

image.png

  • 第三步:找到下图位置,点击超链接

image.png

  • 第四步:按照下图步骤操作

image.png

  • 第五步:继续在springframework目录下找下图的spring,点开之后你会看到很多不同的版本

image.png

  • 第六步:选择对应的版本

image.png

  • 第七步:点击上图的url

image.png
点击spring-5.3.9-dist.zip下载spring框架。
将下载的zip包解压:
image.png
docs:spring框架的API帮助文档
libs:spring框架的jar文件(用spring框架就是用这些jar包
schema:spring框架的XML配置文件相关的约束文件

3.2 Spring的jar文件

打开libs目录,会看到很多jar包:
image.png
spring-core-5.3.9.jar:字节码(这个是支撑程序运行的jar包
spring-core-5.3.9-javadoc.jar:代码中的注释
spring-core-5.3.9-sources.jar:源码
我们来看一下spring框架都有哪些jar包:
image.png

JAR文件 描述
spring-aop-5.3.9.jar 这个jar 文件包含在应用中使用Spring 的AOP 特性时所需的类
spring-aspects-5.3.9.jar 提供对AspectJ的支持,以便可以方便的将面向切面的功能集成进IDE中
spring-beans-5.3.9.jar 这个jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion ofControl / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。
spring-context-5.3.9.jar 这个jar 文件为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。
spring-context-indexer-5.3.9.jar 虽然类路径扫描非常快,但是Spring内部存在大量的类,添加此依赖,可以通过在编译时创建候选对象的静态列表来提高大型应用程序的启动性能。
spring-context-support-5.3.9.jar 用来提供Spring上下文的一些扩展模块,例如实现邮件服务、视图解析、缓存、定时任务调度等
spring-core-5.3.9.jar Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
spring-expression-5.3.9.jar Spring表达式语言。
spring-instrument-5.3.9.jar Spring3.0对服务器的代理接口。
spring-jcl-5.3.9.jar Spring的日志模块。JCL,全称为"Jakarta Commons Logging",也可称为"Apache Commons Logging"。
spring-jdbc-5.3.9.jar Spring对JDBC的支持。
spring-jms-5.3.9.jar 这个jar包提供了对JMS 1.0.2/1.1的支持类。JMS是Java消息服务。属于JavaEE规范之一。
spring-messaging-5.3.9.jar 为集成messaging api和消息协议提供支持
spring-orm-5.3.9.jar Spring集成ORM框架的支持,比如集成hibernate,mybatis等。
spring-oxm-5.3.9.jar 为主流O/X Mapping组件提供了统一层抽象和封装,OXM是Object Xml Mapping。对象和XML之间的相互转换。
spring-r2dbc-5.3.9.jar Reactive Relational Database Connectivity (关系型数据库的响应式连接) 的缩写。这个jar文件是Spring对r2dbc的支持。
spring-test-5.3.9.jar 对Junit等测试框架的简单封装。
spring-tx-5.3.9.jar 为JDBC、Hibernate、JDO、JPA、Beans等提供的一致的声明式和编程式事务管理支持。
spring-web-5.3.9.jar Spring集成MVC框架的支持,比如集成Struts等。
spring-webflux-5.3.9.jar WebFlux是 Spring5 添加的新模块,用于 web 的开发,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
spring-webmvc-5.3.9.jar SpringMVC框架的类库
spring-websocket-5.3.9.jar Spring集成WebSocket框架时使用

注意:
如果你只是想用Spring的IoC功能,仅需要引入:spring-context即可。将这个jar包添加到classpath当中。
如果采用maven只需要引入context的依赖即可。

<!--Spring6的正式版发布之前,这个仓库地址是需要的-->
<repositories>
  <repository>
    <id>repository.spring.milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
  </repository>
</repositories>

<dependencies>
  <!--spring context依赖:使用的是6.0.0-M2里程碑版-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0-M2</version>
  </dependency>
</dependencies>

3.3 第一个Spring程序

前期准备

  • 打开IDEA创建Empty Project:spring6

image.png

  • 设置JDK版本17,编译器版本17

image.png

  • 设置IDEA的Maven:关联自己的maven

image.png

  • 在空的工程spring6中创建第一个模块:spring6-001-first

image.png
第一步:添加spring context的依赖,pom.xml配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode</groupId>
    <artifactId>spring6-001-first</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <!--spring context依赖-->
        <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
        <!--如果,你想使用spring的jdbc,或者说其他的tx,那么还需要再次添加依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

注意:打包方式jar。
当加入spring context的依赖之后,会关联引入其他依赖:
spring aop:面向切面编程
spring beans:IoC核心
spring core:spring的核心工具包
spring jcl:spring的日志包
spring expression:spring表达式
image.png
第二步:添加junit依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode</groupId>
    <artifactId>spring6-001-first</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <!--spring context依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

第三步:定义bean:User

package com.powernode.spring6.bean;

/**
 * bean,封装用户信息。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class User {
   
   
}

第四步:编写spring的配置文件:beans.xml。该文件放在类的根路径下。
image.png
上图是使用IDEA工具自带的spring配置文件的模板进行创建。
配置文件中进行bean的配置。

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

    <!--这就是Spring的配置文件-->
    <!--IDEA工具为我们提供了这个文件的模板,一定要使用这个模板来创建。-->
    <!--这个文件名不一定叫做spring.xml,可以是其它名字。-->
    <!--这个文件最好是放在类路径当中,方便后期的移植。-->
    <!--放在resources根目录下,就相当于是放到了类的根路径下。-->
    <!--配置bean,这样spring才可以帮助我们管理这个对象。-->
    <!--
        bean标签的两个重要属性:
            id:是这个bean的身份证号,不能重复,是唯一的标识。
            class:必须填写类的全路径,全限定类名。(带包名的类名)
    -->
    <bean id="userBean" class="com.powernode.spring6.bean.User"/>

    <!--<bean id="userBean" class="com.powernode.spring6.bean.Vip"/>-->

    <!--配置其他的bean-->
    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDaoImplForMySQL"/>

    <!--配置java.util.Date Bean-->
    <bean id="nowTime" class="java.util.Date"/>
    
</beans>

bean的id和class属性:

  • id属性:代表对象的唯一标识。可以看做一个人的身份证号。
  • class属性:用来指定要创建的java对象的类名,这个类名必须是全限定类名(带包名)。

第五步:编写测试程序

package com.powernode.spring6.test;

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

public class Spring6Test {
   
   

    @Test
    public void testFirst(){
   
   
        // 第一步:获取Spring容器对象。初始化Spring容器上下文(解析beans.xml文件,创建所有的bean对象)
        // ApplicationContext 翻译为:应用上下文。其实就是Spring容器。
        // ApplicationContext 是一个接口。
        // ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext
        // ClassPathXmlApplicationContext 专门从类路径当中加载spring配置文件的一个Spring上下文对象。
        // 这行代码只要执行:就相当于启动了Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        // 第二步:根据bean的id从Spring容器中获取这个对象。
        Object userBean = applicationContext.getBean("userBean");
        System.out.println(userBean);
    }
}

第七步:运行测试程序
image.png

3.4 第一个Spring程序详细剖析

<bean id="userBean" class="com.powernode.spring6.bean.User"/>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userBean = applicationContext.getBean("userBean");
  1. bean标签的id属性可以重复吗?
package com.powernode.spring6.bean;

/**
 * @author 动力节点
 * @version 1.0
 * @className Vip
 * @since 1.0
 **/
public class Vip {
   
   
}

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

    <bean id="userBean" class="com.powernode.spring6.bean.User"/>
    <bean id="userBean" class="com.powernode.spring6.bean.Vip"/>
</beans>

运行测试程序:
image.png
通过测试得出:在spring的配置文件中id是不能重名。

  1. 底层是怎么创建对象的,是通过反射机制调用无参数构造方法吗?
package com.powernode.spring6.bean;

/**
 * bean,封装用户信息。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class User {
   
   
	// Spring是怎么实例化对象的?
    // 默认情况下Spring会通过反射机制,调用类的无参数构造方法来实例化对象。
    // 实现原理如下:
    // Class clazz = Class.forName("com.powernode.spring6.bean.User");
    // Object obj = clazz.newInstance();
    public User() {
   
   
        System.out.println("User的无参数构造方法执行");
    }
}

在User类中添加无参数构造方法,如上。
运行测试程序:
image.png
通过测试得知:创建对象时确实调用了无参数构造方法。
如果提供一个有参数构造方法,不提供无参数构造方法会怎样呢?

package com.powernode.spring6.bean;

/**
 * bean,封装用户信息。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class User {
   
   
    /*public User() {
        System.out.println("User的无参数构造方法执行");
    }*/

    public User(String name){
   
   
        System.out.println("User的有参数构造方法执行");
    }
}

运行测试程序:
image.png
通过测试得知:spring是通过调用类的无参数构造方法来创建对象的,所以要想让spring给你创建对象,必须保证无参数构造方法是存在的。
Spring是如何创建对象的呢?原理是什么?

// dom4j解析beans.xml文件,从中获取class的全限定类名
// 通过反射机制调用无参数构造方法创建对象
Class clazz = Class.forName("com.powernode.spring6.bean.User");
Object obj = clazz.newInstance();
  1. 把创建好的对象存储到一个什么样的数据结构当中了呢?

image.png

  1. spring配置文件的名字必须叫做beans.xml吗?
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

通过以上的java代码可以看出,这个spring配置文件名字是我们负责提供的,显然spring配置文件的名字是随意的。

  1. 像这样的beans.xml文件可以有多个吗?

再创建一个spring配置文件,起名:spring.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">
    <bean id="vipBean" class="com.powernode.spring6.bean.Vip"/>
</beans>
package com.powernode.spring6.test;

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

public class Spring6Test {
   
   

    @Test
    public void testFirst(){
   
   
        // 第一步:获取Spring容器对象。初始化Spring容器上下文(解析beans.xml文件,创建所有的bean对象)
        // ApplicationContext 翻译为:应用上下文。其实就是Spring容器。
        // ApplicationContext 是一个接口。
        // ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext
        // ClassPathXmlApplicationContext 专门从类路径当中加载spring配置文件的一个Spring上下文对象。
        // 这行代码只要执行:就相当于启动了Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
        //ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
        //ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml", "xml/beans.xml");

        // 第二步:根据bean的id从Spring容器中获取这个对象。
        Object userBean = applicationContext.getBean("userBean");
        Object vipBean = applicationContext.getBean("vipBean");
        Object userBean2 = applicationContext.getBean("userBean2");

        System.out.println(userBean);
        System.out.println(vipBean);
        System.out.println(userBean2);
    }
}

运行测试程序:
image.png
通过测试得知,spring的配置文件可以有多个,在ClassPathXmlApplicationContext构造方法的参数上传递文件路径即可。这是为什么呢?通过源码可以看到:
image.png

  1. 在配置文件中配置的类必须是自定义的吗,可以使用JDK中的类吗,例如:java.util.Date?
<?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">

    <bean id="userBean" class="com.powernode.spring6.bean.User"/>
    <!--<bean id="userBean" class="com.powernode.spring6.bean.Vip"/>-->

	<!--配置java.util.Date Bean-->
    <bean id="dateBean" class="java.util.Date"/>
</beans>
package com.powernode.spring6.test;

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

public class Spring6Test {
   
   

    @Test
    public void testFirst(){
   
   
        // 初始化Spring容器上下文(解析beans.xml文件,创建所有的bean对象)
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml","spring6.xml");

        // 根据id获取bean对象
        Object userBean = applicationContext.getBean("userBean");
        Object vipBean = applicationContext.getBean("vipBean");
        Object dateBean = applicationContext.getBean("dateBean");

        System.out.println(userBean);
        System.out.println(vipBean);
        System.out.println(dateBean);
    }
}

运行测试程序:
image.png
通过测试得知,在spring配置文件中配置的bean可以任意类,只要这个类不是抽象的,并且提供了无参数构造方法。

  1. getBean()方法调用时,如果指定的id不存在会怎样?

image.png
运行测试程序:
image.png
通过测试得知,当id不存在的时候,会出现异常。

  1. getBean()方法返回的类型是Object,如果访问子类的特有属性和方法时,还需要向下转型,有其它办法可以解决这个问题吗?
// 不想强制类型转换,可以使用以下代码(通过第二个参数来指定返回的bean的类型。)
User user = applicationContext.getBean("userBean", User.class);
  1. ClassPathXmlApplicationContext是从类路径中加载配置文件,如果没有在类路径当中,又应该如何加载配置文件呢?

image.png

<?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">
    <bean id="vipBean2" class="com.powernode.spring6.bean.Vip"/>
</beans>
@Test
public void testXmlPath(){
   
   

    // FileSystemXmlApplicationContext 不是从类路径当中加载资源。
    // 这种方式很少用。了解即可。
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("d:/spring6.xml");
    User user = applicationContext.getBean("userBean", User.class);
    System.out.println(user);

}

没有在类路径中的话,需要使用FileSystemXmlApplicationContext类进行加载配置文件。
这种方式较少用。一般都是将配置文件放到类路径当中,这样可移植性更强。

  1. ApplicationContext的超级父接口BeanFactory。

Spring底层的IoC是怎么实现的?XML解析 + 工厂模式 + 反射机制

@Test
public void testBeanFactory(){
   
   
    //ApplicationContext接口的超级父接口是:BeanFactory(翻译为Bean工厂,就是能够生产Bean对象的一个工厂对象。)
    //BeanFactory是IoC容器的顶级接口。
    //Spring的IoC容器底层实际上使用了:工厂模式。
    //Spring底层的IoC是怎么实现的?XML解析 + 工厂模式 + 反射机制
    //ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
    BeanFactory applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
    User user = applicationContext.getBean("userBean", User.class);
    System.out.println(user);
}

BeanFactory是Spring容器的超级接口。ApplicationContext是BeanFactory的子接口。
在这里插入图片描述

  1. 开始初始化bean的时机
@Test
public void testBeginInitBean(){
   
   
    // 注意:不是在调用getBean()方法的时候创建对象,执行以下代码的时候,就会实例化对象。
    new ClassPathXmlApplicationContext("spring6.xml");
}

3.5 Spring6启用Log4j2日志框架

从Spring5之后,Spring框架支持集成的日志框架是Log4j2.如何启用日志框架:
第一步:引入Log4j2的依赖

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

第二步:在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。)

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </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>
    </appenders>

</configuration>

第三步:使用日志框架

public class FirstSpringTest {
   
   

    @Test
    public void testBeginInitBean(){
   
   
        // 注意:不是在调用getBean()方法的时候创建对象,执行以下代码的时候,就会实例化对象。
        new ClassPathXmlApplicationContext("spring6.xml");

        // 你自己怎么去使用log4j2记录日志信息呢?
        // 第一步:创建日志记录器对象
        // 获取FirstSpringTest类的日志记录器对象,也就是说只要是FirstSpringTest类中的代码执行记录日志的话,就输出相关的日志信息。
        Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);

        // 第二步:记录日志,根据不同的级别来输出日志
        logger.info("我是一条消息");
        logger.debug("我是一条调试信息");
        logger.error("我是一条错误信息");
    }
    
}

四、Spring对IoC的实现

4.1 IoC 控制反转

  • 控制反转是一种思想。
  • 控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则。
  • 控制反转,反转的是什么?
    • 将对象的创建权利交出去,交给第三方容器负责。
    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责。
  • 控制反转这种思想如何实现呢?
    • DI(Dependency Injection):依赖注入

4.2 依赖注入

依赖注入实现了控制反转的思想。
Spring通过依赖注入的方式来完成Bean管理的。
Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
依赖注入:

  • 依赖指的是对象和对象之间的关联关系。
  • 注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系。

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

新建模块:spring6-003-dependency-injection

4.2.1 set注入

set注入,基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.powernode</groupId>
    <artifactId>spring6-002-dependency-injection</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>
package com.powernode.spring6.dao;

/**
 * @author 动力节点
 * @version 1.0
 * @className UserDao
 * @since 1.0
 **/
public class UserDao {
   
   

    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    public void insert(){
   
   
        //System.out.println("数据库正在保存用户信息。");
        // 使用一下log4j2日志框架
        logger.info("数据库正在保存用户信息。");
    }
}
package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;

/**
 * @author 动力节点
 * @version 1.0
 * @className UserService
 * @since 1.0
 **/
public class UserService {
   
   

    private UserDao userDao;
    private VipDao vipDao;

    public void setAbc(VipDao vipDao){
   
   
        this.vipDao = vipDao;
    }

    // set注入的话,必须提供一个set方法。
    // Spring容器会调用这个set方法,来给userDao属性赋值。
    // 我自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。
    // 至少这个方法是以set单词开始的。前三个字母不能随便写,必须是“set"
    /*public void setMySQLUserDao(UserDao xyz){
        this.userDao = xyz;
    }*/


    // 这个set方法是IDEA工具生成的,符合javabean规范。
    public void setUserDao(UserDao userDao) {
   
   
        this.userDao = userDao;
    }

    public void saveUser(){
   
   
        // 保存用户信息到数据库
        userDao.insert();
        vipDao.insert();
    }
}
<?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">

    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao" ref="userDaoBean"/>
    </bean>

</beans>
package com.powernode.spring6.test;

import com.powernode.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 动力节点
 * @version 1.0
 * @className DITest
 * @since 1.0
 **/
public class DITest {
   
   

    @Test
    public void testSetDI(){
   
   
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userServiceBean", UserService.class);
        userService.saveUser();
    }
}

运行结果:
image.png
重点内容是,什么原理:

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

    <!--配置dao-->
    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

    <!--配置service-->
    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <!-- 想让Spring调用对应的set方法,需要配置property标签 -->
        <!-- name属性怎么指定值:set方法的方法名,去掉set,然后把剩下的单词首字母变小写,写到这里。-->
        <!-- ref翻译为引用。英语单词:references。ref后面指定的是要注入的bean的id。-->
        <!--<property name="mySQLUserDao" ref="userDaoBean"/>-->

        <!--set方法起名的时候,不要为难自己,按照规范来。所以一般情况下name位置写属性名就行了。-->
        <property name="userDao" ref="userDaoBean"/>

        <!--<property name="vipDao" ref="vipDaoBean"/>-->
        <property name="abc" ref="vipDaoBean"/>

    </bean>

    <bean id="vipDaoBean" class="com.powernode.spring6.dao.VipDao"/>

</beans>

实现原理:
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名。
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
可以把set方法注释掉,再测试一下
image.png
通过测试得知,底层实际上调用了setUserDao()方法。所以需要确保这个方法的存在。
我们现在把属性名修改一下,但方法名还是setUserDao(),我们来测试一下:

package com.powernode.spring6.service;

import com.powernode.spring6.dao.UserDao;

/**
 * @author 动力节点
 * @version 1.0
 * @className UserService
 * @since 1.0
 **/
public class UserService {
   
   

    private UserDao aaa;

    // 使用set方式注入,必须提供set方法。
    // 反射机制要调用这个方法给属性赋值的。
    public void setUserDao(UserDao userDao) {
   
   
        this.aaa = userDao;
    }

    public void save(){
   
   
        aaa.insert();
    }
}

运行测试程序:
image.png
通过测试看到程序仍然可以正常执行,说明property标签的name是:setUserDao()方法名演变得到的。演变的规律是:

  • setUsername() 演变为 username
  • setPassword() 演变为 password
  • setUserDao() 演变为 userDao
  • setUserService() 演变为 userService

另外,对于property标签来说,ref属性也可以采用标签的方式,但使用ref属性是多数的:

<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
  <property name="userDao">
    <ref bean="userDaoBean"/>
  </property>
</bean>

总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。

4.2.2 构造注入

核心原理:通过调用构造方法来给属性赋值。

package com.powernode.spring6.dao;

/**
 * @author 动力节点
 * @version 1.0
 * @className OrderDao
 * @since 1.0
 **/
public class OrderDao {
   
   
    public void deleteById(){
   
   
        System.out.println("正在删除订单。。。");
    }
}
package com.powernode.spring6.service;

import com.powernode.spring6.dao.OrderDao;

/**
 * @author 动力节点
 * @version 1.0
 * @className OrderService
 * @since 1.0
 **/
public class OrderService {
   
   
    private OrderDao orderDao;

    // 通过反射机制调用构造方法给属性赋值
    public OrderService(OrderDao orderDao) {
   
   
        this.orderDao = orderDao;
    }

    public void delete(){
   
   
        orderDao.deleteById();
    }
}
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
  <!--index="0"表示构造方法的第一个参数,将orderDaoBean对象传递给构造方法的第一个参数。-->
  <constructor-arg index="0" ref="orderDaoBean"/>
</bean>
@Test
public void testConstructorDI(){
   
   
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class);
    orderServiceBean.delete();
}

运行结果如下:
image.png
如果构造方法有两个参数:

package com.powernode.spring6.service;

import com.powernode.spring6.dao.OrderDao;
import com.powernode.spring6.dao.UserDao;

/**
 * @author 动力节点
 * @version 1.0
 * @className OrderService
 * @since 1.0
 **/
public class OrderService {
   
   
    private OrderDao orderDao;
    private UserDao userDao;

    // 通过反射机制调用构造方法给属性赋值
    public OrderService(OrderDao orderDao, UserDao userDao) {
   
   
        this.orderDao = orderDao;
        this.userDao = userDao;
    }

    public void delete(){
   
   
        orderDao.deleteById();
        userDao.insert();
    }
}

spring配置文件:

<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>

<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
  <!--第一个参数下标是0-->
  <constructor-arg index="0" ref="orderDaoBean"/>
  <!--第二个参数下标是1-->
  <constructor-arg index="1" ref="userDaoBean"/>
</bean>

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

执行测试程序:
image.png
不使用参数下标,使用参数的名字可以吗?

<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>

<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
  <!--这里使用了构造方法上参数的名字-->
  <constructor-arg name="orderDao" ref="orderDaoBean"/>
  <constructor-arg name="userDao" ref="userDaoBean"/>
</bean>

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

执行测试程序:
image.png
不指定参数下标,不指定参数名字,可以吗?

<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
  <!--没有指定下标,也没有指定参数名字-->
  <!--这种方式实际上是根据类型进行注入的。spring会自动根据类型来判断把ref注入给哪个参数。-->
  <constructor-arg ref="orderDaoBean"/>
  <constructor-arg ref="userDaoBean"/>
</bean>

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

执行测试程序:
image.png
配置文件中构造方法参数的类型顺序和构造方法参数的类型顺序不一致呢?(spring可以判断匹配)

<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>

<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
  <!--顺序已经和构造方法的参数顺序不同了-->
  <constructor-arg ref="userDaoBean"/>
  <constructor-arg ref="orderDaoBean"/>
</bean>

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

执行测试程序:
image.png
通过测试得知,通过构造方法注入的时候:

  • 可以通过下标
  • 可以通过参数名
  • 也可以不指定下标和参数名,可以类型自动推断。

Spring在装配方面做的还是比较健壮的。

4.3 set注入专题

4.3.1 注入外部Bean

在之前4.2.1中使用的案例就是注入外部Bean的方式。

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

    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao" ref="userDaoBean"/>
    </bean>

</beans>

外部Bean的特点:bean定义到外面,在property标签中使用ref属性进行注入。通常这种方式是常用。

4.3.2 注入内部Bean

内部Bean的方式:在bean标签中嵌套bean标签。

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

    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao">
            <bean class="com.powernode.spring6.dao.UserDao"/>
        </property>
    </bean>

</beans>
@Test
public void testInnerBean(){
   
   
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-inner-bean.xml");
    UserService userService = applicationContext.getBean("userServiceBean", UserService.class);
    userService.save();
}

执行测试程序:
image.png
这种方式作为了解。

4.3.3 注入简单类型

我们之前在进行注入的时候,对象的属性是另一个对象。

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

那如果对象的属性是int类型呢?

public class User{
   
   
    
    private int age;
    
    public void setAge(int age){
   
   
        this.age = age;
    }
    
}

可以通过set注入的方式给该属性赋值吗?

  • 当然可以。因为只要能够调用set方法就可以给属性赋值。

编写程序给一个User对象的age属性赋值20:
第一步:定义User类,提供age属性,提供age属性的setter方法。

package com.powernode.spring6.beans;

/**
 * @author 动力节点
 * @version 1.0
 * @className User
 * @since 1.0
 **/
public class User {
   
   
    private int age;

    public void setAge(int age) {
   
   
        this.age = age;
    }
    
    @Override
    public String toString() {
   
   
        return "User{" +
                "age=" + age +
                '}';
    }
}

第二步:编写spring配置文件:spring-simple-type.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">
    <bean id="userBean" class="com.powernode.spring6.beans.User">
        <!--如果像这种int类型的属性,我们称为简单类型,这种简单类型在注入的时候要使用value属性,不能使用ref-->
        <!--<property name="age" value="20"/>-->
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

第三步:编写测试程序

@Test
public void testSimpleType(){
   
   
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-simple-type.xml");
    User user = applicationContext.getBean("userBean", User.class);
    System.out.println(user);
}

第四步:运行测试程序
1664444974497(1).png
需要特别注意:如果给简单类型赋值,使用value属性或value标签。而不是ref。
简单类型包括哪些呢?可以通过Spring的源码来分析一下:BeanUtils类

public class BeanUtils{
   
   
    
    //.......
    
    /**
	 * Check if the given type represents a "simple" property: a simple value
	 * type or an array of simple value types.
	 * <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple
	 * value type</em>.
	 * <p>Used to determine properties to check for a "simple" dependency-check.
	 * @param type the type to check
	 * @return whether the given type represents a "simple" property
	 * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
	 * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
	 * @see #isSimpleValueType(Class)
	 */
	public static boolean isSimpleProperty(Class<?> type) {
   
   
		Assert.notNull(type, "'type' must not be null");
		return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
	}

	/**
	 * Check if the given type represents a "simple" value type: a primitive or
	 * primitive wrapper, an enum, a String or other CharSequence, a Number, a
	 * Date, a Temporal, a URI, a URL, a Locale, or a Class.
	 * <p>{@code Void} and {@code void} are not considered simple value types.
	 * @param type the type to check
	 * @return whether the given type represents a "simple" value type
	 * @see #isSimpleProperty(Class)
	 */
	public static boolean isSimpleValueType(Class<?> type) {
   
   
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) || //java.util.Date是简单类型
				Temporal.class.isAssignableFrom(type) || //Temporal是Java8提供的时间和时区类型
				URI.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开五档的蒙奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值