动力节点Spring (1-4)

⼀、Spring启示录

dao层:

UserDao接口:

//持久层
public interface UserDao {
    //根据id删除用户信息
    void deleteById();
}
    

UserDaoImplForMySQL实现类:

public class UserDaoImplForMySQL implements UserDao {
    @Override
    public void deleteById() {
        System.out.println("Mysql数据库正在删除用户信息..........");
    }
}

service层:

UserService接口:

//业务层
public interface UserService {
    //删除用户信息
    void deleteUser();
}

UserServiceImpl实现类:

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


    @Override
    public void deleteUser() {
        //删除用户信息的逻辑
        userDao.deleteById();
    }

    public void saveUser(){
        userDao.deleteById();
    }

}

web层:

UserAction类:

//表示层
public class UserAction {
    private UserService userService = new UserServiceImpl();

    //删除用户信息的请求
    public void deleteRequest(){
        userService.deleteUser();
    }
}

client:

Test类:

public class Test {
    public static void main(String[] args) {
        UserAction userAction = new UserAction();
        userAction.deleteRequest();
    }
}
可以看出,UserDaoImplForMySQL中主要是连接MySQL数据库进⾏操作。如果更换到Oracle数据库上,则需要再提供⼀个UserDaoImplForOracle,如下:
添加UserDaoImplForOracle实现类:
public class UserDaoImplForOracle implements UserDao {
    @Override
    public void deleteById() {
        System.out.println("Oracle数据库正在删除用户数据..............");
    }
}

而且还要修改UserSerciceImpl里面的对象

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

    //private UserDao userDao = new UserDaoImplForOracle();

    @Override
    public void deleteUser() {
        //删除用户信息的逻辑
        userDao.deleteById();
    }

    public void saveUser(){
        userDao.deleteById();
    }

}
可以看出,这样⼀来就违背了开闭原则OCP。

1.1 OCP开闭原则

1. OCP开闭原则
    * 什么是OCP?
        OCP是软件七大开发原则当中最基本的一个原则:开闭原则
        对什么开?对扩展开放。
        对什么闭?对修改关闭。
    * OCP原则是最核心的,最基本的,其他的六个原则都是为这个原则服务的。
    * OCP开闭原则的核心是什么?
        只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合OCP原则的。
        反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背OCP原则。
    * 当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试。这是不想看到的,因为非常麻烦。
这样⼀来就违背了开闭原则OCP。开闭原则是这样说的:在软件开发过程中应当对扩展开放,对修改关闭。也就是说,如果在进⾏功能扩展的时候,添加额外的类是没问题的,但因为功能扩展⽽修改之前运⾏正常的程序,这是忌讳的,不被允许的。因为⼀旦修改之前运⾏正常的程序,就会导致项⽬整体要进⾏全⽅位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太⾼。如下图所示:
可以很明显的看出, 上层 是依赖 下层 的。UserController依赖UserServiceImpl,⽽UserServiceImpl依赖 UserDaoImplForMySQL,这样就会导致 下⾯只要改动 上⾯必然会受牵连(跟着也会改) ,所谓牵⼀发⽽动全身。 这样也就同时违背了另⼀个开发原则:依赖倒置原则。

1.2 依赖倒置原则DIP

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

3. 当前程序的设计,显然既违背OCP,又违背DIP,怎么办?
    可以采用“控制反转”这种编程思想来解决这个问题。
依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导⾯向抽象编程,⾯向接⼝编程,不要⾯向具体编程,让 上层 不再依赖 下层 ,下⾯改动了,上⾯的代码不会受到牵连。这样可以⼤⼤降低程序的耦合度,耦合度低了,扩展⼒就强了,同时代码复⽤性也会增强。( 软件七⼤开发原则都是在为解耦合服务
确实已经⾯向接⼝编程了,但对象的创建是:new UserDaoImplForOracle()显然并没有完全⾯向接编程,还是使⽤到了具体的接⼝实现类。什么叫做完全⾯向接⼝编程?什么叫做完全符合依赖倒置原则呢?请看以下代码:
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    //private UserDao userDao = new UserDaoImplForOracle();

    @Override
    public void deleteUser() {
        //删除用户信息的逻辑
        userDao.deleteById();
    }

    public void saveUser(){
        userDao.deleteById();
    }

}
如果代码是这样编写的,才算是完全⾯向接⼝编程,才符合依赖倒置原则。

1.3 控制反转IoC

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

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

控制反转(Inversion of Control,缩写为IoC),是⾯向对象编程中的⼀种设计思想,可以⽤来降低代码之间的耦合度,符合依赖倒置原则。
控制反转的核⼼是: 将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三⽅容器来负责创建与维护
控制反转常⻅的实现⽅式:依赖注⼊(Dependency Injection,简称DI)
通常,依赖注⼊的实现由包括两种⽅式:
      ● set⽅法注⼊
      ● 构造⽅法注⼊
⽽Spring框架就是⼀个实现了IoC思想的框架。
IoC可以认为是⼀种 全新的设计模式 ,但是理论和时间成熟相对较晚,并没有包含在GoF中。(GoF指的是23 种设计模式)

1.4 spring

 那你可能会问,这样userDao 是null,在执⾏的时候就会出现空指针异常呀。说的有道理,确实是这样的,所以我们要解决这个问题。解决空指针异常的问题,其实就是解决两个核⼼的问题:
