首先感谢动力节点和杜老师的教学分享!Respect!
学习来源:B站
https://www.bilibili.com/video/BV1Ft4y1g7Fb/?spm_id_from=333.337.search-card.all.click&vd_source=07c8a1a7d89af39fe20c3a6894f5ff6a
资料来源:百度网盘
链接:https://pan.baidu.com/s/17caxbwGPn2uHY8624yzqqw?pwd=3pi7
提取码:3pi7
Spring6笔记将分为3部分,typora复制过来的图片不能直接使用非常头痛只能一个一个换,所以去年11月就写完的笔记到现在都没有完全整理到优快云上,后面有空再慢慢更新。
Spring框架自学笔记
- 环境
文章目录
一、Spring启示录
-
阅读以下代码
- com.powernode.spring6.dao.UserDao
package com.powernode.spring6.dao; /** * 持久层接口 * @author ShiningSong * @version 1.0 * @since 1.0 * @className UserDao */ public interface UserDao{ /** * 根据id删除用户信息 */ void deleteById(); }
- com.powernode.spring6.service.impl.UserDaoForMySQL
package com.powernode.spring6.dao.impl; import com.powernode.spring6.dao.UserDao; /** * 持久层接口实现类 * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.impl.UserDaoForMySQL * @date 2022/11/8 * @since 1.0 */ public class UserDaoImplForMySQL implements UserDao { @Override public void deleteById() { System.out.println("MySQL数据库正在删除用户信息。。。。。。"); } }
- com.powernode.spring6.service.UserService
package com.powernode.spring6.service; /** * 业务层接口 * @author ShiningSong * @version 1.0 * @since 1.0 */ public interface UserService { /** * 删除用户信息 */ void deleteUser(); }
- com.powernode.spring6.service.impl.UserServiceImpl
package com.powernode.spring6.service.impl; import com.powernode.spring6.dao.UserDao; import com.powernode.spring6.dao.impl.UserDaoImplForMySQL; import com.powernode.spring6.service.UserService; /** * 业务层接口实现类 * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.impl.UserServiceImpl * @date 2022/11/8 * @since 1.0 */ public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImplForMySQL(); @Override public void deleteUser() { userDao.deleteById(); } }
- com.powernode.spring6.web.UserAction
package com.powernode.spring6.web; import com.powernode.spring6.service.UserService; import com.powernode.spring6.service.impl.UserServiceImpl; /** * 表示层 * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.web.UserAction * @date 2022/11/8 * @since 1.0 */ public class UserAction { private UserService userService = new UserServiceImpl(); /** * 删除用户信息请求 */ public void deleteRequest(){ userService.deleteUser(); } }
- com.powernode.spring6.client.Test
package com.powernode.spring6.client; import com.powernode.spring6.web.UserAction; /** * 测试类 * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.client.Test * @date 2022/11/8 * @since 1.0 */ public class Test { public static void main(String[] args) { UserAction userAction = new UserAction(); userAction.deleteRequest(); } }
- 测试结果
-
可以看出,UserDaoImplForMySQL中主要是连接MySQL数据库进行操作。如果更换到Oracle数据库上,则需要再提供一个UserDaoImplForOracle,如下:
- com.powernode.spring6.dao.impl.UserDaoForOrcle
package com.powernode.spring6.dao.impl; import com.powernode.spring6.dao.UserDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.dao.impl.UserDaoForOrcle * @date 2022/11/8 * @since 1.0 */ public class UserDaoForOrcle implements UserDao { @Override public void deleteById() { System.out.println("Orcle数据库正在删除用户信息。。。。。。"); } }
-
很明显,以上的操作正在进行功能的扩展,添加了一个新的类UserDaoImplForOracle来应付数据库的变化,这里的变化会引起连锁反应吗?当然会,如果想要切换到Oracle数据库上,UserServiceImpl类代码就需要修改,如下:
- com.powernode.spring6.service.impl.UserServiceImpl
package com.powernode.spring6.service.impl; import com.powernode.spring6.dao.UserDao; import com.powernode.spring6.dao.impl.UserDaoForOrcle; import com.powernode.spring6.dao.impl.UserDaoImplForMySQL; import com.powernode.spring6.service.UserService; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.impl.UserServiceImpl * @date 2022/11/8 * @since 1.0 */ public class UserServiceImpl implements UserService { // private UserDao userDao = new UserDaoImplForMySQL(); // 修改为 private UserDao userDao = new UserDaoForOrcle(); @Override public void deleteUser() { userDao.deleteById(); } }
- 测试结果
1.1 OCP开闭原则
-
这样一来就违背了开闭原则OCP。
-
开闭原则是这样说的:
- 在软件开发过程中应当对扩展开放,对修改关闭。
- 也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序。
- 这是忌讳的,不被允许的。
-
因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。
- 这是相当麻烦的过程。
- 导致以上问题的主要原因是:代码和代码之间的耦合度太高。
-
如下图所示:
- 可以很明显的看出,上层是依赖下层的。
- UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySQL,这样就会导致下面只要改动,上面必然会受牵连(跟着也会改),所谓牵一发而动全身。
- 这样也就同时违背了另一个开发原则:依赖倒置原则。
1.2 依赖倒置原则DIP
-
依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。
-
这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务)
-
你可能会说,上面的代码已经面向接口编程了呀:
- 确实已经面向接口编程了,但对象的创建是:new UserDaoImplForOracle()显然并没有完全面向接口编程,还是使用到了具体的接口实现类。
- 什么叫做完全面向接口编程?什么叫做完全符合依赖倒置原则呢?
- 请看以下代码:
- 如果代码是这样编写的,才算是完全面向接口编程,才符合依赖倒置原则。
- 那你可能会问,这样userDao是null,在执行的时候就会出现空指针异常呀。
- 你说的有道理,确实是这样的,所以我们要解决这个问题。解决空指针异常的问题,其实就是解决两个核心的问题:
- 第一个问题:谁来负责对象的创建。【也就是说谁来:new UserDaoImplForOracle()/new UserDaoImplForMySQL()】
- 第二个问题:谁来负责把创建的对象赋到这个属性上。【也就是说谁来把上面创建的对象赋给userDao属性】
- 如果我们把以上两个核心问题解决了,就可以做到既符合OCP开闭原则,又符合依赖倒置原则。
- Spring框架可以做到。在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。
- 换句话说,Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。比如:
- 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种设计模式)
课堂笔记
1. OCP开闭原则
* 什么是OCP?
OCP是软件七大开发原则当中最基本的一个原则:开闭原则
对什么开?对扩展开放。
对什么闭?对修改关闭。
* OCP原则是最核心的,最基本的,其他的六个原则都是为这个原则服务的。
* OCP开闭原则的核心是什么?
只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合OCP原则的。
反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背OCP原则。
* 当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试。这是不想看到的,因为非常麻烦。
2. 依赖倒置原则(DIP原则)
* 什么是依赖倒置原则?
面向接口编程,面向抽象编程,不要面向具体编程。
* 依赖倒置原则的目的?
降低程序的耦合度,提高扩展力。
* 什么叫做符合依赖倒置?
上 不依赖 下,就是符合。
* 什么叫做违背依赖倒置?
上 依赖 下,就是违背。
只要“下”一改动,“上”就受到牵连。
3. 当前程序的设计,显然既违背OCP,又违背DIP,怎么办?
可以采用“控制反转”这种编程思想来解决这个问题。
4. 什么是控制反转?
控制反转:IoC(Inversion 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简介
- 来自百度百科
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。
从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
Spring最初的出现是为了解决EJB臃肿的设计,以及难以测试等问题。
Spring为简化开发而生,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。
2.2 Spring8大模块
- 注意:Spring5版本之后是8个模块。在Spring5中新增了WebFlux模块。
-
Spring Core模块
- 这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。
- 核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。
- 它使用IoC将应用配置和依赖从实际的应用代码中分离出来。
-
Spring Context模块
-
如果说核心模块中的BeanFactory使Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。
-
这个模块扩展了BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。
-
另外提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。
-
也包括了对模版框架例如Velocity和FreeMarker集成的支持
-
-
Spring AOP模块
- Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。
- 通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。
-
Spring DAO模块
- 提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。
-
Spring ORM模块
- Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
-
Spring Web MVC模块
- Spring为构建Web应用提供了一个功能全面的MVC框架。
- 虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。
-
Spring WebFlux模块
- Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。
- 反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。
-
Spring Web模块
- Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。
- 还提供了一些面向服务支持,例如:实现文件上传的multipart请求。
2.3 Spring特点
-
轻量
- 从大小与开销两方面而言Spring都是轻量的。
- 完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。
- 并且Spring所需的处理开销也是微不足道的。
- Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。
- 从大小与开销两方面而言Spring都是轻量的。
-
控制反转
- Spring通过一种称作控制反转(IoC)的技术促进了松耦合。
- 当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
- 你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
- Spring通过一种称作控制反转(IoC)的技术促进了松耦合。
-
面向切面
- Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
- 应用对象只实现它们应该做的——完成业务逻辑——仅此而已。
- 它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
- Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
-
容器
- Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器.
- 你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。
- 然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
- Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器.
-
框架
- Spring可以将简单的组件配置、组合成为复杂的应用。
- 在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。
- Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
- 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/
- 官网地址(中文):http://spring.p2hp.com/
- 打开Spring官网后,可以看到Spring Framework,以及通过Spring Framework衍生的其它框架:
-
我们即将要学习的就是Spring Framework。
-
怎么下载呢?
- 第一步:进入github
- 第二步:找到下图位置,点击超链接
- 第三步:找到下图位置,点击超链接
- 第四步:按照下图步骤操作
- 第五步:继续在springframework目录下找下图的spring,点开之后你会看到很多不同的版本
- 第六步:选择对应的版本
- 第七步:点击上图的url
-
点击spring-5.3.9-dist.zip下载spring框架。
-
将下载的zip包解压:
- docs:spring框架的API帮助文档
- libs:spring框架的jar文件(用spring框架就是用这些jar包)
- schema:spring框架的XML配置文件相关的约束文件
3.2 Spring的jar文件
-
打开libs目录,会看到很多jar包:
- spring-core-5.3.9.jar:字节码(这个是支撑程序运行的jar包)
- spring-core-5.3.9-javadoc.jar:代码中的注释
- spring-core-5.3.9-sources.jar:源码
-
我们来看一下spring框架都有哪些jar包:
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的依赖即可。
-
spring bean依赖
<!--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
- 设置JDK版本17,编译器版本17
- 设置IDEA的Maven:关联自己的maven
- 在空的工程spring6中创建第一个模块:spring6-002-first
第一步:添加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-002-first</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<repositories>
<!--spring6仓库-->
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<!--spring6依赖-->
<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>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
注意:打包方式jar。
-
当加入spring context的依赖之后,会关联引入其他依赖:
- spring aop:面向切面编程
- spring beans:IoC核心
- spring core:spring的核心工具包
- spring jcl:spring的日志包
- spring expression:spring表达式
第二步:添加junit依赖
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
第三步:定义bean:User
package com.powernode.spring6.bean;
/**
* bean,封装用户信息
* @author shanglinsong
* @version 1.0
* @className com.powernode.spring6.bean.User
* @date 2022/11/8
* @since 1.0
*/
public class User {
}
第四步:编写spring的配置文件:beans.xml。该文件放在类的根路径下。
-
上图是使用IDEA工具自带的spring配置文件的模板进行创建。
-
配置文件中进行bean的配置。
beans.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.bean.User" /> </beans>
- bean的id和class属性:
- id属性:代表对象的唯一标识。可以看做一个人的身份证号。
- class属性:用来指定要创建的java对象的类名,这个类名必须是全限定类名(带包名)。
- bean的id和class属性:
第五步:编写测试程序
package com.powernode.spring6.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author shanglinsong
* @version 1.0
* @className com.powernode.spring6.test.Spring6Test
* @date 2022/11/8
* @since 1.0
*/
public class Spring6Test {
@Test
public void testFirstSpring(){
// 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 根据id获取bean对象
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
}
}
第七步:运行测试程序
3.4 第一个Spring程序详细剖析
-
beans.xml
<bean id="userBean" class="com.powernode.spring6.bean.User"/>
Spring6Test.testFirstSpring
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); Object userBean = applicationContext.getBean("userBean");
-
bean标签的id属性可以重复吗?
- 新建Vip.java
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Vip * @date 2022/11/8 * @since 1.0 */ public class Vip { }
- 修改beans.xml配置文件
<bean id="userBean" class="com.powernode.spring6.bean.User" /> <bean id="userBean" class="com.powernode.spring6.bean.Vip" />
- 测试结果:
答:通过测试得出:在spring的配置文件中id是不能重名。
-
底层是怎么创建对象的,是通过反射机制调用无参数构造方法吗?
- 给User.java类添加无参数构造方法
public User() { System.out.println("User的无参数构造方法执行。。。"); }
- 测试结果
答:通过测试得知:创建对象时确实调用了无参数构造方法。
- 如果提供一个有参数构造方法,不提供无参数构造方法会怎样呢?
- 给User.java类添加有参数构造方法
public User(String username) { System.out.println("User的无参数构造方法执行。。。"); }
- 测试结果
答:通过测试得知:spring是通过调用类的无参数构造方法来创建对象的,所以要想让spring给你创建对象,必须保证无参数构造方法是存在的。
- Spring是如何创建对象的呢?原理是什么?
// dom4j解析beans.xml文件,从中获取class的全限定类名 // 通过反射机制调用无参数构造方法创建对象 Class clazz = Class.forName("com.powernode.spring6.bean.User"); Object obj = clazz.newInstance();
-
把创建好的对象存储到一个什么样的数据结构当中了呢?
答:Map集合。
-
spring配置文件的名字必须叫做beans.xml吗?
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
答:通过以上的java代码可以看出,这个spring配置文件名字是我们负责提供的,显然spring配置文件的名字是随意的。
-
像这样的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>
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.xml"); // 根据id获取bean对象 Object userBean = applicationContext.getBean("userBean"); Object vipBean = applicationContext.getBean("vipBean"); System.out.println(userBean); System.out.println(vipBean); }
- 测试结果
答:通过测试得知,spring的配置文件可以有多个,在ClassPathXmlApplicationContext构造方法的参数上传递文件路径即可。
- 这是为什么呢?通过源码可以看到:
-
在配置文件中配置的类必须是自定义的吗,可以使用JDK中的类吗,例如:java.util.Date?
- beans.xml配置文件中添加
<bean id="dateBean" class="java.util.Date" />
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.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); }
- 测试结果
答:通过测试得知,在spring配置文件中配置的bean可以任意类,只要这个类不是抽象的,并且提供了无参数构造方法。
-
getBean()方法调用时,如果指定的id不存在会怎样?
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.xml"); // 根据id获取bean对象 Object noThisBean = applicationContext.getBean("noThisBean"); 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); }
- 测试结果
通过测试得知,当id不存在的时候,会出现异常。
-
getBean()方法返回的类型是Object,如果访问子类的特有属性和方法时,还需要向下转型,有其它办法可以解决这个问题吗?
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.xml"); // 根据id获取bean对象 // Object noThisBean = applicationContext.getBean("noThisBean"); // Object userBean = applicationContext.getBean("userBean"); User userBean = applicationContext.getBean("userBean", User.class); Object vipBean = applicationContext.getBean("vipBean"); Object dateBean = applicationContext.getBean("dateBean"); System.out.println(userBean); System.out.println(vipBean); System.out.println(dateBean); }
- 测试结果
-
ClassPathXmlApplicationContext是从类路径中加载配置文件,如果没有在类路径当中,又应该如何加载配置文件呢?
- 配置文件在d:/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="vipBean1" class="com.powernode.spring6.bean.Vip" /> </beans>
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.xml"); // 配置文件不在类路径当中,在系统路径下时,解析配置文件的方法 ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("d:/spring.xml"); // 根据id获取bean对象 Vip vipBean1 = applicationContext1.getBean("vipBean1", Vip.class); System.out.println(vipBean1); // Object noThisBean = applicationContext.getBean("noThisBean"); // Object userBean = applicationContext.getBean("userBean"); User userBean = applicationContext.getBean("userBean", User.class); Object vipBean = applicationContext.getBean("vipBean"); Object dateBean = applicationContext.getBean("dateBean"); System.out.println(userBean); System.out.println(vipBean); System.out.println(dateBean); }
- 测试结果
答:没有在类路径中的话,需要使用FileSystemXmlApplicationContext类进行加载配置文件。
- 这种方式较少用。一般都是将配置文件放到类路径当中,这样可移植性更强。
-
ApplicationContext的超级父接口BeanFactory。
- 测试类
@Test public void testFirstSpring(){ // 初始化Spring容器上下文,解析beans.xml文件,创建所有的bean对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml", "spring.xml"); // 配置文件不在类路径当中,在系统路径下时,解析配置文件的方法 ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("d:/spring.xml"); // 使用BeanFactory读取配置文件 BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring.xml"); // 根据id获取bean对象 Vip vipBean2 = beanFactory.getBean("vipBean", Vip.class); System.out.println(vipBean2); Vip vipBean1 = applicationContext1.getBean("vipBean1", Vip.class); System.out.println(vipBean1); // Object noThisBean = applicationContext.getBean("noThisBean"); // Object userBean = applicationContext.getBean("userBean"); User userBean = applicationContext.getBean("userBean", User.class); Object vipBean = applicationContext.getBean("vipBean"); Object dateBean = applicationContext.getBean("dateBean"); System.out.println(userBean); System.out.println(vipBean); System.out.println(dateBean); }
- 测试结果
BeanFactory是Spring容器的超级接口。ApplicationContext是BeanFactory的子接口。
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>
-
第三步:使用日志框架
- 测试类
@Test public void testLog4j2(){ Logger logger = LoggerFactory.getLogger(Spring6Test.class); logger.info("我是一条日志消息"); }
- 测试结果
四、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方法。
- 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-003-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>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> <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> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>
- com.powernode.spring6.dao.UserDao
package com.powernode.spring6.dao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.dao.UserDao * @date 2022/11/8 * @since 1.0 */ public class UserDao { // 使用log4j2日志工具,定义logger private static final Logger logger = LoggerFactory.getLogger(UserDao.class); public void insert(){ // System.out.println("正在保存用户数据。。。"); logger.info("数据库正在保存用户信息。。。"); } }
- com.powernode.spring6.service.UserService
package com.powernode.spring6.service; import com.powernode.spring6.dao.UserDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.UserService * @date 2022/11/8 * @since 1.0 */ public class UserService { private UserDao userDao; // set注入的话,必须提供一个set方法。 // Spring容器会调用这个set方法,来给userDao属性赋值。 // 我自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。 // 至少这个方法是以set单词开始的。前三个字母不能随便写,必须是"set" /*public void setMySQLUserDao(UserDao xyz){ this.userDao = xyz; }*/ // 使用set方式注入,必须提供set方法。 // 反射机制要调用这个方法给属性赋值的。 // 这个set方法是IDEA工具生成的,符合javabean规范。 public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** * 保存用户信息到数据库 */ public void saveUser(){ userDao.insert(); } }
- 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="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。--> <!--set方法起名的时候,不要为难自己,按照规范来。所以一般情况下name位置写属性名就行了。--> <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 shanglinsong * @version 1.0 * @className com.powernode.spring6.test.SpringDITest * @date 2022/11/8 * @since 1.0 */ public class SpringDITest { @Test public void testSetDI(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = applicationContext.getBean("userServiceBean", UserService.class); userService.saveUser(); } }
- 测试结果
-
重点内容是,什么原理:
- 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="userDaoBean" class="com.powernode.spring6.dao.UserDao" /> <bean id="userServiceBean" class="com.powernode.spring6.service.UserService"> <!--name需要为set方法名去掉set字母后首字母小写的字符串--> <property name="userDao" ref="userDaoBean"/> </bean> </beans>
- 实现原理:
- 通过property标签获取到属性名:userDao
- 通过属性名推断出set方法名:setUserDao
- 通过反射机制调用setUserDao()方法给属性赋值
- property标签的name是属性名。
- property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
- 可以把set方法注释掉,再测试一下:
- 通过测试得知,底层实际上调用了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(); } }
- 测试结果
- 通过测试看到程序仍然可以正常执行,说明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 构造注入
- 核心原理:通过调用构造方法来给属性赋值。
- com.powernode.spring6.dao.OrderDao
package com.powernode.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author shanglinsong
* @version 1.0
* @className com.powernode.spring6.dao.OrderDao
* @date 2022/11/8
* @since 1.0
*/
public class OrderDao {
private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
public void deleteById(){
logger.info("正在删除订单。。。");
}
}
- com.powernode.spring6.service.OrderService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.OrderDao;
/**
* @author shanglinsong
* @version 1.0
* @className com.powernode.spring6.service.OrderService
* @date 2022/11/8
* @since 1.0
*/
public class OrderService {
private OrderDao orderDao;
// 通过反射机制调用构造方法给属性赋值
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void delete(){
orderDao.deleteById();
}
}
- spring.xml
<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 testConstrutorDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class);
orderServiceBean.delete();
}
- 测试结果
-
如果构造方法有两个参数:
- com.powernode.spring6.service.OrderService
package com.powernode.spring6.service; import com.powernode.spring6.dao.OrderDao; import com.powernode.spring6.dao.UserDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.OrderService * @date 2022/11/8 * @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.xml
<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" />
- 测试程序
@Test public void testConstrutorDI(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class); orderServiceBean.delete(); }
- 测试结果
-
不使用参数下标,使用参数的名字可以吗?
- spring.xml
<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" />
- 测试结果
-
不指定参数下标,不指定参数名字,可以吗?
- spring.xml
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/> <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService"> <!--没有指定下标,也没有指定参数名字--> <constructor-arg ref="orderDaoBean"/> <constructor-arg ref="userDaoBean"/> </bean> <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao" />
- 测试结果
-
配置文件中构造方法参数的类型顺序和构造方法参数的类型顺序不一致呢?
- spring.xml
<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" />
- 测试结果
-
通过测试得知,通过构造方法注入的时候:
- 可以通过下标
- 可以通过参数名
- 也可以不指定下标和参数名,可以类型自动推断。
-
Spring在装配方面做的还是比较健壮的。
4.3 set注入专题
4.3.1 注入外部Bean
-
在之前4.2.1中使用的案例就是注入外部Bean的方式。
- 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="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标签。
- spring-inner-bean.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userServiceBean" class="com.powernode.spring6.service.UserService"> <property name="userDao"> <!--在property标签中使用嵌套的bean标签,这就是内部Bean--> <bean class="com.powernode.spring6.dao.UserDao"/> </property> </bean> </beans>
- 测试程序
@Test public void testInnerBean(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-inner-bean.xml"); UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class); userServiceBean.saveUser(); }
- 测试结果
-
这种方式作为了解。
4.3.3 注入简单类型
-
我们之前在进行注入的时候,对象的属性是另一个对象。
-
那如果对象的属性是int类型呢?
public class User{ private int age; public void setAge(int age){ this.age = age; } }
- 可以通过set注入的方式给该属性赋值吗?
- 当然可以。因为只要能够调用set方法就可以给属性赋值。
- 可以通过set注入的方式给该属性赋值吗?
-
编写程序给一个User对象的age属性赋值20:
- 第一步:定义User类,提供age属性,提供age属性的setter方法。
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.User * @date 2022/11/9 * @since 1.0 */ public class User { private int age; @Override public String toString() { return "User{" + "age=" + age + '}'; } public void setAge(int age) { this.age = age; } }
- 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.bean.User"> <!--<property name="age" value="20"/>--> <property name="age"> <!--如果像这种int类型的属性,我们称为简单类型,这种简单类型在注入的时候要使用value属性,不能使用ref--> <value>20</value> </property> </bean> </beans>
- 测试程序
@Test public void testInnerBean(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-inner-bean.xml"); UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class); userServiceBean.saveUser(); }
- 测试结果
- 需要特别注意:如果给简单类型赋值,使用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) || 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
- 另外还包括以上简单值类型对应的数组类型。
- 通过源码分析得知,简单类型包括:
-
经典案例:给数据源的属性注入值:
假设我们现在要自己手写一个数据源,我们都知道所有的数据源都要实现javax.sql.DataSource接口,并且数据源中应该有连接数据库的信息,例如:driver、url、username、password等。
- com.powernode.spring6.bean.MyDataSource
package com.powernode.spring6.bean; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.MyDataSource * @date 2022/11/9 * @since 1.0 */ public class MyDataSource implements DataSource { private String driver; private String url; private String username; private String password; public void setDriver(String driver) { this.driver = driver; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } @Override public Connection getConnection() throws SQLException { return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } }
- 我们给driver、url、username、password四个属性分别提供了setter方法,我们可以使用spring的依赖注入完成数据源对象的创建和属性的赋值吗?看配置文件: spring-datasource.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="myDataSourceBean" class="com.powernode.spring6.bean.MyDataSource"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> </beans>
- 测试程序
@Test public void testMyDataSource(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-datasource.xml"); MyDataSource myDataSourceBean = applicationContext.getBean("myDataSourceBean", MyDataSource.class); System.out.println(myDataSourceBean); }
- 测试结果
-
接下来,我们编写一个程序,把所有的简单类型全部测试一遍:
- com.powernode.spring6.bean.A
package com.powernode.spring6.bean; import java.net.URI; import java.net.URL; import java.time.LocalDate; import java.util.Date; import java.util.Locale; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.A * @date 2022/11/9 * @since 1.0 */ public class A { private byte b; private short s; private int i; private long l; private float f; private double d; private boolean flag; private char c; private Byte b1; private Short s1; private Integer i1; private Long l1; private Float f1; private Double d1; private Boolean flag1; private Character c1; private String str; private Date date; private Season season; private URI uri; private URL url; private LocalDate localDate; private Locale locale; private Class clazz; @Override public String toString() { return "A{" + "b=" + b + ", s=" + s + ", i=" + i + ", l=" + l + ", f=" + f + ", d=" + d + ", flag=" + flag + ", c=" + c + ", \nb1=" + b1 + ", s1=" + s1 + ", i1=" + i1 + ", l1=" + l1 + ", f1=" + f1 + ", d1=" + d1 + ", flag1=" + flag1 + ", c1=" + c1 + ", \nstr='" + str + '\'' + ", date=" + date + ", season=" + season + ", uri=" + uri + ", url=" + url + ", \nlocalDate=" + localDate + ", locale=" + locale + ", clazz=" + clazz + '}'; } public void setB(byte b) { this.b = b; } public void setS(short s) { this.s = s; } public void setI(int i) { this.i = i; } public void setL(long l) { this.l = l; } public void setF(float f) { this.f = f; } public void setD(double d) { this.d = d; } public void setFlag(boolean flag) { this.flag = flag; } public void setC(char c) { this.c = c; } public void setB1(Byte b1) { this.b1 = b1; } public void setS1(Short s1) { this.s1 = s1; } public void setI1(Integer i1) { this.i1 = i1; } public void setL1(Long l1) { this.l1 = l1; } public void setF1(Float f1) { this.f1 = f1; } public void setD1(Double d1) { this.d1 = d1; } public void setFlag1(Boolean flag1) { this.flag1 = flag1; } public void setC1(Character c1) { this.c1 = c1; } public void setStr(String str) { this.str = str; } public void setDate(Date date) { this.date = date; } public void setSeason(Season season) { this.season = season; } public void setUri(URI uri) { this.uri = uri; } public void setUrl(URL url) { this.url = url; } public void setLocalDate(LocalDate localDate) { this.localDate = localDate; } public void setLocale(Locale locale) { this.locale = locale; } public void setClazz(Class clazz) { this.clazz = clazz; } }
- spring-all-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="aBean" class="com.powernode.spring6.bean.A"> <property name="b" value="1"/> <property name="s" value="1"/> <property name="i" value="1"/> <property name="l" value="1"/> <property name="f" value="1"/> <property name="d" value="1"/> <property name="flag" value="false"/> <property name="c" value="a"/> <property name="b1" value="2"/> <property name="s1" value="2"/> <property name="i1" value="2"/> <property name="l1" value="2"/> <property name="f1" value="2"/> <property name="d1" value="2"/> <property name="flag1" value="true"/> <property name="c1" value="a"/> <property name="str" value="zhangsan"/> <!--注意:value后面的日期字符串格式不能随便写,必须是Date对象toString()方法执行的结果。--> <!--如果想使用其他格式的日期字符串,就需要进行特殊处理了。--> <property name="date" value="Fri Sep 30 00:00:00 CST 2022"/> <property name="season" value="WINTER"/> <property name="uri" value="/save.do"/> <!--spring6之后,会自动检查url是否有效,如果无效会报错。--> <property name="url" value="http://www.baidu.com"/> <property name="localDate" value="EPOCH"/> <!--java.util.Locale 主要在软件的本地化时使用。它本身没有什么功能,更多的是作为一个参数辅助其他方法完成输出的本地化。--> <property name="locale" value="CHINESE"/> <property name="clazz" value="java.lang.String"/> </bean> </beans>
- 测试程序
@Test public void testA(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-all-simple-type.xml"); Object aBean = applicationContext.getBean("aBean", A.class); System.out.println(aBean); }
- 测试结果
-
需要注意的是:
- 如果把Date当做简单类型的话,日期字符串格式不能随便写。格式必须符合Date的toString()方法格式。显然这就比较鸡肋了。如果我们提供一个这样的日期字符串:2010-10-11,在这里是无法赋值给Date类型的属性的。
- spring6之后,当注入的是URL,那么这个url字符串是会进行有效性检测的。如果是一个存在的url,那就没问题。如果不存在则报错。
4.3.4 级联属性赋值(了解)
-
com.powernode.spring6.bean.Clazz
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Clazz * @date 2022/11/9 * @since 1.0 */ public class Clazz { private String name; @Override public String toString() { return "Clazz{" + "name='" + name + '\'' + '}'; } public Clazz() { } public Clazz(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
- com.powernode.spring6.bean.Student
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Student * @date 2022/11/9 * @since 1.0 */ public class Student { private String name; private Clazz clazz; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", clazz=" + clazz + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Clazz getClazz() { return clazz; } public void setClazz(Clazz clazz) { this.clazz = clazz; } public Student(String name, Clazz clazz) { this.name = name; this.clazz = clazz; } public Student() { } }
- spring-cascade.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="clazzBean" class="com.powernode.spring6.bean.Clazz"/> <bean id="studentBean" class="com.powernode.spring6.bean.Student"> <property name="name" value="张三"/> <!--要点1:以下两行配置的顺序不能颠倒--> <property name="clazz" ref="clazzBean"/> <!--要点2:clazz属性必须有getter方法--> <property name="clazz.name" value="高三一班"/> </bean> </beans>
- 测试程序
@Test public void testCascade(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-cascade.xml"); Student studentBean = applicationContext.getBean("studentBean", Student.class); System.out.println(studentBean); }
- 测试结果
-
要点:
- 在spring配置文件中,如上,注意顺序。
- 在spring配置文件中,clazz属性必须提供getter方法
4.3.5 注入数组
-
当数组中的元素是简单类型:
- com.powernode.spring6.bean.Person
package com.powernode.spring6.bean; import java.util.Arrays; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Person * @date 2022/11/9 * @since 1.0 */ public class Person { private String[] favoriteFoods; @Override public String toString() { return "Person{" + "favoriteFoods=" + Arrays.toString(favoriteFoods) + '}'; } public void setFavoriteFoods(String[] favoriteFoods) { this.favoriteFoods = favoriteFoods; } }
- spring-array-simple.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="personBean" class="com.powernode.spring6.bean.Person"> <property name="favoriteFoods"> <array> <value>鸡排</value> <value>汉堡</value> <value>鹅肝</value> </array> </property> </bean> </beans>
- 测试程序
@Test public void testArraySimple(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array-simple.xml"); Person personBean = applicationContext.getBean("personBean", Person.class); System.out.println(personBean);
- 测试结果
-
当数组中的元素是非简单类型:一个订单中包含多个商品。
- com.powernode.spring6.bean.Goods
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Goods * @date 2022/11/9 * @since 1.0 */ public class Goods { private String name; @Override public String toString() { return "Goods{" + "name='" + name + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Goods(String name) { this.name = name; } public Goods() { } }
- com.powernode.spring6.bean.Order
package com.powernode.spring6.bean; import java.util.Arrays; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Order * @date 2022/11/9 * @since 1.0 */ public class Order { // 一个订单中有多个商品 private Goods[] goods; @Override public String toString() { return "Order{" + "goods=" + Arrays.toString(goods) + '}'; } public void setGoods(Goods[] goods) { this.goods = goods; } public Order() { } public Order(Goods[] goods) { this.goods = goods; } }
- spring-array.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="goodsBean1" class="com.powernode.spring6.bean.Goods"> <property name="name" value="香蕉"/> </bean> <bean id="goodsBean2" class="com.powernode.spring6.bean.Goods"> <property name="name" value="葡萄"/> </bean> <bean id="orderBean" class="com.powernode.spring6.bean.Order"> <property name="goods"> <array> <!--这里使用ref标签即可--> <ref bean="goodsBean1" /> <ref bean="goodsBean2" /> </array> </property> </bean> </beans>
- 测试程序
@Test public void testArray(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml"); Order orderBean = applicationContext.getBean("orderBean", Order.class); System.out.println(orderBean); }
- 测试结果
-
要点:
- 如果数组中是简单类型,使用value标签。
- 如果数组中是非简单类型,使用ref标签。
4.3.6 注入List集合
-
List集合:有序可重复
- com.powernode.spring6.bean.People
package com.powernode.spring6.bean; import java.util.List; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.People * @date 2022/11/9 * @since 1.0 */ public class People { // 一个人有多个名字 private List<String> names; @Override public String toString() { return "People{" + "names=" + names + '}'; } public void setNames(List<String> names) { this.names = names; } }
- spring-collection.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="peopleBean" class="com.powernode.spring6.bean.People"> <property name="names"> <!--list集合有序可重复--> <list> <value>铁锤</value> <value>张三</value> <value>张三</value> <value>张三</value> <value>狼</value> </list> </property> </bean> </beans>
- 测试程序
@Test public void testCollection(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml"); People peopleBean = applicationContext.getBean("peopleBean", People.class); System.out.println(peopleBean); }
- 测试结果
-
注意:注入List集合的时候使用list标签,如果List集合中是简单类型使用value标签,反之使用ref标签。
4.3.7 注入Set集合
-
Set集合:无序不可重复
-
com.powernode.spring6.bean.People
package com.powernode.spring6.bean; import java.util.List; import java.util.Set; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.People * @date 2022/11/9 * @since 1.0 */ public class People { // 一个人有多个名字 private List<String> names; // 一个人有多个电话 private Set<String> phones; @Override public String toString() { return "People{" + "names=" + names + ", phones=" + phones + '}'; } public void setPhones(Set<String> phones) { this.phones = phones; } public void setNames(List<String> names) { this.names = names; } }
-
spring-collection.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="peopleBean" class="com.powernode.spring6.bean.People"> <property name="names"> <!--list集合有序可重复--> <list> <value>铁锤</value> <value>张三</value> <value>张三</value> <value>张三</value> <value>狼</value> </list> </property> <property name="phones"> <!--Set集合:无序不可重复--> <set> <!--非简单类型可以使用ref,简单类型使用value--> <value>110</value> <value>110</value> <value>120</value> <value>120</value> <value>119</value> <value>119</value> </set> </property> </bean> </beans>
-
测试程序
@Test public void testCollection(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml"); People peopleBean = applicationContext.getBean("peopleBean", People.class); System.out.println(peopleBean); }
- 测试结果
-
-
要点:
- 使用标签
- set集合中元素是简单类型的使用value标签,反之使用ref标签。
4.3.8 注入Map集合
-
com.powernode.spring6.bean.People
package com.powernode.spring6.bean; import java.util.List; import java.util.Map; import java.util.Set; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.People * @date 2022/11/9 * @since 1.0 */ public class People { // 一个人有多个名字 private List<String> names; // 一个人有多个电话 private Set<String> phones; // 一个人有多个地址 private Map<Integer, String> addrs; @Override public String toString() { return "People{" + "names=" + names + ", phones=" + phones + ", addrs=" + addrs + '}'; } public void setAddrs(Map<Integer, String> addrs) { this.addrs = addrs; } public void setPhones(Set<String> phones) { this.phones = phones; } public void setNames(List<String> names) { this.names = names; } }
-
spring-collection.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="peopleBean" class="com.powernode.spring6.bean.People"> <property name="names"> <!--list集合有序可重复--> <list> <value>铁锤</value> <value>张三</value> <value>张三</value> <value>张三</value> <value>狼</value> </list> </property> <property name="phones"> <!--Set集合:无序不可重复--> <set> <!--非简单类型可以使用ref,简单类型使用value--> <value>110</value> <value>110</value> <value>120</value> <value>120</value> <value>119</value> <value>119</value> </set> </property> <property name="addrs"> <map> <!--如果key不是简单类型,使用 key-ref 属性--> <!--如果value不是简单类型,使用 value-ref 属性--> <entry key="1" value="北京大兴区"/> <entry key="2" value="上海浦东区"/> <entry key="3" value="深圳宝安区"/> </map> </property> </bean> </beans>
-
测试程序
@Test
public void testCollection(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
People peopleBean = applicationContext.getBean("peopleBean", People.class);
System.out.println(peopleBean);
}
- 测试结果
- 要点:
- 使用<map>标签
- 如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
- 如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。
4.3.9 注入Properties
-
java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
-
com.powernode.spring6.bean.People
package com.powernode.spring6.bean;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author shanglinsong
* @version 1.0
* @className com.powernode.spring6.bean.People
* @date 2022/11/9
* @since 1.0
*/
public class People {
// 一个人有多个名字
private List<String> names;
// 一个人有多个电话
private Set<String> phones;
// 一个人有多个地址
private Map<Integer, String> addrs;
private Properties properties;
@Override
public String toString() {
return "People{" +
"names=" + names +
", phones=" + phones +
", addrs=" + addrs +
", \nproperties=" + properties +
'}';
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setAddrs(Map<Integer, String> addrs) {
this.addrs = addrs;
}
public void setPhones(Set<String> phones) {
this.phones = phones;
}
public void setNames(List<String> names) {
this.names = names;
}
}
- spring-collection.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="peopleBean" class="com.powernode.spring6.bean.People">
<property name="names">
<!--list集合有序可重复-->
<list>
<value>铁锤</value>
<value>张三</value>
<value>张三</value>
<value>张三</value>
<value>狼</value>
</list>
</property>
<property name="phones">
<!--Set集合:无序不可重复-->
<set>
<!--非简单类型可以使用ref,简单类型使用value-->
<value>110</value>
<value>110</value>
<value>120</value>
<value>120</value>
<value>119</value>
<value>119</value>
</set>
</property>
<property name="addrs">
<map>
<!--如果key不是简单类型,使用 key-ref 属性-->
<!--如果value不是简单类型,使用 value-ref 属性-->
<entry key="1" value="北京大兴区"/>
<entry key="2" value="上海浦东区"/>
<entry key="3" value="深圳宝安区"/>
</map>
</property>
<property name="properties">
<!--注入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>
</bean>
</beans>
- 测试程序
@Test
public void testCollection(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
People peopleBean = applicationContext.getBean("peopleBean", People.class);
System.out.println(peopleBean);
}
- 测试结果
- 要点:
- 使用<props>标签嵌套<prop>标签完成。
4.3.10 注入null和空字符串
-
注入空字符串使用:<value/> 或者 value=“”
-
注入null使用:<null/> 或者 不为该属性赋值
-
我们先来看一下,怎么注入空字符串。
-
com.powernode.spring6.bean.Vip
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Vip * @date 2022/11/9 * @since 1.0 */ public class Vip { private String email; @Override public String toString() { return "Vip{" + "email='" + email + '\'' + '}'; } public void setEmail(String email) { this.email = email; } }
- spring-null.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"> <!--空串的第一种方式--> <!--<property name="email" value=""/>--> <!--空串的第二种方式--> <property name="email"> <value/> </property> </bean> </beans>
- 测试程序
@Test public void testNull(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-null.xml"); Vip vipBean = applicationContext.getBean("vipBean", Vip.class); System.out.println(vipBean); }
- 测试结果
-
-
怎么注入null呢?
-
第一种方式:不给属性赋值
- spring-null.xml
<bean id="vipBean" class="com.powernode.spring6.bean.Vip" />
- 测试结果
-
第二种方式:使用<null/>
- spring-null.xml
<bean id="vipBean" class="com.powernode.spring6.bean.Vip"> <property name="email"> <null/> </property> </bean>
- 测试结果
-
4.3.11 注入的值中含有特殊符号
-
XML中有5个特殊字符,分别是:<、>、'、"、&
- 以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。
-
解决方案包括两种:
- 第一种:特殊符号使用转义字符代替。
- 第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。
-
5个特殊字符对应的转义字符分别是:
特殊字符 转义字符 > > < < ’ ' " " & & -
先使用转义字符来代替:
- com.powernode.spring6.bean.Math
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Math * @date 2022/11/9 * @since 1.0 */ public class Math { private String result; @Override public String toString() { return "Math{" + "result='" + result + '\'' + '}'; } public void setResult(String result) { this.result = result; } }
- spring-special.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="mathBean" class="com.powernode.spring6.bean.Math"> <property name="result" value="2 < 3"/> </bean> </beans>
- 测试程序
@Test public void testSpecial(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-special.xml"); Math mathBean = applicationContext.getBean("mathBean", Math.class); System.out.println(mathBean);
- 测试结果
-
我们再来使用CDATA方式:
- spring-special.xml
<bean id="mathBean" class="com.powernode.spring6.bean.Math"> <property name="result"> <value><![CDATA[2 < 3]]]></value> </property> </bean>
- 测试结果
-
注意:使用CDATA时,不能使用value属性,只能使用value标签。
4.4 p命名空间注入
-
目的:简化配置。
-
使用p命名空间注入的前提条件包括两个:
-
第一:在XML头部信息中添加p命名空间的配置信息:xmlns:p=“http://www.springframework.org/schema/p”
-
第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
- com.powernode.spring6.bean.Customer
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.Customer * @date 2022/11/9 * @since 1.0 */ public class Customer { private String name; private int age; @Override public String toString() { return "Customer{" + "name='" + name + '\'' + ", age=" + age + '}'; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
- spring-p.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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="customerBean" class="com.powernode.spring6.bean.Customer" p:name="zhangsan" p:age="20"/> </beans>
- 测试程序
@Test public void testP(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml"); Customer customerBean = applicationContext.getBean("customerBean", Customer.class); System.out.println(customerBean); }
- 测试结果
-
-
把setter方法去掉:
-
所以p命名空间实际上是对set注入的简化。
4.5 c命名空间注入
-
c命名空间是简化构造方法注入的。
-
使用c命名空间的两个前提条件:
-
第一:需要在xml配置文件头部添加信息:xmlns:c=“http://www.springframework.org/schema/c”
-
第二:需要提供构造方法。
- com.powernode.spring6.bean.MyTime
package com.powernode.spring6.bean; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.MyTime * @date 2022/11/9 * @since 1.0 */ public class MyTime { private int year; private int month; private int day; @Override public String toString() { return "MyTime{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } public MyTime(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } }
- spring-c.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" 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="myTimeBean" class="com.powernode.spring6.bean.MyTime" c:year="1970" c:month="1" c:day="1"/>--> <bean id="myTimeBean" class="com.powernode.spring6.bean.MyTime" c:_0="2008" c:_1="8" c:_2="8"/> </beans>
- 测试程序
@Test public void testC(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml"); MyTime myTimeBean = applicationContext.getBean("myTimeBean", MyTime.class); System.out.println(myTimeBean); }
- 测试结果
-
-
把构造方法注释掉:
-
所以,c命名空间是依靠构造方法的。
注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。
4.6 util命名空间
-
使用util命名空间可以让配置复用。
-
使用util命名空间的前提是:在spring配置文件头部添加配置信息。如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> </beans>
-
com.powernode.spring6.bean.MyDataSource1
package com.powernode.spring6.bean; import java.util.Properties; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.MyDataSource1 * @date 2022/11/9 * @since 1.0 */ public class MyDataSource1 { private Properties properties; @Override public String toString() { return "MyDataSource1{" + "properties=" + properties + '}'; } public void setProperties(Properties properties) { this.properties = properties; } }
-
com.powernode.spring6.bean.MyDataSource2
package com.powernode.spring6.bean; import java.util.Properties; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.MyDataSource2 * @date 2022/11/9 * @since 1.0 */ public class MyDataSource2 { private Properties properties; @Override public String toString() { return "MyDataSource2{" + "properties=" + properties + '}'; } public void setProperties(Properties properties) { this.properties = properties; } }
-
spring-util.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:properties id="prop"> <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">000000</prop> </util:properties> <bean id="myDataSource1" class="com.powernode.spring6.bean.MyDataSource1"> <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">000000</prop> </props> </property> </bean> <bean id="myDataSource2" class="com.powernode.spring6.bean.MyDataSource2"> <property name="properties" ref="prop" /> </bean> </beans>
-
测试程序
@Test public void testUtil(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml"); MyDataSource1 myDataSource1 = applicationContext.getBean("myDataSource1", MyDataSource1.class); System.out.println(myDataSource1); MyDataSource2 myDataSource2 = applicationContext.getBean("myDataSource2", MyDataSource2.class); System.out.println(myDataSource2); }
-
测试结果
4.7 基于XML的自动装配
- Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。
4.7.1 根据名称自动装配
-
com.powernode.spring6.dao.UserDao
package com.powernode.spring6.dao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.dao.UserDao * @date 2022/11/8 * @since 1.0 */ public class UserDao { private static final Logger logger = LoggerFactory.getLogger(UserDao.class); public void insert(){ // System.out.println("正在保存用户数据。。。"); logger.info("数据库正在保存用户信息。。。"); } }
-
com.powernode.spring6.service.UserService
package com.powernode.spring6.service; import com.powernode.spring6.dao.UserDao; import com.powernode.spring6.dao.VipDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.UserService * @date 2022/11/8 * @since 1.0 */ public class UserService { private UserDao userDao; // 这个set方法非常关键 public void setAaa(UserDao userDao) { this.userDao = userDao; } /** * 保存用户信息到数据库 */ public void saveUser(){ userDao.insert(); } }
-
spring-autowire.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="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/> <bean id="aaa" class="com.powernode.spring6.dao.UserDao"/> </beans>
-
这个配置起到关键作用:
- UserService Bean中需要添加autowire=“byName”,表示通过名称进行装配。
- UserService类中有一个UserDao属性,而UserDao属性的名字是aaa,对应的set方法是setAaa(),正好和UserDao Bean的id是一样的。这就是根据名称自动装配。
-
测试程序
@Test public void testAutowireByName(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml"); UserService userService = applicationContext.getBean("userService", UserService.class); userService.saveUser(); }
-
测试结果
-
我们来测试一下,byName装配是和属性名有关还是和set方法名有关系:
- com.powernode.spring6.service.UserService
package com.powernode.spring6.service; import com.powernode.spring6.dao.UserDao; import com.powernode.spring6.dao.VipDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.UserService * @date 2022/11/8 * @since 1.0 */ public class UserService { private UserDao userDao; // 这个set方法非常关键 /*public void setAaa(UserDao userDao) { this.userDao = userDao; }*/ // 修改set方法名 public void setDao(UserDao userDao) { this.userDao = userDao; } /** * 保存用户信息到数据库 */ public void saveUser(){ userDao.insert(); } }
- 测试结果
-
通过测试得知,aaa属性并没有赋值成功。也就是并没有装配成功。
-
我们将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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/> <!--<bean id="aaa" class="com.powernode.spring6.dao.UserDao"/>--> <bean id="dao" class="com.powernode.spring6.dao.UserDao"/> </beans>
-
测试结果
-
这说明,如果根据名称装配(byName),底层会调用set方法进行注入。
-
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。
4.7.2 根据类型自动装配
-
com.powernode.spring6.dao.AccountDao
package com.powernode.spring6.dao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.dao.AccountDao * @date 2022/11/9 * @since 1.0 */ public class AccountDao { private static final Logger logger = LoggerFactory.getLogger(AccountDao.class); public void insert(){ logger.info("正在保护账户信息。。。"); } }
-
com.powernode.spring6.service.AccountService
package com.powernode.spring6.service; import com.powernode.spring6.dao.AccountDao; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.service.AccountService * @date 2022/11/9 * @since 1.0 */ public class AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void save(){ accountDao.insert(); } }
-
spring-autowire.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"> <!--byType表示根据类型自动装配--> <bean id="accountService" class="com.powernode.spring6.service.AccountService" autowire="byType"/> <bean class="com.powernode.spring6.dao.AccountDao"/> </beans>
-
测试程序
@Test public void testAutowireByType(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml"); AccountService accountService = applicationContext.getBean("accountService", AccountService.class); accountService.save(); }
-
测试结果
-
我们把AccountService中的set方法注释掉,再执行:
-
可以看到无论是byName还是byType,在装配的时候都是基于set方法的。所以set方法是必须要提供的。提供构造方法是不行的,大家可以测试一下。这里就不再赘述。
-
如果byType,根据类型装配时,如果配置文件中有两个类型一样的bean会出现什么问题呢?
-
spring-autowire.xml
<bean id="accountService" class="com.powernode.spring6.service.AccountService" autowire="byType"/> <bean id="x" class="com.powernode.spring6.dao.AccountDao"/> <bean id="y" class="com.powernode.spring6.dao.AccountDao"/>
-
测试结果
-
-
测试结果说明了,当byType进行自动装配的时候,配置文件中某种类型的Bean必须是唯一的,不能出现多个。
4.8 spring引入外部属性配置文件
-
我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中吗,这样用户修改起来会更加的方便。
- 当然可以。
-
第一步:写一个数据源类,提供相关属性。
package com.powernode.spring6.bean; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.bean.MyDataSource * @date 2022/11/9 * @since 1.0 */ public class MyDataSource implements DataSource { private String driver; private String url; private String username; private String password; public void setDriver(String driver) { this.driver = driver; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } @Override public Connection getConnection() throws SQLException { return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } }
-
第二步:在类路径下新建jdbc.properties文件,并配置信息。
# 不加jdbc. , username的root会自动定位到Administrator jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring6 jdbc.username=root jdbc.password=000000
-
第三步:在spring配置文件中引入context命名空间。
spring-properties.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
-
第四步:在spring中配置使用jdbc.properties文件。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="jdbc.properties"/> <bean id="dataSource" class="com.powernode.spring6.bean.MyDataSource"> <property name="driver" value="${jdbc.driver}" /> <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 dataSource = applicationContext.getBean("dataSource", MyDataSource.class); System.out.println(dataSource); }
-
测试结果
五、Bean的作用域
5.1 singleton
-
默认情况下,Spring的IoC容器创建的Bean对象是单例的。来测试一下:
- com.powernode.spring6.beans.SpringBean
package com.powernode.spring6.beans; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.beans.SpringBean * @date 2022/11/14 * @since 1.0 */ public class SpringBean { }
- spring-scope.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="sb" class="com.powernode.spring6.beans.SpringBean"/> </beans>
- 测试程序
package com.powernode.spring6.test; import com.powernode.spring6.beans.SpringBean; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.test.SpringScopeTest * @date 2022/11/14 * @since 1.0 */ public class SpringScopeTest { @Test public void testScope(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml"); SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb1); SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb2); } }
- 测试结果
- 通过测试得知:Spring的IoC容器中,默认情况下,Bean对象是单例的。
-
这个对象在什么时候创建的呢?可以为SpringBean提供一个无参数构造方法,测试一下,如下:
- com.powernode.spring6.beans.SpringBean
package com.powernode.spring6.beans; /** * @author shanglinsong * @version 1.0 * @className com.powernode.spring6.beans.SpringBean * @date 2022/11/14 * @since 1.0 */ public class SpringBean { public SpringBean() { System.out.println("SpringBean的无参数构造方法执行。"); } }
- 测试程序,将测试程序中getBean()所在行代码注释掉:
@Test public void testScope(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml"); }
- 测试结果
- 通过测试得知,默认情况下,Bean对象的创建是在初始化Spring上下文的时候就完成的。
5.2 prototype
-
如果想让Spring的Bean对象以多例的形式存在,可以在bean标签中指定scope属性的值为:prototype,这样Spring会在每一次执行getBean()方法的时候创建Bean对象,调用几次则创建几次。
- spring-scope.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="sb" class="com.powernode.spring6.beans.SpringBean" scope="prototype"/> </beans>
- 测试程序
@Test public void testScope(){ /** * 1. Spring默认情况下是如何管理这个Bean的: * 默认情况下Bean是单例的。(单例:singleton) * 在Spring上下文初始化的时候实例化。 * 每一次调用getBean()方法的时候,都返回那个单例的对象。 * * 2. 当将bean的scope属性设置为prototype: * bean是多例的。 * spring上下文初始化的时候,并不会初始化这些prototype的bean。 * 每一次调用getBean()方法的时候,实例化该bean对象。 * prototype翻译为:原型。 */ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml"); SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb1); SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb2); }
- 测试结果
-
我们可以把测试代码中的getBean()方法所在行代码注释掉:
- 测试结果
- 可以看到这一次在初始化Spring上下文的时候,并没有创建Bean对象。
-
scope如果没有配置,它的默认值是什么呢?
- 默认值是singleton,单例的。
5.3 其它scope
-
scope属性的值不止两个,它一共包括8个选项:
- singleton:默认的,单例。
- prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象。或每次注入的时候都是新对象。
- request:一个请求对应一个Bean。仅限于在WEB应用中使用。
- session:一个会话对应一个Bean。仅限于在WEB应用中使用。
- global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
- application:一个应用对应一个Bean。仅限于在WEB应用中使用。
- websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
- 自定义scope:很少使用。
-
接下来咱们自定义一个Scope,线程级别的Scope,在同一个线程中,获取的Bean都是同一个。跨线程则是不同的对象:(以下内容作为了解)
-
第一步:自定义Scope。(实现Scope接口)
-
- spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可以直接用。
-
第二步:将自定义的Scope注册到Spring容器中。
-
spring-scope.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 class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="myThread"> <bean class="org.springframework.context.support.SimpleThreadScope"/> </entry> </map> </property> </bean> <!--<bean id="sb" class="com.powernode.spring6.beans.SpringBean"/>--> <!--<bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="prototype"/>--> <!--<bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="singleton"/>--> <bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="myThread"/> </beans>
- 测试程序
@Test public void testCustomScope(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml"); SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb1); SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb2); // 启动线程 new Thread(new Runnable() { @Override public void run() { SpringBean sb3 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb3); SpringBean sb4 = applicationContext.getBean("sb", SpringBean.class); System.out.println(sb4); } }).start(); }
- 测试结果
-