Java Spring Framework笔记
1.Spring概述
1.1 什么是Spring
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
1.2 Spring的优点
- 方便解耦,简化开发 (高内聚低耦合)
Spring就是一个大工厂(容器),可以将所有对象创建和依赖关系维护,交给Spring管理
spring工厂是用于生成bean - AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能 - 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程 - 方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序 - 方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持 - 降低JavaEE API的使用难度
Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
1.3 Spring框架的体系结构和版本
Spring框架至今已集成了20多个模块,这些模块分布在以下模块中:
- 核心容器(Core Container)
- 数据访问/集成(Data Access/Integration)层
- Web层
- AOP(Aspect Oriented Programming)模块
- 植入(Instrumentation)模块
- 消息传输(Messaging)
- 测试(Test)模块
Spring体系结构如下图:
现在Spring版本最新的是5.3那么现在针对于5.3学习肯定是不吃香的,但是现在去学Spring4那估计也没有人这些这么做,所以我们就取一个中间值,选择5版本发行之后更新的下一个版本,一来已经有很多大佬们使用过有过一些经验,二来我们在了解了低版本但是依然是Spring5之内的内容的话,再去相对着学高一点版本效率非常高。
Spring5发布时说明,Spring5已经是基于JDK 8来构建的了,所以 不懂lamba表达式,也就看不懂源码,还有就是Spring5要求的tomcat版本也需要到8.5+,servlet版本时基于3.1的 但是支持servlet4.0
2.Spring IOC容器
想要学好这个框架你去看一些视频看一些别人的笔记是绝对不够的,这种框架的最规范文档就是官方文档
这里的传送门直接传送到Spring的core部分
打开之后的界面是这样的
那么可以看到第一个内容就是 IOC container 也就是IOC容器
2.1 IOC容器理解
IOC即为**(Inversion of Control)** 控制反转
那么针对这个Inversion of Contro我提出了四个问题 :
- 何为控制
- 谁控制谁
- 何为反转
- 反转益处
首先来说既然是控制反转,那则必须有控制方面的内容,那在java里面的控制其实也逃不过类和对象,一个类或一个在运行时需要对另一个类的对象,这就是控制,那么谁控制谁这个问题也就解决了,是类或对象来控制所需要的对象。
如同 A对象在运行时需要B对象那么就会控制B对象使B对象进行某些操作那么转化成代码
class A {
private B b;
A(B b){
this.b = b;
}
}
class B{
B(){};
public void saveAccount(){
System.out.println('账户保存了');
};
}
class TestRun(){
// new A 时候自动得到了b对象并且获得了控制权
B b = new B();
A a = new A();
}
从代码演示可以看到 在A一经创建之后,就用到了B类进行保存账户,那么现在B的的对象其实就在被A来控制 。而且A与B之间产生了一种耦合关系。这里我说耦合可能有一点牵强,因为只有两个类,但是如果我们有10个类互相耦合,互相控制那么随便可能动一点代码就可能造成巨大的工作量来修改项目。那么也就引出了何为反转这个问题。
那么反转究竟是什么,出现在这里大家都懂,这个反转肯定是上述问题的解决方案,那么反转其实就是引入一个容器,由这个所有类与对象的执行权上缴容器,由这个容器来处理,各个类之间的耦合关系
继上述代码之后 分析ioc容器模式:
当A运行时无需传入B对象而是传入IOC容器对象,概况为A齿轮转动时调动IOC齿轮转动
那么IOC需要使B来转动,其实分析也就是给A对象传入B对象,那IOC里是不是必须需要有一个B对象呢
答案肯定是不是,如果它必须要设置一个B对象那么这个容器本质没什么作用,我们可以让A动的时候IOC动,A让IOC去转动谁IOC就去转动谁,并不是A去转动IOC容器内的对象。那么IOC容器里就可以存放B对象可能还有C对象等的控制信息,那其实到我们java里面就是全限定类名,有了全限定类名我们就可以通过反射来进行对象创建而无需进行实例化对象了。
那么总结一下控制反转其实就是A对象本来是需要控制or创建B对象的,但是现在A对象需要B对象时IOC容器就会把对象送过来,A于B之间没了联系,那么A就变成了被动,抽象到现实世界,当你需要女朋友时,你无需自己去追,IOC会根据你得需求创建女朋友,在你需要时送给你,那么你是不是得在遗憾下,懊恼下欣然接受这个女朋友呢? 这其实就是控制反转, 对于A来说由主动变成了被动。同时也极大程度地解决了耦合问题,让类与类之间无需直接地控制
2.2 Spring IOC入门
2.2.1 开发环境准备
开发环境我选择IDEA+maven,来创建Spring项目
搭建好了maven工程我们还需要在maven中引入Spring的依赖包
<?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.hbsi</groupId>
<artifactId>com.hbsi.Spring_01iocContainer</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- spring依赖 5.0.2版本 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- log4j打印日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>
要用的依赖已经全部配置完成了接下来就正式进入Spring的学习了
他说在 Spring 中,构成应用程序主干并由 Spring IoC容器管理的对象称为bean。Bean 是实例化、组装或以其他方式由 Spring IoC 容器管理的对象。否则,Bean 只是应用程序中的许多对象之一。Bean 及其之间的依赖项反映在容器使用的配置元数据中。
2.2.2 配置元数据(configuration metadata)?
这里我还是打开翻译找他中文的解释吧,其实我是怕看中文的技术文档有些奇怪,但其实以我的翻译水平可能自己翻译的更奇怪。。
这些个豆其实是bean搞得其实我心态有点炸,但没有办法 想偷懒就得承受这个痛苦
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nu7fvxT6-1592203079236)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200601160354769.png)]
提到ApplicationContext接口,负责实例化配置组装bean,需要通过读取配置元数据所以我们还是得先找到配置元数据的内容,有了配置再去考虑怎么用这个接口
那么他既然这么慷慨,我肯定直接直接copy了,那其实这个xml文件的头信息,有助于IDEA识别并加载相应的标签信息,所以这个头文件一定要写对。
找到我的项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDzkLxdG-1592203079237)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200601194641338.png)]
我已经新建好了Bean的配置信息,beans标签下有bean,bean的id是唯一标识,class顾名思义也就是全限定类名
然后我在java包下创建一些类和接口用来测试bean标签
我这里有一个类 一个接口那么就分别试一试呗
这个IDEA还是非常智能的提示我们非抽象的bean不能使用接口
那我们还是先不写接口了
然后现在我在test包下新建一个测试类 用于测试如何导入bean
那既然我们需要获取bean,那么就肯定是需要初始化bean的容器,而文档的下一节正好就是初始化容器
我们还是在测试类里原样复制一下,在文档中他给出了两个xml然后经过我的仔细比对,发现这两个xml是完全没有任何联系的,可能将两个xml中的类装载进容器吧
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-06-01 19:54
* Notes : 测试利用bean导入class
*/
public class BeanTest {
@Test
public void TestImportClass(){
//根据配置文件初始化容器
ApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
}
}
现在我们获取到了容器· ap对象要怎么从容器中获得bean呢,
这个文档的下一节又~是我们需要的继续copy
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLg9lcyV-1592203079239)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200601200228401.png)]
然后我发现getBean方法有这么多的重载,其实分析一下,第一个传入字符串参数的应该就是传入bean的id值,返回出一个object类型
没有问题,跟我们设想的一样,那么第二个参数是一个泛型class可能就是需要我们传入全限定类名的喽
也没什么问题,那么我们其他的就不测试了呗暂时用这两个就够了
那么现在来分析一下这段代码 代码全貌是这样的
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-06-01 19:54
* Notes : 测试利用bean导入class
*/
public class BeanTest {
@Test
public void TestImportClass(){
//根据配置文件初始化容器
ApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
// Object a = ap.getBean("AccountServiseimpl");
// AccountServise accountServise = (AccountServiseimpl) accountServiseimpl;
// accountServise.saveAccount();
AccountServise accountServise = ap.getBean(com.hbsi.services.impl.AccountServiseimpl.class);
accountServise.saveAccount();
}
}
ApplicationContext的实现类众多但是具体去看只有三个常用的
2.2.3 ApplicationContext接口的三个实现类:
-
ClassPathXmlApplicationContext : 加载类路径下的配置文件,要求配置文件必须在类路径下
-
FileSystemXmlApplicationContext : 加载磁盘任意目录下的配置文件
-
AnnotationConfigApplicationContext : 解析注解的相关配置
2.2.4 ApplicationContext与BeanFactory
在结构上ApplicationContext是BeanFactory的子类 如图
ApplicationContext和BeanFactory都是构建核心容器的接口,那其实从体系结构也可以看的出来,ApplicationContext是BeanFactory的子接口,那就必然是要比BeanFactory的功能多,并且两个容器的加载机制是完全不同的
- ApplicationContext : 采用立即加载的策略,只要读完配置文件就马上创建配置文件中配置的对象(单例对象适用)
- BeanFactory : 采用延迟加载的策略,读完配置文件不会创建,用的时候才会创2建配置中的对象(多例对象使用)
2.2.5 创建Bean的三种方式
-
使用默认构造函数创建bean对象,如果类中无默认构造函数,则对象无法创建
-
使用工厂创建对象,需指定工厂类为bean并给出创建对象的类 与要获取的类的bean配置信息
-
利用工厂的静态方法创建对象
第一种方式测试
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-05-31 21:17
* Notes : 模拟表现层调用业务
*/
public class client {
//获取Spring容器的 ioc核心容器
public static void main(String[] args) {
ApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
//根据class获取Bean对象
AccountServiseimpl bean;
bean = (AccountServiseimpl) ap.getBean(com.hbsi.services.impl.AccountServiseimpl.class);
bean.saveAccount();
}
}
bean文件配置信息如上 运行:
没有问题,那么现在我准备把AccountServiseimpl的默认构造函数给干掉
package com.hbsi.services.impl;
import com.hbsi.services.AccountServise;
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-05-31 21:12
* Notes :
*/
public class AccountServiseimpl implements AccountServise{
private AccountServise accountServise;
//重载掉默认构造函数
//只有传入name才会构建
public AccountServiseimpl(String name){
System.out.println("对象创建了");
}
// public AccountServiseimpl() {
// System.out.println("对象创建了");
// }
public void saveAccount(){
System.out.println("serivise中的saveAccount方法执行了");
}
}
client文件保持不变 执行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vfjhWjWk-1592203079244)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200605102509754.png)]
报错,No default constructor fonud 没有默认构造函数,仔细想也会是这样,如果没有默认构造函数 就说明会出现构造函数重载的情况,那么确实无法选择正确的构造函数
第二种方法测试
创建一个Factory类
运行:
没有问题
第三种方法:工厂类中 新建静态方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRDbVXQD-1592203079246)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200605130412516.png)]
新的工厂类和静态方法如下
运行测试:
没有问题
2.2.6 bean的作用范围
bean标签的scope属性:
- singleton : (默认属性) 表明为单例的
- prototype : 表示为多例的
- request : 作用于web应用的请求范围
- session : 作用于web应用的会话范围
- global- session : 集群环境(暂时不去了解)
测试一下singleton 和prototype
继上次利用静态工厂配置的bean继续测试 默认为singleton 也就是单例的
public class client {
//获取Spring容器的 ioc核心容器
public static void main(String[] args) {
ApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
//根据id获取Bean对象
AccountServiseimpl bean = (AccountServiseimpl) ap.getBean("AccountServiseimpl");
AccountServiseimpl bean2 = (AccountServiseimpl) ap.getBean("AccountServiseimpl");
System.out.println(bean == bean2);
}
}
从ioc容器中获取两个bean,接着判断是否相等,如果为单例则肯定是相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axGlqeib-1592203079248)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200605131440125.png)]
可以看到为true,默认函数只执行了一次
切换scope属性为prototype
创建了两次对象,结果为false
2.2.7 bean的生命周期
单例对象的生命周期 :
- 出生: 容器出生 对象出生
- 活着: 容器活着 对象活着
- 死亡: 容器死亡(销毁) 对象死亡
为了测试方便在AccountServiseimpl中加入 init 和 destory两个方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWsLogQA-1592203079250)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200605132114595.png)]
在bean中配置单例对象的初始化方法和销毁方法
修改client代码
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-05-31 21:17
* Notes : 模拟表现层调用业务
*/
public class client {
//获取Spring容器的 ioc核心容器
public static void main(String[] args) {
ApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
//根据id获取Bean对象
AccountServiseimpl bean = (AccountServiseimpl) ap.getBean("AccountServiseimpl");
//把调用saveAccount方法看成是正在活着
bean.saveAccount();
}
}
并没有执行销毁,那么我们分析可能是容器资源没有释放呗,所以bean的destory方法也无法随之调用,
修改client代码
/**
* Created by IntelliJ IDEA.
* User: Ronin
* Date: 2020-05-31 21:17
* Notes : 模拟表现层调用业务
*/
public class client {
//获取Spring容器的 ioc核心容器
public static void main(String[] args) {
ClassPathXmlApplicationContext ap = new ClassPathXmlApplicationContext("Bean.xml");
//根据id获取Bean对象
AccountServiseimpl bean = (AccountServiseimpl) ap.getBean("AccountServiseimpl");
//把调用saveAccount方法看成是正在活着
bean.saveAccount();
ap.close();
}
}
注意声明容器时,改为了将ApplicationContext类型对象改为了ClassPathXmlApplicationContext,因为后者时前者的实现类,功能肯定只增不减所以问题不大
运行一下:
总结一下单例对象生命周期就是随着容器的变化而变化
多例对象
- 出生: 使用时 Spring创建对象
- 活着: 在使用时就一直活着
- 死亡: 无使用时 等待java的垃圾回收机制 回收
2.2.8 Spring的依赖注入
Spring依赖注入 就是可能有些在IOC中的对象需要一些参数,就像在java运行时,肯定会有一些对象 是需要带一些依赖数据才能使用的
构造函数注入:
<bean id="User" class="com.hbsi.core.user">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="brithday" ref="userBrithday"></constructor-arg>
</bean>
<!-- 配置一个日期类型-->
<bean id="userBrithday" class="java.util.Date"></bean>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZmk3fYu-1592203079252)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200606142514333.png)]
xml配置与实体类如上
constructor-arg是构造函数的参数标签需要写在bean的内部共有五个属性
- index :需配置的属性的索引 0开始
- type : 需配置的属性的数据类型
- name: 需配置的属性的属性名
- value : 需传入的属性值
- ref : 需要引入的bean
set方法注入
constructor-arg 变为 property并且property中没有index 与type属性
<bean id="User" class="com.hbsi.core.user">
<property name="age" value="18"></property>
<property name="brithday" ref="userBrithday"></property>
<property name="name" value="咋咋"></property>
</bean>
<!-- 配置一个日期类型-->
<bean id="userBrithday" class="java.util.Date"></bean>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUAOqK3m-1592203079252)(C:\Users\Ronin\AppData\Roaming\Typora\typora-user-images\image-20200606143059475.png)]
需要注意的是 使用set方法注入时,依然需要默认构造函数的存在
set方法的优势相比于构造函数形式可以选择一些不是必须的参数无需注入
2.3 Spring IOC注解开发
2.3.1 创建bean的注解
可利用的注解有 @Component,@Repository,@Service,@Controller
@Component:
使用构造 方法实例化,使用@Component,在class上使用@Component标识这个是一个bean,用spring的IOC容器管理。
使用流程:
1、在class上使用@Component标记
2、配置组件扫描器
配置组件扫描器,spring会根据配置的扫描的包路径,去扫描包下的class,如果遇到@component,去管理这个 bean。
使用spring可以应用在三层(web层、业务层、持久层),spring提供三个对应三层bean的注解:
web层:@Controller (表示一个控制器)
业务层:@Service (表示一个业务bean)
持久层:@Repository(表示一个持久层的dao)
如果遇到无法确定属于哪一层的bean 使用@Component
以上三个注解和Component完全 等价的。
测试一下:
运行
圆满成功
2.3.2 注入数据的注解
@Autowired
自动根据bean中的类型,只要容器中有唯一的一个bean对象类型与要对应的相同就会自动注入,如果一个都没有就会注入失败
有两个bean的类型与之相同时,那么会将自身的变量名当成id与匹配的两个bean对象的id匹配,如果也相同或没有相同的则注入失败
@Qualifier
在给类成员注入时,需要与Autowire配合使用 如果有两个相同时Qualifier选择需要的bean对象
@Resource(name = ‘’)
注入bean的id,来将bean相应的对象注入
基本类型无法使用上述三个方法实现
@Value
用于注入基本类型和String类型的数据
value 可以使用el表达式
spEl的写法 $(); 同于mybatis
数组类型数据只能通过xml注入
2.3.3 用于改变作用域范围的注解
@Scope与xml中相同属性取值也相同
2.3.4 和生命周期相关
@PreDestory 指定初始化方法
@PostConstruct 指定销毁方法