第⼀个问题:谁来负责对象的创建。【也就是说谁来:new UserDaoImplForOracle()/new
UserDaoImplForMySQL()】
第⼆个问题:谁来负责把创建的对象赋到这个属性上。【也就是说谁来把上⾯创建的对象赋给
userDao属性】
如果我们把以上两个核⼼问题解决了,就可以做到既符合OCP开闭原则,⼜符合依赖倒置原则。
在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。换句话说, Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。⽐如:
Spring可以new出来UserDaoImplForMySQL对象,也可以new出来UserDaoImplForOracle对象,并且还可以让new出来的dao对象和service对象产⽣关系(产⽣关系其实本质上就是给属性赋值)。
很显然,这种⽅式是将对象的创建权/管理权交出去了,不再使⽤硬编码的⽅式了。同时也把对象关系的管理权交出去了,也不再使⽤硬编码的⽅式了。像这种把对象的创建权交出去,把对象关系的管理权交出去,被称为控制反转。
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简介

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

2.2 Spring8⼤模块

注意:Spring 5 版本之后是 8 个模块。在Spring 5 中新增了WebFlux模块。
1. Spring Core模块
这是Spring框架最基础的部分,它提供了依赖注⼊(DependencyInjection)特征来实现容器对Bean的管理。核⼼容器的主要组件是 BeanFactory,BeanFactory是⼯⼚模式的⼀个实现,是任何Spring应⽤的核⼼。它使⽤IoC将应⽤配置和依赖从实际的应⽤代码中分离出来。
2. Spring Context模块
如果说核⼼模块中的BeanFactory使Spring成为容器的话,那么上下⽂模块就是Spring成为框架的原因。这个模块扩展了BeanFactory,增加了对国际化(I18 N)消息、事件传播、验证的⽀持。另外提供了许多企业服务,例如电⼦邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的⽀持
3. Spring AOP模块
Spring在它的AOP模块中提供了对⾯向切⾯编程的丰富⽀持,Spring AOP 模块为基于 Spring 的应⽤程序中的对象提供了事务管理服务。通过使⽤ Spring AOP,不⽤依赖组件,就可以将声明性事务管理集成到应⽤程序中,可以⾃定义拦截器、切点、⽇志等操作。
4. Spring DAO模块
提供了⼀个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库⼚商特有的错误代码解析,⽤于简化JDBC。
5. Spring ORM模块
Spring提供了ORM模块。Spring并不试图实现它⾃⼰的ORM解决⽅案,⽽是为⼏种流⾏的ORM框架提供了集成⽅案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通⽤事务和 DAO 异常层次结构。
6. Spring Web MVC模块
Spring为构建Web应⽤提供了⼀个功能全⾯的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使⽤IoC对控制逻辑和业务对象提供了完全的分离。
7. Spring WebFlux模块
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专⻔为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5 . 0 版的后期添加的。它是完全⾮阻塞的,⽀持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3 . 1 +容器等服务器上运⾏。

8. Spring Web模块
Web 上下⽂模块建⽴在应⽤程序上下⽂模块之上,为基于 Web 的应⽤程序提供了上下⽂,提供了
Spring和其它Web框架的集成,⽐如Struts、WebWork。还提供了⼀些⾯向服务⽀持,例如:实现⽂件上传的multipart请求。

2.3 Spring特点

1. 轻量
a. 从⼤⼩与开销两⽅⾯⽽⾔Spring都是轻量的。完整的Spring框架可以在⼀个⼤⼩只有 1MB多的
JAR⽂件⾥发布。并且Spring所需的处理开销也是微不⾜道的。
b. Spring是⾮侵⼊式的:Spring应⽤中的对象不依赖于Spring的特定类。
2. 控制反转
a. Spring通过⼀种称作控制反转(IoC)的技术促进了松耦合。当应⽤了IoC,⼀个对象依赖的其
它对象会通过被动的⽅式传递进来,⽽不是这个对象⾃⼰创建或者查找依赖对象。你可以认为
IoC与JNDI相反——不是对象从容器中查找依赖,⽽是容器在对象初始化时不等对象请求就主动
将依赖传递给它。
3. ⾯向切⾯
a. Spring提供了⾯向切⾯编程的丰富⽀持,允许通过分离应⽤的业务逻辑与系统级服务(例如审
计(auditing)和事务(transaction)管理)进⾏内聚性的开发。应⽤对象只实现它们应该做
的——完成业务逻辑——仅此⽽已。它们并不负责(甚⾄是意识)其它的系统级关注点,例如
⽇志或事务⽀持。
4. 容器
a. Spring包含并管理应⽤对象的配置和⽣命周期,在这个意义上它是⼀种容器,你可以配置你的
每个bean如何被创建——基于⼀个可配置原型(prototype),你的bean可以创建⼀个单独的
实例或者每次需要时都⽣成⼀个新的实例——以及它们是如何相互关联的。然⽽,Spring不应
该被混同于传统的重量级的EJB容器,它们经常是庞⼤与笨重的,难以使⽤。
5. 框架
a. Spring可以将简单的组件配置、组合成为复杂的应⽤。在Spring中,应⽤对象被声明式地组
合,典型地是在⼀个XML⽂件⾥。Spring也提供了很多基础功能(事务管理、持久化框架集成
等等),将应⽤逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更⼲净、更可管理、并且更易于测试的代码。它们也为Spring中的 各种模块提供了基础⽀持。

三、Spring的⼊⻔程序

 3.1 Spring的下载

官⽹地址: https://spring.io/
Spring Framework -> GitHub -> Access to Binaries -> Spring Repostitories -> Artifacts -> plugins-release -> org -> Spring Framework -> Spring
注意:
如果你只是想⽤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.2 第⼀个Spring程序

创建模块:spring 6 - 002 -first
pom.xml:
<packaging>jar</packaging>

    <dependencies>
        <!--spring context依赖-->
        <!--当你引入Spring Context依赖以后,表示将Spring的基础依赖引入了-->
        <!--如果,你想使用spring的jdbc,或者说其他的tx,那么还需要再次添加依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
