Spring框架简述
- 什么是Spring
- Spring的工厂设计模式
- Spring5.x与日志框架的整合
- 注入(Injection)
- Set注入详解
- 构造注入
- 控制反转与依赖注入
- Spring创建复杂对象
- 控制Spring工厂创建对象的次数
- 对象的生命周期
- 配置文件参数化
- 自定义类型转换器
- 后置处理Bean
- 静态代理设计模式
- Spring的动态代理开发
- Spring动态代理详解
- AOP编程
- AOP的底层实现原理
- 基于注解的AOP编程
- AOP开发中的坑
- AOP阶段知识总结
- 持久层整合
- Spring与Mybatis整合
- Spring的事务处理
- Spring中的事务属性(Transaction Attribute)
- 注解基础概念
- Spring的基础注解(Spring 2.x)
- Spring的高级注解(Spring3.x以上)
什么是Spring
SpringFranmeWork是一个轻量级控制反转(IOC)和面向切面(AOP)的控制框架,简称Spring,由Rod Johnson(一个音乐学博士)创建,前身是interface21,也是由Rod Johnson在他的书中《J2EE设计和开发》提出的,他在书中指出了JavaEE规范中EJB框架的缺陷,在2004年正式发布,是为了解决企业级应用开发的复杂性而创建,完全开源免费,非侵入式(你的原有代码引入Spring框架后不会受到任何影响);
EJB(Enterprise Java Bean)的缺陷:
- 运行环境苛刻,首先本身的代码需要写进EJB的容器,这个EJB的容器,支持这个容器运行的环境都是收费且闭源的,并且还需要实现相应的运行环境的接口才可以运行,收费是次要,闭源意味着代码的扩展性和定制性极差,使用他的公司改不了Weblogic这一类功能强大的服务器的代码来实现一些针对公司业务的个性化需求;
- 代码移植性差,想要运行在Weblogic里面,EJB的程序需要实现相应的接口,然后再把这代码拿到Websphere里面就直接寄,挺nt的;
- 这也是EJB是重量级而Spring是轻量级框架的主要原因,主要就是这两点;
- 当然轻量级和重量级也是相对而言的,对于spring容器,它提供了很多服务,但这些服务并不是默认为应用打开的,应用需要某种服务,还需要指明使用该服务,如果应用使用的服务很少,如:只使用了spring核心服务,那么我们可以认为此时应用属于轻量级的,如果应用使用了spring提供的大部分服务,这时应用就属于重量级。目前EJB容器就因为它默认为应用提供了EJB规范中所有的功能,所以它属于重量级。
为什么Spring是轻量级?
- 对于运行环境没有要求,开源(tomcat、resion、jetty)及收费(weblogic、websphere)服务器皆可运行,且不用实现特定的接口;
- 代码移植性高,不用实现特定接口;
Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架和众多优秀的设计模式,是一个J2EE解决方案;
为什么说Spring是一个J2EE解决方案?
- Spring做到了Java开发中每一层的对应解决,Controller层Spring提供SpringMVC来解决,Service层Spring提供AOP技术来解决其事务控制及日志处理的问题,DAO层Spring与现有的持久化解决方案(JDBC、Mybatis、Hibernate)进行整合来解决数据持久化的问题,Spring比传统意义上的框架来讲更加全面,所以我们说Spring是一个完整的体系的J2EE解决方案;
什么是设计模式?
- 广义的概念:面向对象设计中,解决特定问题的经典代码;
狭义的概念:GOF4人帮定义的23种设计模式:工厂、适配器、装饰器、代理、模板、门面。。。
Spring的工厂设计模式
Spring通过工厂设计模式,来解决程序中对象创建的硬编码,这是最重要的引入此模式的原因,这个模式解决了对象创建的硬编码,即通过工厂类创建对象,从而达到我们说的解耦合;
耦合的坏处?
- 当我们想要创建一个UserService对象的时候,我们直接new,但是我们发布项目以后,更新了UserService里面的相关代码后,还要去重新把项目编译测试一遍吗,项目里面所有设计UserService的new的语句,都去改一遍?累死你,而且可能出bug,通过工厂创建,解决了这种强耦合,也就是我们再更改我们的UserService对象以后,几乎不用对代码进行任何操作;
但是也不是一步就达到,首先把创建对象的方法丢到一个BeanFactory类里面;
简单工厂的实现
此时做到了第一步,即Controller层已经实现了解耦合,我们在Controller层创建UserServiceImpl是通过调用BeanFactory的方法来创建的;
但是明显的,BeanFactory里面我们还是一样要进行new对象的,如何解决呢?
通过反射+配置文件来解决这个耦合,即反射创建需要创建的类,而通过Properties来读取需要创建的类的全限定名,当然配置文件需要IO来读取,写在Static代码块中,是因为IO在整个程序运行中,是一个系统级资源,我们一般要尽量避免重复性的打开IO,最好在程序启动的时候一次性读取我们想要的内容,下图有一错误,IO的关闭,inputStream.close();需要放在finally中执行
通用工厂的实现
通用工厂的概念很好理解,我们需要创建的类很多,不可能需要一个就去工厂类里面添加一个,因为整体上,创建的方式和代码都基本一致,所以这个时候就需要设计一个通用的工厂类,然后只通过这一个工厂类,去创建任何我们想要的对象;
Spring核心API
- ApplicationContext
作用:Spring提供的ApplicationContext这个工厂,用于对象的创建
好处:解耦合
- ApplicationContext接口类型
接口:屏蔽实现的差异
非web环境:ClassPathXmlApplicationContext(main junit)
web环境:XmlWebApplicationContext
- 重量级资源
ApplicationContext工厂的对象占用大量的内存
不会频繁的创建对象,一个应用只会创建一个工厂对象
ApplicationContext工厂:一定是线程安全的(多线程并发访问)(与Mybatis的sqlsessionFactory一样道理)
- 通过ApplicationContext工厂进行第一个Spring程序的开发:
ApplicationContext ctx=new ClassPathXmlApplicationContext("/applicationContext.xml");
Person person=(Person)ctx.getBean("Person);
细节分析
- 名词解释
Spring工厂创建的对象,叫做bean或者组件(Componet)
- Spring工厂的相关的代码
- 配置文件中需要注意的细节
Spring工厂的底层实现原理(简化版)
- Spring工厂可以调用对象私有的构造方法创造对象;
思考
问题:在未来开发过程中,是不是所有对象都交给Spring来创建呢?
理论上,是的,但是有特例:实体对象(entity)是不会交给Spring工厂创建,它是由持久层框架进行创建,最核心的原因是实体对象要承载数据库的数据,而Spring是没有的;
Spring5.x与日志框架的整合
Spring与日志框架进行整合,这样日志框架就能在控制台中,输出Spring框架运行过程中的一些重要的信息;
好处:便于了解Spring框架的运行过程,利于程序的调试;
- Spring如何整合日志框架
默认
Spring1/2/3早期都是与commons-logging.jar;
Spring5.x默认整合的日志框架logback、log4j2;
Spring5.x整合log4j
1.引入log4j jar
2.引入log4j.properties
pom:
```xml
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
门面日志jar,为了干掉默认的logback+log4j2来支持下面的log4j
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
```
log4j.properties:
```xml
#resource文件夹根目录下
### 设置根###
log4j.rootLogger = debug,console
### 输出信息到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
注入(Injection)
- 什么是注入
通过Spring工厂及配置文件,为所创建对象的成员变量赋值;
- 为什么需要注入
原来通过代码赋值存在耦合;
- 如何进行注入(开发步骤)
为类的成员变量提供get/set方法;(set方法必须)
配置Spring的配置文件;
- 注入的好处
解耦合;
Set注入详解
- 针对不同类型的成员变量,在property标签中,需要嵌套其它标签
JDK内置类型
- String+8种基本类型
<value></value>
- 数组
<list></list>内部再嵌套<value></value>
- Set集合(语法规则:无序&不可重复)
<set><类型></类型></set>内部嵌套的标签,具体情况具体分析
- List集合(有序&可重复)
<list><类型></类型></list>内部嵌套的标签,具体情况具体分析
底层就是数组实现,所以java设计使用list标签
- Map集合()
<map></map>内部嵌套键值对标签<entry></entry>
<entry></entry>内部嵌套一个<key><key的类型></key的类型><key>+值的标签对<值的类型></值的类型>
<key><key>内部嵌套的key的类型,具体情况具体分析
<值的类型></值的类型>值的类型,具体情况具体分析
- Properties
Properties 特殊的Map key=String value=String
<props><prop key=""></prop></props>
props对应多个键值对,键写在key属性里,值写在prop中
- 复杂的JDK类型(Date)
需要程序员自定义类型转换器处理;
自定义类型
- 第一种方式
为成员变量提供get/set方法;
配置文件中进行注入(赋值);
<bean id="userService" class="xxx.UserServiceImpl">
<property name="userDao">
<bean classs="xxx.UserDaoImpl">
</property>
</bean>
- 第二种方式
第一种赋值方式存在的问题
配置文件代码冗余;
被注入的对象(UserDao),多次创建,浪费(JVM)内存资源;
步骤:
为成员变量提供get set方法;
配置文件中进行配置
配置文件中进行配置:
<bean>id="userDao" class="com.xxx.UserDaoImpl"</bean>
<bean id="userService" class="com.xxx.UserServiceImpl">
<property name="userDao">
<ref bean="userDao"/>
</property>
</bean>
#Spring4.X废除<ref local=""/>基本等效<ref bean=""/>,区别是local只能引用本配置文件的bean,而bean可以引用父容器的bean
Set注入简化写法
JDK类型注入简化
<bean id="Person" classs="com.xxx.Person" >
<property name="name">
<value>zhangsan</value>
</property>
</bean>
<bean id="Person" classs="com.xxx.Person" >
<property name="name" value="zhangsan"/>
</bean>
注意:value属性 只能简化8种基本类型+String类型;
用户自定义类型
<bean id="Person" classs="com.xxx.Person" >
<property name="userDao">
<ref bean="userDao">
</property>
</bean>
<bean id="Person" classs="com.xxx.Person" >
<property name="userDao" ref="userDao"/>
</bean>
P标签简化JDK类型(P其实就是代表Property)
<bean id="Person" classs="com.xxx.Person" p:name="xiaojr" p:id="100"/>
P标签简化自定义类型(P其实就是代表Property)
<bean id="userService" classs="com.xxx.UserUserviceImpl" p:userDao-ref="userDao"/>
构造注入
Spring调用构造方法,通过配置文件,为成员变量赋值;
- 开发步骤
提供有参构造方法;
Spring的配置文件;
<constructor-arg><constructor-arg/>标签对应构造参数的个数及顺序;(关键)
- 构造方法重载
参数个数不同时:
通过构造参数的个数进行区分;
参数个数相同时:
通过在<constructor-arg type=""><constructor-arg/>指定参数来确定要重载的方法
注入的总结
未来的实战中,应用set注入还是构造注入?
答案:set注入更多
原因:
1.构造方法麻烦(重载)
2.Spring框架底层大量set注入
控制反转与依赖注入
控制反转(IOC Inverse of Control)
控制:对于成员变量赋值的控制权;
控制反转:把对于成员变量的控制权,从代码反转(转移)到Spring工厂和配置文件中;
好处:解耦合;
底层实现:工厂设计模式
- 依赖注入(Dependency Injection DI)
通过Spring的工厂及配置文件,为对象(bean、组件)的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把两一个类作为本类的成员变量,通过Spring进行注入
好处:解耦合
Spring创建复杂对象
什么是复杂对象
Spring工厂创建复杂对象的三种方式
FactoryBean接口
- 开发步骤
实现FactoryBean接口;
Spring配置文件的配置;
复杂对象与简单对象的配置含义是有很大不同的,简单对象是直接创建路径的指定简单对象;而复杂对象则是FactoryBean接口的实现类下的复杂对象,例子里的ConnectionFactoryBean里的Connection复杂对象;
- 细节
如果就想获得FactoryBean类型的对象?ctx.getBean("&conn");
获得的就是ConnectionFactoryBean
isSingeton方法
返回true只会创建一个复杂对象;
返回false每一次都会创建新的对象;
问题:根据这个对象的特点,决定返回是true(SqlessionFactory)还是false(Connection),例子里面SqlSessionFactory是重量级资源,创建一次就够了,而Connection设计事务的执行,无法共用;
依赖注入的体会(DI)
将driver、url、username、password抽取成为成员变量进行配置文件的注入,解耦合;
- FactoryBean的实现原理
通过id来找到类,通过instanceof方法确认你的类是否实现FactoryBean的接口,即是否为其子类,若是则调用getObject()方法中我们写的创建复杂对象的代码来创建复杂对象,若不是,直接创建此类的简单对象;
- FactoryBean总结
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续Spring整合其他框架,会大量运用此技术;
实例工厂
1.避免Spring框架的侵入(必须使用FactoryBean才能创建复杂对象);
2.整合遗留系统(之前的公司遗留的class文件,没有java源代码,通过此方法来创建复杂对象);
<bean id="connFactory" class="com.wuman.factorybean.ConnectionFactory"></bean>
<bean id="conn" factory-bean="connFactory" factory-method="getConnection" scope="prototype"></bean>
因为connection作为数据库的连接不应该是单例模式,所以scope="prototype";
静态工厂
- 特点
创建复杂对象的方法是静态的;
配置文件中不需要先创建工厂的实例,直接在方法中就可以进行调用;
Spring工厂创建对象的总结
控制Spring工厂创建对象的次数
如何控制简单对象的创建次数
如何控制复杂对象的创建次数
为什么要控制对象的创建次数?
好处:节省不必要的内存浪费;
- 什么样的对象只创建一次?
1.SqlSessionFactory
2.DAO
3.Service
- 什么样的对象 每一次都要创建新的?
1.Connection
2.SqlSession
对象的生命周期
什么是一个对象的生命周期
指的是一个对象创建、存活、消亡的完整过程;
为什么要学习对象的生命周期
由Spring负责对象的创建、存活、销毁,了解生命周期,有利于使用好Spring为我们创建的对象;
生命周期的三个阶段
-
创建阶段
-
初始化阶段
-
销毁阶段
scope=“prototype”,销毁是由GC回收;
配置文件参数化
- 提供一个小的配置文件(Properties)
- Spring的配置文件与小的配置文件进行整合
location=“classpath:/xxx.properties”
为什么是classpath?
这是maven编译项目的一个规则,在编译期间,会将resources包和java包整合到一个路径下运行,在运行期间产生的目标文件target中可以明显看到这点,就是这两个包被maven融合了,所以classpath也顾名思义就是class的path;
自定义类型转换器
类型转换器
作用:Spring通过类型转换器,把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,完成了注入;
自定义类型转换器
作用:当Spring内没有提供特定类型转换器时,而程序员在应用的过程中还需使用,那么就需要程序员自定义类型转换器;
- 类implements Converter接口;
- 在Spring的配置文件中进行配置
需要注意的是,我们自定义的类型转换器,Spring是没办法知道的,我们得告诉它,我们自定义了一个自定义的类型转换器,Spring为我们提供了这样的方式,就是在Spring的默认的类型转换器工厂里面去注册,即把我们的自定义的转换器注册到Spring中,这种方式就是在配置文件中注入它的ConversionServiceFactoryBean(转换服务工厂组件),并且引入这个Bean,其id="conversionService"是固定的,不对会出错;
这里注册的时候需要注意的是,注册的方式是Set注入,打开ConversionServiceBean的源码可以知道这点,类型是Set<?>,所以我们赋值给它的成员变量的converters的标签要用<set></set>
提取日期格式,依赖注入,解耦合,对程序进行更合理的编码;
- Spring框架内置的日期类型转换器
日期格式:yyyy/MM/dd
后置处理Bean
BeanPostProcessor作用:对Spring工厂创建的对象,进行再加工;
AOP底层实现中关键的部分都有用到BeanPostProcessor,整体的思想也类似;
- BeanPostProcessor的开发步骤
类实现BeanPostProcessor接口
由于jdk1.8新特性,这里实现了接口不会报红,里面的两个方法是default属性;
-
Spring的配置文件中进行配置
-
BeanPostProcessor细节
BeanPostProcessor会对Spring工厂创建的所有对象进行加工;
需要在方法中加上类似instance of这样的方法进行判断;
静态代理设计模式
为什么需要代理设计模式
问题
- 在JavaEE分层开发中,哪个层次对我们来说是最重要的?
DAO/Service/Controller
JavaEE开发中,最为重要的是Service层;
- Service层中包含了哪些代码
Service层中=核心功能(几十行/上百行)+额外功能(附加功能);
核心功能
业务运算;
DAO调用;
额外功能
不属于业务;
可有可无;
代码量很小;
事务、日志、性能...
- 额外功能书写在Service中好不好?
Service的调用者的角度(Controller):需要在Service中添加额外功能;
软件设计者:Service层不需要额外功能;
代理设计模式
概念
通过代理类,为原始类(目标)增加额外的功能;
好处:利于原始(目标)类的维护;
名词解释
代理开发的核心要素
- 代码(静态代理)
静态代理:为每一个原始类,手工编写代理类(.java.class)
静态代理存在的问题
静态代理文件数量过多,不利于项目管理;
额外功能维护性差;(修改个功能就需要把所有代理类修改一遍)
Spring的动态代理开发
动态代理的概念
通过代理类,为原始类(目标)增加额外的功能;
好处:利于原始(目标)类的维护;
搭建开发环境
Spring动态代理的开发步骤
1.创建原始对象(目标类);
2.额外功能
3.定义切入点
切入点:额外功能加入的位置;
目的:由程序员根据自己的需要觉得额外功能加入给哪个原始方法;
简单的测试:为所有方法都添加额外功能;
4.组装(2、3整合)
所有的方法都加入before功能;
5.调用
目的:获得Spring工厂创建的动态代理对象,并进行调用;
动态代理细节分析
Spring创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失;
什么叫动态字节码技术?
通过第三方字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,字节码跟着消失;
结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理,类文件过多,影响项目管理的问题;
动态代理编程简化代理的开发
在额外功能不改变的前提下,创建其它原始类(目标类)的代理对象时,只需要指定原始类(目标类)即可;
动态代理的维护性大大增强
Spring动态代理详解
额外功能的详解
MethodBeforeAdvice的分析
MethodInterceptor(方法拦截器)
MethodInterceptor接口:额外功能可以根据需要运行在原始方法 前、后、前后;
额外功能运行在原始方法执行之后
额外功能运行在原始方法执行之前&后
额外功能运行在原始方法抛出异常的时候
MethodInterceptor影响原始方法的返回值;
切入点详解
切入点表达式
- 方法切入点表达式
类切入点
包切入点表达式 实战
切入点函数
这里的log是自定义的注解类型文件
切入点函数的逻辑运算
指的是,整合多个切入点函数一起配合工作,进而完成更为复杂的要求;
AOP编程
AOP编程的开发步骤
切面的名词解释
AOP的底层实现原理
核心问题
动态代理类的创建
JDK的动态代理
- Proxy.newProxyInstance方法参数详解
代码实现:
CGlib的动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法);
代码实现:
- 总结
Spring工厂如何加工原始对象
- 思路分析
- 代码实现
基于注解的AOP编程
基于注解的AOP编程的开发步骤
原始对象
额外功能
切入点
组装切面
细节
切入点复用
动态代理的创建方式
AOP开发中的坑
AOP阶段知识总结
持久层整合
Spring框架为什么要与持久层框架进行整合
Spring可以与哪些持久层技术进行整合?
Spring与Mybatis整合
Mybatis开发步骤的回顾
Mybatis在开发过程中存在的问题
配置繁琐 代码冗余
Spring与Mybatis整合思路分析
Spring与Mybatis整合的开发步骤
- 配置文件(ApplicationContext.xml)进行相关配置
- 编码
Spring与Mybatis整合编码
- Spring配置文件的配置
- 编码
Spring与Mybatis整合细节
- 问题:Spring与Mybatis整合后,为什么DAO不提交事务,但是数据能够插入数据库中?
Spring的事务处理
什么是事务?
保证业务操作完整性的一种数据库机制;
如何控制事务
Spring控制事务的开发
Spring是通过AOP的方式来进行事务开发的;
原始对象
额外功能
切入点
组装切面
Spring控制事务的编码
- 搭建开发环境
- 编码
- 细节
Spring中的事务属性(Transaction Attribute)
什么是事务属性
如何添加事务属性
事务属性详解
隔离属性(ISOLATION)
- 隔离属性的概念
- 事务并发产生的问题
1.脏读
2.不可重复读
3.幻读
- 总结
- 数据库对于隔离属性的支持
- 默认隔离属性
- 隔离属性在实战中的建议
传播属性(PROPAGATION)
- 传播属性的概念
- 传播属性的值及其用法
只读属性(readonly)
超时属性(timeout)
异常属性
事务属性常见配置总结
基于标签的事务配置方式(事务开发的第二种形式)
- 基于标签的事务在实战中的应用方式
注解基础概念
什么是注解编程
为什么要学习注解编程
注解的作用
- 替换xml这种配置形式,来简化配置
- 替换接口,实现调用双方的契约性
Spring注解的发展历程
Spring注解开发的一个问题
Spring的基础注解(Spring 2.x)
这个阶段的注解,仅仅是简化xml的配置,并不能完全替代xml;
对象创建相关注解
- 搭建开发环境
- 对象创建相关注解
@Component
@Component细节
@Component的衍生注解
@Scope注解
作用:控制简单对象创建次数;
@Lazy注解
用于延迟创建单实例对象;(在获取它的时候才会创建,而不是工厂创建出来就已经对其创建)
生命周期方法相关注解
注入相关注解
- 用户自定义类型@Autowired
- JDK类型
注解扫描详解
对于注解开发的思考
SSM整合开发(半注解开发)
Spring的高级注解(Spring3.x以上)
1.配置Bean
1.配置Bean在应用的过程中,具体替换了XML什么内容呢?
2.AnnotationConfigApplicationContext
2.@Bean注解
1.@Bean注解的基本使用
2.@Bean注解的注入
- 用户自定义类型
- JDK类型的注入
3.@ComponentScan注解
1.基本使用
2.排除、包含的使用
- 排除
- 包含
4.Spring工厂创建对象的多种配置方式
1.多种配置方式的应用场景
2.配置优先级
- 解决基于注解进行配置的耦合问题
5.整合多个配置信息
- 为什么会有多个配置信息
拆分多个配置bean的开发,是一种模块化开发的形式,也体现了面向对象各司其职的思想;
- 多配置信息整合的方式
- 整合多种配置需要关注哪些要点
1.多个配置bean的整合
2.配置Bean与@Component相关注解的整合
3.配置bean与配置文件整合
6.配置Bean底层实现原理
7.四维一体的开发思想
1.什么是四维一体
2.四维一体的开发案例
8.纯注解版AOP编程
1.搭建环境
2.开发步骤
3.注解AOP细节分析
9.纯注解版Spring+Mybatis整合
- 基础配置
- 编码
1.MapperLocations编码时通配的写法
2.配置Bean数据耦合的问题
10.纯注解版事务编程
11.Spring框架中YML的使用
1.什么是YML
2.Properties进行配置问题
3.YML语法简介
4.Spring与YML集成思路的分析
5.Spring与YML集成编码
- 环境搭建
- 编码