User类:
//这是一个Bean,封装了用户的信息,Spring可以帮助我们创建User对象
public class User {
}

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

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

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

        //第二步:根据bean的id从Spring容器中获取这个对象,里面写bean的id
        Object userBean = applicationContext.getBean("userBean");
        System.out.println(userBean);//com.powernode.spring6.bean.User@294425a7

        //获取其他bean
        Object userDaoBean = applicationContext.getBean("userDaoBean");
        System.out.println(userDaoBean);//com.powernode.spring6.dao.UserDaoImplForMySQL@67d48005
    }
1. bean标签的id属性不可以重复
2.创建对象是通过反射机制调⽤⽆参数构造⽅法
spring是通过调⽤类的⽆参数构造⽅法来创建对象的,所以要想让spring给你创建对象,必须保证⽆参数构造⽅法是存在的。
Spring是如何创建对象的呢?原理是什么?
// dom4j解析beans.xml⽂件,从中获取class的全限定类名
// 通过反射机制调⽤⽆参数构造⽅法创建对象
Class clazz = Class.forName("com.powernode.spring6.bean.User");
Object obj = clazz.newInstance();
3.创建好的对象存储到⼀个Map集合的数据结构当中

4. spring配置⽂件的名字是随意的

这个spring配置⽂件名字是我们负责提供的,显然spring配置⽂件的名字是随意的。

5. 像这样的beans.xml⽂件可以有多个

spring的配置⽂件可以有多个,在ClassPathXmlApplicationContext构造⽅法的参数上传递⽂件路径即可。这是为什么呢?通过源码可以看到:
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml","beans.xml");
6. 在配置⽂件中配置的类必须是⾃定义的吗,可以使⽤JDK中的类,例如:java.util.Date
    <!--配置java.util.Date Bean-->
    <bean id="nowTime" class="java.util.Date"/>
7. getBean()⽅法调⽤时,如果指定的id不存在会怎样?
        //出现异常,不会返回null ,NoSuchBeanDefinitionException: No bean named 'nowTime22' available
        Object nowTime = applicationContext.getBean("nowTime22");
        System.out.println(nowTime);//Sat Aug 05 14:31:09 CST 2023
8. getBean()⽅法返回的类型是Object,如果访问⼦类的特有属性和⽅法时,还需要向下转型,有其它办法可以解决这个问题吗?
向下转型:
        //NoSuchBeanDefinitionException: No bean named 'nowTime22' available
//        Object nowTime = applicationContext.getBean("nowTime");
        Date nowTime = (Date)applicationContext.getBean("nowTime");
        //System.out.println(nowTime);//Sat Aug 05 14:31:09 CST 2023
        //日期格式化 需要将object强转成Date类型,才能使用
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String format = sdf.format(nowTime);
        System.out.println(format);//2023-08-05 14:37:14 542

使用getBean里面的参数:

        //不想强制类型转换,指定第二个参数,直接返回我们需要的类型
        Date nowTime1 = applicationContext.getBean("nowTime", Date.class);
        System.out.println("nowTime1="+nowTime1);//nowTime1Sat= Aug 05 14:38:57 CST 2023
9. ClassPathXmlApplicationContext是从类路径中加载配置⽂件,如果没有在类路径当中,⼜应该如何加载配置⽂件呢?
ApplicationContext urlSpring = new FileSystemXmlApplicationContext("d:/spring6.xml");
没有在类路径中的话,需要使⽤FileSystemXmlApplicationContext类进⾏加载配置⽂件。
这种⽅式较少⽤。⼀般都是将配置⽂件放到类路径当中,这样可移植性更强。
10. ApplicationContext的超级⽗接⼝BeanFactory。
    @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 userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);
    }
BeanFactory是Spring容器的超级接⼝。ApplicationContext是BeanFactory的⼦接⼝,功能更丰富。
注意:
@Test
    public void testBeginInitBean(){
        // 注意:不是在调用getBean()方法的时候创建对象,执行以下代码的时候,就会实例化对象。
        new ClassPathXmlApplicationContext("spring6.xml");
    }

3.3 Spring6启⽤Log4j2⽇志框架

从Spring 5 之后,Spring框架⽀持集成的⽇志框架是Log 4 j 2 .如何启⽤⽇志框架:
第⼀步:引⼊Log 4 j 2 的依赖
  <!--log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.20.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.20.0</version>
        </dependency>
第⼆步:在类的根路径下提供log 4 j 2 .xml配置⽂件(⽂件名固定为:log 4 j 2 .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>
如果要使⽤自己的⽇志框架时:
    @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注⼊
      ●第⼆种:构造注⼊
新建模块:spring 6 - 003 -dependency-injection

4.2.1 set注⼊

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

UserDao类:

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

    public void insert(){
        //使用log4j2日志框架
        logger.info("数据库正在保存用户信息..........");
    }
}

vipDao类:

public class VipDao {
    private static final Logger logger= LoggerFactory.getLogger(VipDao.class);

    public void insert(){
        //使用log4j2日志框架
        logger.info("正在保存VIP信息..........");
    }
}

UserService类:

public class UserService {
    private UserDao userDao;
    private VipDao vipDao;

    //set注入的话,必须要提供一个set方法
    //Spring容器会调用这个set方法,来给userDao属性赋值
    //这个set方法是IDEA工具生成的,符合javabean规范
    public void setUserDao(UserDao userDao){
        this.userDao=userDao;
    }

    // 我自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。
    // 至少这个方法是以set单词开始的。前三个字母不能随便写,必须是“set"
    /*public void setMySQLUserDao(UserDao xyz){
        this.userDao = xyz;
    }*/

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

    public void saveUser(){
        //保存用户信息到数据库
        userDao.insert();
        vipDao.insert();
    }
}

spring.xml:

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

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

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

SpringDITest类:

    @Test
    public void testSetDI(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService= applicationContext.getBean("userServiceBean", UserService.class);
        userService.saveUser();
        //2023-08-05 15:44:10 362 [main] INFO com.powernode.spring6.dao.UserDao - 数据库正在保存用户信息..........
        //2023-08-05 15:51:54 210 [main] INFO com.powernode.spring6.dao.VipDao - 正在保存VIP信息..........
    }
实现原理:
通过property标签获取到属性名:userDao
通过属性名推断出set⽅法名:setUserDao
通过反射机制调⽤setUserDao()⽅法给属性赋值
property标签的name是属性名。
property标签的ref是要注⼊的bean对象的id。 (通过ref属性来完成bean的装配,这是bean最简单的⼀种装配⽅式。装配指的是:创建系统组件之间关联的动作)
总结:set注⼊的核⼼实现原理:通过反射机制调⽤set⽅法来给属性赋值,让两个对象之间产⽣关系。

4.2.2 构造注⼊

核⼼原理:通过调⽤构造⽅法来给属性赋值。
依然是UserDao和VipDao不变。
CustomerService类:
public class CustomerService {
    private UserDao userDao;
    private VipDao vipDao;

    //构造方法注入


    public CustomerService(UserDao userDao, VipDao vipDao) {
        this.userDao = userDao;
        this.vipDao = vipDao;
    }

    public void save() {
        userDao.insert();
        vipDao.insert();
    }
}

beans.xml:

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

    <bean id="csBean" class="com.powernode.spring6.service.CustomerService">
        <!--构造注入-->
        <!--
            index属性指定参数下标,第一个参数是0,第二个参数是1,第三个参数是2,以此类推。
            ref属性用来指定注入的bean的id
        -->
        <!--根据构造方法的下标注入-->
        <!--指定构造方法的第一个参数,下标是0-->
        <constructor-arg index="0" ref="userDaoBean"/>
        <!--指定构造方法的第二个参数,下标是1-->
        <constructor-arg index="1" ref="vipDaoBean"/>
    </bean>

    <bean id="csBean2" class="com.powernode.spring6.service.CustomerService">
        <!--根据构造方法的名字注入-->
        <constructor-arg name="userDao" ref="userDaoBean"/>
        <constructor-arg name="vipDao" ref="vipDaoBean"/>
    </bean>

    <bean id="csBean3" class="com.powernode.spring6.service.CustomerService">
        <!--不指定下标,也不指定名字,让spring自己做类型匹配推断-->
        <!--这种方式实际上是根据类型进行注入的,spring会自动根据类型来判断把ref注入给哪个参数-->
        <constructor-arg ref="vipDaoBean"/>
        <constructor-arg ref="userDaoBean"/>
    </bean>

SpringTest类:

  @Test
    public void testConstructorDI(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        CustomerService csBean = applicationContext.getBean("csBean", CustomerService.class);
        csBean.save();
//        2023-08-05 16:56:41 845 [main] INFO com.powernode.spring6.dao.UserDao - 数据库正在保存用户信息..........
//        2023-08-05 16:56:41 847 [main] INFO com.powernode.spring6.dao.VipDao - 正在保存VIP信息..........

        CustomerService csBean2 = applicationContext.getBean("csBean2", CustomerService.class);
        csBean2.save();

        CustomerService csBean3 = applicationContext.getBean("csBean3", CustomerService.class);
        csBean3.save();
    }
通过测试得知,通过构造⽅法注⼊的时候:
     ●可以通过下标
     ●可以通过参数名
     ●也可以不指定下标和参数名,可以类型⾃动推断。
Spring在装配⽅⾯做的还是⽐较健壮的。

4.3 set注⼊专题

4.3.1 注⼊外部Bean

set-di.xml:

    <!--外部Bean-->
    <!--声明Bean,或者定义Bean-->
    <bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>

    <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
        <!--注入外部Bean,使用ref属性来引入,这就是注入外部Bean-->
        <property name="orderDao" ref="orderDaoBean"/>
    </bean>
 @Test
    public void testSetDI2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class);
        orderServiceBean.generate();
        //2023-08-09 09:56:41 920 [main] INFO com.powernode.spring6.dao.OrderDao - 订单正在生成
}

4.3.2 注⼊内部Bean

内部Bean的⽅式:在bean标签中嵌套bean标签。
set-di.xml
    <!--内部Bean-->
    <bean id="orderServiceBean2" class="com.powernode.spring6.service.OrderService">
        <property name="orderDao">
            <!--在property标签中使用嵌套的bean标签,这既是内部Bean-->
            <bean class="com.powernode.spring6.dao.OrderDao"></bean>
        </property>
    </bean>
    @Test
    public void testSetDI2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        OrderService orderServiceBean2 = applicationContext.getBean("orderServiceBean2", OrderService.class);
        orderServiceBean2.generate();
        //2023-08-09 10:00:23 675 [main] INFO com.powernode.spring6.dao.OrderDao - 订单正在生成
    }
这种⽅式作为了解。

4.3.3 注⼊简单类型

我们之前在进⾏注⼊的时候,对象的属性是另⼀个对象。
public class UserService{
 
 private UserDao userDao;
 
 public void setUserDao(UserDao userDao){
 this.userDao = userDao;
 }
 
}
那如果对象的属性是int类型呢?
第⼀步:定义User类,提供age属性,提供age属性的setter⽅法。
public class User {
    private String username;
    private String password;
    private int age;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

set-di.xml:

    <!--注入简单类型-->
    <bean id="userBean" class="com.powernode.spring6.bean.User">
        <!--如果给简单类型赋值,就不能使用ref了,要使用value了-->
        <property name="username" value="张三"/>
        <property name="password" value="123456"/>
        <property name="age" value="20"/>
    </bean>
    @Test
    public void testSimpletypeSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);//User{username='张三', password='123456', age=20}
    }
需要特别注意:如果给简单类型赋值,使⽤value属性或value标签。⽽不是ref。
简单类型包括哪些呢?可以通过Spring的源码来分析⼀下:BeanUtils类
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) ||
    Temporal.class.isAssignableFrom(type) ||
    URI.class == type ||
    URL.class == type ||
    Locale.class == type ||
    Class.class == type));
}
 
 //........
}
通过源码分析得知,简单类型包括:
      ●基本数据类型
      ●基本数据类型对应的包装类
      ●String或其他的CharSequence⼦类
      ●Number⼦类
      ●Date⼦类
      ●Enum⼦类
      ●URI
      ●URL
      ●Temporal⼦类
      ●Locale
      ●Class
      ●另外还包括以上简单值类型对应的数组类型。
测试简单类型:
SimpleValueType类:
//测试简单类型
public class SimpleValueType {
    private int age;
    private Integer age2;

    private boolean flag;
    private Boolean flag2;

    private char c;
    private Character c2;

    private Season season;

    private String username;

    private Class clazz;

    private Date birth;
    后面提供set方法和toString方法

Season枚举类:

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

set-di.xml文件:

<!--测试哪些类型是简单类型-->
    <bean id="svt" class="com.powernode.spring6.bean.SimpleValueType">
        <property name="age" value="18"/>
        <property name="age2" value="19"/>
        <property name="c" value="男"/>
        <property name="c2" value="女"/>
        <property name="clazz" value="java.lang.String"/>
        <property name="flag" value="false"/>
        <property name="flag2" value="true"/>
        <property name="season" value="SPRING"/>
        <property name="username" value="猪猪侠"/>
        <!--报错了,说2023-06-15这个字符串无法转换成java.util.Date类型-->
        <!--        <property name="birth" value="2023-06-15"/>-->
        <!--如果你硬要把Date当做简单类型的话,使用value赋值的话,这个日期字符串格式有要求-->
        <!--在实际开发中,我们一般不会把Date当做简单类型,虽然它是简单类型。一般会采用ref给Date类型的属性赋值。-->
        <property name="birth" value="Wed Oct 19 16:28:13 CST 2022"/>
    </bean>
    @Test
    public void testSimpletypeSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);//User{username='张三', password='123456', age=20}

        System.out.println("==============================");
        SimpleValueType svt = applicationContext.getBean("svt", SimpleValueType.class);
        System.out.println(svt);
        //SimpleValueType{age=18, age2=19, flag=false, flag2=true, c=男, c2=女, season=SPRING, username='猪猪侠', clazz=class java.lang.String, birth=Thu Oct 20 06:28:13 CST 2022}
    }
需要注意的是:
如果把Date当做简单类型的话,⽇期字符串格式不能随便写。格式必须符合Date的toString()⽅法
格式。显然这就⽐较鸡肋了。如果我们提供⼀个这样的⽇期字符串: 2010 - 10 - 11 ,在这⾥是⽆法赋
值给Date类型的属性的。
spring 6 之后,当注⼊的是URL,那么这个url字符串是会进⾏有效性检测的。如果是⼀个存在的
url,那就没问题。如果不存在则报错。
经典案例:给数据源的属性注⼊值:
假设我们现在要⾃⼰⼿写⼀个数据源,我们都知道所有的数据源都要实现javax.sql.DataSource接⼝,并且数据源中应该有连接数据库的信息,例如:driver、url、username、password等。
MyDataSource类:
//所有的数据源都要实现java规范:java.sql.DataSource
//什么是数据源:能够给你提供Connection对象的,都是数据源
public class MyDateSource implements DataSource {//可以把数据源交给Spring容器来管理
    private String url;
    private String driver;
    private String username;
    private String password;
    后面set方法和toString方法
    还有实现DataSource的方法

set-di.xml文件:

 <!--让spring来管理我们的数据源-->
    <bean id="myDatasource" class="com.powernode.spring6.jdbc.MyDateSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    @Test
    public void testMyDataSource(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        MyDateSource myDatasource = applicationContext.getBean("myDatasource", MyDateSource.class);
        System.out.println(myDatasource);
        //MyDateSource{url='jdbc:mysql://localhost:3306/spring6', driver='com.mysql.cj.jdbc.Driver', username='root', password='123456'}
    }

4.3.4 注⼊数组

当数组中的元素是简单类型:
QianDaYe类:
public class QianDaYe {
    private String[] aiHaos;
    

    public void setAiHaos(String[] aiHaos) {
        this.aiHaos = aiHaos;
    }

    @Override
    public String toString() {
        return "QianDaYe{" +
                "aiHaos=" + Arrays.toString(aiHaos) +
                '}';
    }
}

spring-array.xml文件:

<bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
        <!--这个数组属性当中的元素类型是String简单类型-->
        <property name="aiHaos">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
    </bean>
@Test
    public void testArray(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");
        QianDaYe yuQian = applicationContext.getBean("yuQian", QianDaYe.class);
        System.out.println(yuQian);//QianDaYe{aiHaos=[抽烟, 喝酒, 烫头]}
    }
当数组中的元素是⾮简单类型:
Women类:
public class Woman {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Woman{" +
                "name='" + name + '\'' +
                '}';
    }
}

QianDaYe类:

public class QianDaYe {
    private String[] aiHaos;

    private Woman[] womens;

    public void setWomens(Woman[] womens) {
        this.womens = womens;
    }

    public void setAiHaos(String[] aiHaos) {
        this.aiHaos = aiHaos;
    }

    @Override
    public String toString() {
        return "QianDaYe{" +
                "aiHaos=" + Arrays.toString(aiHaos) +
                ", womens=" + Arrays.toString(womens) +
                '}';
    }
}

spring-array.xml类:

    <bean id="w1" class="com.powernode.spring6.bean.Woman">
        <property name="name" value="小花"/>
    </bean>
    <bean id="w2" class="com.powernode.spring6.bean.Woman">
        <property name="name" value="小亮"/>
    </bean>
    <bean id="w3" class="com.powernode.spring6.bean.Woman">
        <property name="name" value="小明"/>
    </bean>

    <bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
        <!--这个数组属性当中的元素类型是String简单类型-->
        <property name="aiHaos">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>

        <!--这个数组当中不是简单类型了-->
        <property name="womens">
            <array>
                <ref bean="w1"/>
                <ref bean="w2"/>
                <ref bean="w3"/>
            </array>
        </property>
    </bean>
   @Test
    public void testArray(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");
        QianDaYe yuQian = applicationContext.getBean("yuQian", QianDaYe.class);
        System.out.println(yuQian);//QianDaYe{aiHaos=[抽烟, 喝酒, 烫头]}
        //QianDaYe{aiHaos=[抽烟, 喝酒, 烫头], womens=[Woman{name='小花'}, Woman{name='小亮'}, Woman{name='小明'}]}
    }

4.3.5 注⼊List集合和Set集合

List集合:有序可重复
Set集合:⽆序不可重复
Person类:
public class Person {
    //注入List集合
    private List<String> names;

    //注入set集合
    private Set<String> addrs;

    public void setNames(List<String> names) {
        this.names = names;
    }

    public void setAddrs(Set<String> addrs) {
        this.addrs = addrs;
    }

    @Override
    public String toString() {
        return "Person{" +
                "names=" + names +
                ", addrs=" + addrs +
                '}';
    }
}

spring-collection.xml文件:

    <bean id="personBean" class="com.powernode.spring6.bean.Person">
        <property name="names">
            <!--list是有序可重复的-->
            <list>
                <value>张山</value>
                <value>李四</value>
                <value>王五</value>
                <value>张山</value>
            </list>
        </property>

        <property name="addrs">
            <!--set集合无序不可重复-->
            <set>
                <value>北京大兴区</value>
                <value>北京大兴区</value>
                <value>北京海淀区</value>
                <value>北京海淀区</value>
            </set>
        </property>
    </bean>
    @Test
    public void testCollection(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
        Person personBean = applicationContext.getBean("personBean", Person.class);
        System.out.println(personBean);//Person{names=[张山, 李四, 王五, 张山], addrs=[北京大兴区, 北京海淀区]}
    }

4.3.注⼊Map集合

Person类:

  private Map<Integer, String> phones;

    public void setPhones(Map<Integer, String> phones) {
        this.phones = phones;
    }

spring-collection.xml文件:

<property name="phones">
            <map>
                <entry key="1" value="110"/>
                <entry key="2" value="119"/>
                <entry key="3" value="120"/>
                <entry key="4" value="10086"/>
            </map>
        </property>
    @Test
    public void testCollection(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
        Person personBean = applicationContext.getBean("personBean", Person.class);
        System.out.println(personBean);//Person{names=[张山, 李四, 王五, 张山], addrs=[北京大兴区, 北京海淀区]}
        //phones={1=110, 2=119, 3=120, 4=10086}}
    }
要点:
      ●使⽤<map>标签
      ●如果key是简单类型,使⽤ key 属性,反之使⽤ key-ref 属性。
      ●如果value是简单类型,使⽤ value 属性,反之使⽤ value-ref 属性。

4.3.7 注⼊Properties

java.util.Properties继承java.util.Hashtable,所以Properties也是⼀个Map集合。
   //注入属性类
    //Properties本质上也是一个Map集合
    //Properties的父类Hashtable,Hashtable实现了Map接口
    //显然这个也是一个Map集合,但是和Map的注入方式有点像,但是不同
    //Properties的key和Value只能是String类型,不能是别的
    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

spring-collection.xml文件:

<property name="properties">
            <props>
                <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
 @Test
    public void testCollection(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
        Person personBean = applicationContext.getBean("personBean", Person.class);
        System.out.println(personBean);//Person{names=[张山, 李四, 王五, 张山], addrs=[北京大兴区, 北京海淀区]}
        //phones={1=110, 2=119, 3=120, 4=10086}}
        //properties={password=123456, driver=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring6, username=root}}
    }
要点:
      ●使⽤<props>标签嵌套<prop>标签完成。

4.3.8 注⼊null和空字符串

注⼊空字符串使⽤:<value/> 或者 value=""
注⼊null使⽤:<null/> 或者 不为该属性赋值
      ●我们先来看⼀下,怎么注⼊空字符串。
Cat类:
public class Cat {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

set-di.xml文件:

    <bean id="catBean" class="com.powernode.spring6.bean.Cat">
        <!--不给属性注入,属性的默认值是null-->

        <!--<property name="name" value="null"/>-->
        <!--这不是注入null,这是给属性注入了一个null的字符串-->

        <!--这种方式是手动注入null-->
        <!--<property name="name">
            <null/>
        </property>-->

        <!--注入空字符串第一种方式-->
        <!--<property name="name" value=""/>-->
        <!--注入空字符串第二种方式-->
        <!--<property name="name" value="tom"/>-->
        <property name="age" value="18"/>
    </bean>
    @Test
    public void testNull(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        Cat catBean = applicationContext.getBean("catBean", Cat.class);
        System.out.println(catBean);    //Cat{name='tom', age=18}
        //Cat{name='null', age=18}
    }

4.3.9 注⼊的值中含有特殊符号

XML中有 5 个特殊字符,分别是:<、>、'、"、&
以上 5 个特殊符号在XML中会被特殊对待,会被当做XML语法的⼀部分进⾏解析,如果这些特殊符号直接出现在注⼊的字符串当中,会报错。
解决⽅案包括两种:
      ●第⼀种:特殊符号使⽤转义字符代替。
      ●第⼆种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML⽂件解析器解析。

 MathBean类:

public class MathBean {
    private String result;

    public void setResult(String result) {
        this.result = result;
    }

    @Override
    public String toString() {
        return "MathBean{" +
                "result='" + result + '\'' +
                '}';
    }
}

set-di.xml文件:

    <bean id="mathBean" class="com.powernode.spring6.bean.MathBean">
        <!--第一种方案:使用实体符号代替特殊符号-->
        <!--<property name="result" value="2 &lt; 3" />-->

        <!--第二种方案:使用<![CDATA[]]>-->
        <property name="result">
            <!--只能使用value标签-->
            <value><![CDATA[2 < 3]]></value>
        </property>
    </bean>
  @Test
    public void testSpecial(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        MathBean mathBean = applicationContext.getBean("mathBean", MathBean.class);
        System.out.println(mathBean);//MathBean{result='2 < 3'}
    }
注意:使⽤CDATA时,不能使⽤value属性,只能使⽤value标签。

4.4 p命名空间注⼊

⽬的:简化配置。
使⽤p命名空间注⼊的前提条件包括两个:
      ●第⼀:在XML头部信息中添加p命名空间的配置信息                        xmlns:p=" http://www.springframework.org/schema/p"
      ●第⼆:p命名空间注⼊是基于setter⽅法的,所以需要对应的属性提供setter⽅法。
Dog类:
public class Dog {
    //简单类型
    private String name;
    private int age;
    //非简单类型
    private Date birth;

    public void setName(String name) {
        this.name = name;
    }

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

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birth=" + birth +
                '}';
    }
}

spring-p.xml文件:

       xmlns:p="http://www.springframework.org/schema/p"
    <!--
            第一步:在spring的配置文件头部添加p命名空间。xmlns:p="http://www.springframework.org/schema/p"
            第二步:使用 p:属性名 = "属性值"
        -->
    <bean id="dogBean" class="com.powernode.spring6.bean.Dog" p:name="小花" p:age="3" p:birth-ref="birthBean"/>

    <!--这里获取当前系统时间-->
    <bean id="birthBean" class="java.util.Date"/>
    @Test
    public void testP(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");
        Dog dogBean = applicationContext.getBean("dogBean", Dog.class);
        System.out.println(dogBean);//Dog{name='小花', age=3, birth=Wed Aug 09 21:34:01 CST 2023}
    }
p命名空间实际上是对set注⼊的简化。

4.5 c命名空间注⼊

c命名空间是简化构造⽅法注⼊的。
使⽤c命名空间的两个前提条件:
第⼀:需要在xml配置⽂件头部添加信息: xmlns: c ="http://www.springframework.org/schema/c"
第⼆:需要提供构造⽅法。
People类:
public class People {
    private String name;
    private int age;
    private boolean sex;

    //c命名空间是简化构造注入的
    //c命名空间注入方法是基于构造方法的
    public People(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}

spring-c.xml文件:

       xmlns:c="http://www.springframework.org/schema/c"
    <!--
     第一步:在spring的配置文件头部添加: xmlns:c="http://www.springframework.org/schema/c"
     第二步:使用
         c:_0 下标方式
         c:name 参数名方式
 -->
    <bean id="peopleBean" class="com.powernode.spring6.bean.People" c:_0="张三" c:age="100" c:sex="true"/>
 @Test
    public void testC(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml");
        People peopleBean = applicationContext.getBean("peopleBean", People.class);
        System.out.println(peopleBean);//People{name='张三', age=100, sex=true}
    }
注意:不管是p命名空间还是c命名空间,注⼊的时候都可以注⼊简单类型以及⾮简单类型。

4.6 util命名空间

使⽤util命名空间可以让 配置复⽤
使⽤util命名空间的前提是:在spring配置⽂件头部添加配置信息。如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ">
MyDataSource 1 类:
public class MyDataSource1 implements DataSource {
    //连接数据库的信息
/*    private String driver;
    private String url;
    private String username;
    private String password;*/

    //Properties属性类对象,这是一个Map集合,key和Value都是string类型
    private Properties properties;


    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "MyDataSource1{" +
                "properties=" + properties +
                '}';
    }
    后面跟DataSource实现方法

}
MyDataSource2 类:
public class MyDataSource2 implements DataSource {

    //Properties属性类对象,这是一个Map集合,key和Value都是string类型
    private Properties properties;


    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "MyDataSource1{" +
                "properties=" + properties +
                '}';
    }
    后面跟Datasource实现方法
}

spring-util.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"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ">

    <!--引入util命名空间
      在spring的配置文件头部添加:
      xmlns:util="http://www.springframework.org/schema/util"

      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
  -->
    <util:properties id="prop">
        <prop key="drive">com.mysql.cj.jdbc.Driver</prop>
        <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
        <prop key="username">root</prop>
        <prop key="password">123456</prop>
    </util:properties>

    <!--数据源1-->
    <bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
        <property name="properties" ref="prop"/>
    </bean>

    <!--数据源2-->
    <bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource2">
        <property name="properties" ref="prop"/>
    </bean>
</beans>
    @Test
    public void testUtil(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml");
        MyDataSource1 ds1 = applicationContext.getBean("ds1", MyDataSource1.class);
        MyDataSource2 ds2 = applicationContext.getBean("ds2", MyDataSource2.class);
        System.out.println(ds1);
        System.out.println(ds2);
//        MyDataSource1{properties={password=123456, drive=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring6, username=root}}
//        MyDataSource1{properties={password=123456, drive=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring6, username=root}}

    }

4.7 基于XML的⾃动装配

Spring还可以完成⾃动化的注⼊,⾃动化注⼊⼜被称为⾃动装配。它可以根据 名字 进⾏⾃动装配,也可以根据 类型 进⾏⾃动装配。

4.7.1 根据名称⾃动装配

OrderDao类:

public class OrderDao {
    private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);

    public void insert(){
        logger.info("订单正在生成");

    }
}

OrderService类:

public class OrderService {
    private OrderDao orderDao;

    //通过set属性给方法赋值
    public void setOrderDao(OrderDao  orderDao){
        this.orderDao=orderDao;
    }

    //生成订单的业务方法
    public void generate(){
        orderDao.insert();
    }
}

spring-autowire.xml文件:

 <!--根据名字进行自动装配-->
    <!-- 注意:自动装配也是基于set方式实现的-->
    <bean id="orderService" class="com.powernode.spring6.service.OrderService" autowire="byName"></bean>

    <!--id一般叫做bean的名称-->
    <!--根据名字进行自动装配的时候,被注入的对象的bean的id不能随便写,怎么写?set方法的方法名去掉set,剩下单词首字母小写。-->
    <bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>


    <!--    <bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>-->

    <!--    <bean id="orderService" class="com.powernode.spring6.service.OrderService">-->
    <!--        <property name="orderDao" ref="orderDao"/>-->
    <!--    </bean>-->
 @Test
    public void testAutowire(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
        //2023-08-09 22:22:15 704 [main] INFO com.powernode.spring6.dao.OrderDao - 订单正在生成
    }
如果根据名称装配(byName),底层会调⽤set⽅法进⾏注⼊。
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。

4.7.2 根据类型⾃动装配

UserDao类:

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

    public void insert(){
        //使用log4j2日志框架
        logger.info("数据库正在保存用户信息..........");
    }
}

VipDao类:

public class VipDao {
    private static final Logger logger= LoggerFactory.getLogger(VipDao.class);

    public void insert(){
        //使用log4j2日志框架
        logger.info("正在保存VIP信息..........");
    }
}

CustomerService类:

public class CustomerService {
    private UserDao userDao;
    private VipDao vipDao;

    //构造方法注入
//    public CustomerService(UserDao userDao, VipDao vipDao) {
//        this.userDao = userDao;
//        this.vipDao = vipDao;
//    }


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

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

    public void save() {
        userDao.insert();
        vipDao.insert();
    }
}

spring-autowire.xml文件:

    <!--根据类型进行自动装配-->
    <!--自动装配是基于set方法的-->
    <!--根据类型进行自动装配的时候,在有效的配置文件当中,某种类型的实例只能有一个。-->
    <bean class="com.powernode.spring6.dao.VipDao"></bean>
    <bean class="com.powernode.spring6.dao.UserDao"></bean>
    <!--<bean id="y" class="com.powernode.spring6.dao.UserDao"></bean>-->
    <bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType"></bean>
    @Test
    public void testAutowire(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
        //2023-08-09 22:22:15 704 [main] INFO com.powernode.spring6.dao.OrderDao - 订单正在生成

        CustomerService cs = applicationContext.getBean("cs", CustomerService.class);
        cs.save();
//        2023-08-09 22:35:10 669 [main] INFO com.powernode.spring6.dao.UserDao - 数据库正在保存用户信息..........
//        2023-08-09 22:35:10 670 [main] INFO com.powernode.spring6.dao.VipDao - 正在保存VIP信息..........
    }
可以看到⽆论是byName还是byType,在装配的时候都是基于set⽅法的。所以set⽅法是必须要提供的。提供构造⽅法是不⾏的。
当byType进⾏⾃动装配的时候,配置⽂件中某种类型的Bean必须是唯⼀的,不能出现多个。

4.8 spring引⼊外部属性配置⽂件

我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到⼀个属性配置⽂件中吗,这样⽤户修改起来会更加的⽅便。当然可以。
第⼀步:写⼀个数据源类,提供相关属性。
MyDataSource类:
//所有的数据源都要实现java规范:java.sql.DataSource
//什么是数据源:能够给你提供Connection对象的,都是数据源
public class MyDataSource implements DataSource {//可以把数据源交给Spring容器来管理
    private String url;
    private String driver;
    private String username;
    private String password;

    public void setUrl(String url) {
        this.url = url;
    }
    后面跟DataSource实现方法
}
jdbc.properties:
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/spring6
jdbc.username=root
jdbc.password=123456
spring-proerties.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
        引入外部的properties文件
            第一步:引入context命名空间。
            第二步:使用标签context:property-placeholder的location属性来指定属性配置文件的路径。
                    location默认从类的根路径下开始加载资源。
    -->

    <context:property-placeholder location="jdbc.properties"/>

    <!--配置数据源-->
    <bean id="ds" class="com.powernode.spring6.jdbc.MyDataSource">
        <!--怎么取值呢-->
        <property name="driver" value="${jdbc.driverClass}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Test
    public void testProperties() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-properties.xml");
        MyDataSource ds = applicationContext.getBean("ds", MyDataSource.class);
        System.out.println(ds);
        //MyDateSource{url='jdbc:mysql://localhost/spring6', driver='com.mysql.cj.jdbc.Driver', username='root', password='123456'}
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值