Spring框架概述
- Spring是一个轻量级的开源的JavaEE框架
- Spring可以解决企业应用开发的复杂性
- Spring有两个核心部分:IOC和AOP
- IOC:控制反转,把创建对象过程交给Spring进行管理
- Aop:面向切面编程,不修改源代码进行功能的增强
- Spring特点
- 方便解耦,简化开发
- Aop编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低API开发难度
IOC容器
IOC(概念和原理)
- 什么是IOC
- IOC又名控制反转,把对象的创建和对象之间的调用的过程,交给Spring进行管理
- 使用IOC的目的:为了降低耦合度
- IOC的底层原理
- xml解析、工厂模式、反射
IOC接口
-
IOC思想是基于IOC容器完成的,IOC容器底层是对象工厂 -
Spring提供了IOC容器的两种实现方式(两个接口)
- BeanFactory: IOC容器的最基本的实现,是Spring内部使用的接口,不提供开发人员使用
- ApplicationContext: BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员使用
注:BeanFactory 和ApplicationContext 我们在开发的时候都可以用,但值得注意的是如果你使用BeanFactory 那么Spring在加载配置文件的时候不会立即创建对象,而是在你要使用的时候才会给你创建,而ApplicationContext 则会在加载配置文件的时候就会把被配置的对象进行创建。 -
ApplicationContext接口的实现类
IOC操作Bean
Bean 管理指的就是两个操作: Spring对象创建、Spring对象注入属性 Bean管理操作有两种方式:基于xml配置文件实现、基于注解实现
IOC操作Bean(基于xml)
- 在Spring配置文件中,使用bean标签,在标签里添加对应的属性,就可以实现对象的创建
- bean标签的常见属性:
- id属性:该bean的唯一标识
- class属性:定义 bean 的类型并使用完全限定的类名
- 创建对象时,默认使用无参构造器创建对象
- 基于xml进行依赖注入
- DI: 依赖注入,就是注入属性
- 第一种注入方式,使用set方式进行依赖注入
- 使用
property 标签实现
<bean id="" class="">
<property name="" value="" />
<property name="" ref="" />
</bean>
name的值是属性名,value用于对基本数据类型+String的赋值,ref用于对引用对象的赋值,ref的值是bean对象的id 例子:
<bean id="UserDaoImpl" class="com.spring.dao.impl.UserDaoImpl"/>
<bean id="UserSeviceImpl" class="com.spring.service.UserSeviceImpl">
<property name="name" value="例子"/>
<property name="userDao" ref="UserDaoImpl"/>
</bean>
- p命名空间实现
1. xml中加入命名空间
xmlns:p="http://www.springframework.org/schema/p"
<bean id="" class="" p:属性名1="" p:属性名2="" />
例子:
<bean id="UserMapperImpl" class="com.spring.dao.impl.UserMapperImpl" />
<bean id="UserServiceImpl" class="com.spring.service.impl.UserServiceImpl"
p:userMapper-ref="UserMapperImpl"/>
- 第二种注入方式,使用有参构造进行依赖注入
使用constructor-arg 标签实现
<bean id="user" class="com.spring.pojo.User">
<constructor-arg name="id" value="12"/>
<constructor-arg name="password" value="123" />
<constructor-arg name="userName" value="张三" />
<constructor-arg name="userCode" value="VTB1233123" />
</bean>
-
特殊值的依赖注入 -
空值和特殊符号 (1) null值:标签null (2)属性值中包含特殊符号:<![CDATA[属性值]]> -
Array、List、Map -
FactoryBean Spring有两种Bean:普通类型、工厂Bean 普通bean:再配置文件中定义的bean类型就是返回类型 工厂bean:在配置文件定义的bean类型可以和返回值类型不一样 第一步:创建类,让这个类作为工厂bean,实现接口FactoryBean 第二步:实现接口里面的方法,在实现的方法里面定义bean类型 -
bean的作用域
我们在Spring中定义一个bean时,必须要声明该bean的作用域,默认为singletno,即,单例模式,Spring框架支持六个作用域singletno,prototype、request、session、application、websocket
作用域 | 描述 |
---|
singleton | 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值 | prototype | 每次从容器中调用bean时,都会放回一个新的实例 | request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个自己的bean实例,它是在单个bean定义的后 面创建的。仅在可感知网络的Spring ApplicationContext上下文中有效。 | session | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个自己的bean实例,它是在单个bean定义的后 面创建的。仅在可感知网络的Spring ApplicationContext上下文中有效。 | application | 将单个bean定义的作用域限定为ServletContext的生命周期。仅在可感知网络的Spring ApplicationContext上下文中有效。 | websocket | 将单个bean定义的作用域限定为WebSocket的生命周期。仅在可感知网络的Spring ApplicationContext上下文中有效。 |
生命周期:对象从创建到销毁的过程
Bean的生命周期:
- 通过构造器创建Bean实例(无参构造)
- 为Bean的属性设置值和对bean引用
- 调用bean的初始化方法(需要进行配置初始化的方法)
- 定义一个初始化时要执行方法
- bean属性:init-method
- bean可以使用了(对象获取到)
- 当容器关闭时,调用bean的销毁的方法(需要进行配置销毁的方法)
- 定义一个初始化时要执行的方法
- bean属性:destroy-method
Bean的后置处理器:
- 定义Bean的后置处理器后,会在bean的初始化前和初始化后会分别执行后置处理器方法
- 要想创建一个Bean的后置处理器,需要创建一个类,并且这个类要实现接口BeanPostProcess
- 有后置处理器的Bean的生命周期
1. 通过构造器创建Bean实例(无参构造)
2. 为Bean的属性设置值和对bean引用
-------------------------
3. 后置处理器初始化前的操作
-------------------------
4. 调用bean的初始化方法(需要进行配置初始化的方法)
-------------------------
5. 后置处理器初始化后的操作
-------------------------
6. bean可以使用了(对象获取到)
7. 当容器关闭时,调用bean的销毁的方法(需要进行配置销毁的方法)
生命周期例子:
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="user" class="com.spring.beanpostproess.pojo.User"
init-method="init" destroy-method="destroy">
<property name="name" value="123"/>
</bean>
<bean class="com.spring.beanpostproess.MyBeanPost" />
</beans>
User.java
public class User {
private String name;
public User() {
System.out.println("无参构造器创建Bean");
}
public void setName(String name) {
System.out.println("调用set设置方法设置属性值");
this.name = name;
}
public void init(){
System.out.println("执行初始化方法");
}
public void destroy(){
System.out.println("执行销毁销毁方法");
}
}
MyBeanPost.java
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
IOC操作Bean(基于注解)
@Configuration
@ComponentScan(basePackages={"扫描包路径"})
public class SpringConfig{}
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
AOP
AOP(概念)
什么是AOP
- 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个方面之间的耦合度降低,提高程序的可可从用性,提高了开发的效率
- 通俗理解:不通过修改代码的方式,在主干功能里面添加新功能
AOP(底层原理)
AOP底层使用的是动态代理,动态代理分两种情况:
- 有接口情况,使用JDK动态代理
创建接口实现类代理对象,增强类的方法
- 没有接口情况,使用CGLIB动态代理
创建子类的代理对象,增强类的方法
-
JDK动态代理简单实现: -
使用JDK动态代理,使用Proxy类里面的方法创建代理对象
调用newProxyInstance方法
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
ClassLoader loader: 代理类的类加载器
Class<?>[] interfaces:代理类实现的接口实现类class文件列表
InvocationHandler h: 调度方法调用的调用处理函数(增强方法类)
- 实现代码
public class Demo {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDaoImpl = new UserDaoImpl();
UserDao userDao =
(UserDao) Proxy.newProxyInstance(
Demo.class.getClassLoader(),
interfaces,
new UserDaoProxy(userDaoImpl));
int add = userDao.add(1, 2);
System.out.println(add);
}
}
class UserDaoProxy implements InvocationHandler {
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前" + method.getName() + " 方法参数:" + args);
Object res = method.invoke(obj, args);
System.out.println("方法执行后" + method.getName());
return res;
}
}
public interface UserDao {
int add(int a,int b);
}
public class UserDaoImpl implements UserDao {
public int add(int a, int b){
return a+b;
}
}
JdbcTemplate
什么是JdbcTemplate
JdbcTemplate是Spring框架对JDBC进行的封装,使用它可以更方便的对数据库进行操作
事务
什么是事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
事务四个特性(ACID)
-
原子性:操作不可分割,要么都成功,一个失败都失败 -
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。 这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 -
隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的 -
持久性:一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响
事务的实现
事务操作基本流程
- 开启事务
- 进行业务操作
- 没有发生异常,提交事务;出现异常,事务回滚
spring事务管理介绍
-
事务一般添加到JavaEE三层架构的Service层中 -
在Spring进行事务管理操作
- 编程式事务(不常用)
- 声明式事务
-
声明式事务管理
- 基于注解实现(常用)
- 基于xml配置文件方式
-
在Spring进行声明式事务管理,底层使用AOP原理 -
Spring事务管理API
- 提供一个接口(PlatformTransactionManager),代表事务管理器,这个接口针对不同框架,有不同实现类
?
Spring事务操作(注解声明式事务管理)
Spring事务操作步骤
-
在Spring配置文件配置事务管理器 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
-
在Spring配置文件,开启事务
-
添加命名空间 xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
-
开启事务注解 <tx:annotation-driven transaction-manager="transactionManager" />
-
在Service类上面(或Service类里面的方法上面)添加事务注解
- @Transactional,这个注解可以添加到类上,也可以添加到方法上
- 添加到类上,这个类里面的所有的方法都添加事务
- 添加到方法上,为这个方法添加事务
@Service
@Transactional
public class UserServiceImpl implements UserService {
}
参数配置
参数名 | 描述 |
---|
propagation | 事务的传播类型 | isolation | 隔离级别 | timeout | 超时时间 | readOnly | 是否只读 | rollbackFor | 回滚 | rollbackForClassName | | noRollbackFor | 不回滚 | noRollbackForClassName | |
propagation: 当一个事务方法被另一个事务方法调用时这个事务如何进行
传播属性 | 描述 |
---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 | REQUIRED_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起 | SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 | NOT_SUPPORTS | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 | MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛异常 | NEVER | 当前的方法不应该运行在事务内部,如果有正在运行的事务,就抛异常 | NESTED | 如果事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 |
isolation: 事务有特性为隔离性,多事务操作之间不会产生影响。如果不考虑隔离性在并发访问数据库时,读取数据时会产生很多问题:
脏读: 读取未提交数据
时间顺序 | 转账事务 | 取款事务 |
---|
1 | | 开始事务 | 2 | 开始事务 | | 3 | | 查询账户余额为2000元 | 4 | | 取款1000元,余额被更改为1000元 | 5 | 查询账户余额为1000元(产生脏读) | | 6 | | 取款操作发生未知错误,事务回滚,余额变更为2000元 | 7 | 转入2000元,余额被更改为3000元(脏读的1000+2000) | | 8 | 提交事务 | | 备注 | 按照正确逻辑,此时账户余额应该为4000元 | |
不可重复读: 前后多次读取,数据内容不一致
时间顺序 | 事务A | 事务B |
---|
1 | 开始事务 | | 2 | 第一次查询,小明的年龄为20岁 | | 3 | | 开始事务 | 4 | 其他操作 | | 5 | | 更改小明的年龄为30岁 | 6 | | 提交事务 | 7 | 第二次查询,小明的年龄为30岁 | | 备注 | 按照正确逻辑,事务A前后两次读取到的数据应该一致 | |
幻读: 前后多次读取,数据总量不一致
时间顺序 | 事务A | 事务B |
---|
1 | 开始事务 | | 2 | 第一次查询,数据总量为100条 | | 3 | | 开始事务 | 4 | 其他操作 | | 5 | | 新增100条数据 | 6 | | 提交事务 | 7 | 第二次查询,数据总量为200条 | | 备注 | 按照正确逻辑,事务A前后两次读取到的数据总量应该一致 | |
隔离性级别: ×(未解决)、√(解决)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|
读未提交(Read uncommitted) | × | × | × | 读已提交(Read committed) | √ | × | × | 可重复读(Repeatable read)(mysql默认) | √ | √ | × | 可串行化(Serializable) | √ | √ | √ |
timeout: 事务必须在一定时间内进行提交,如果规定时间内没有提交,事务就会回滚(默认值-1,一直不超时)
readOnly: 是否只读
- 读:查询操作;写:添加、修改,删除操作
- readOnly默认值false,表示增删改查都可以
- 设置readOnly值为true后,表示只能查询,不能增删改
rollbackFor: 设置出现那些异常进行回滚
noRollbackFor: 设置出现那些异常不进行回滚
xml配置方式实现事务
…
完全注解开发
…
Spring5的新功能
整个Spring5框架代码基于JDK8
- 通过使用泛型等特性提高可读性
- 对java8提高直接的代码支撑
- 运行时兼容JDK9
- Java EE 7API需要Spring相关的模块支持
- 运行时兼容Java EE8 API
- 取消的包,类和方法
- 包 beans.factory.access
- 包 dbc.support.nativejdbc
- 从spring-aspects 模块移除了包mock.staicmock,不在提AnnotationDrivenStaticEntityMockingControl支持
- 许多不建议使用的类和方法在代码库中删除
核心特性
-
访问Resuouce时提供getFile或和isFile防御式抽象 -
有效的方法参数访问基于java 8反射增强 -
在Spring核心接口中增加了声明default方法的支持一贯使用JDK7 Charset和StandardCharsets的增强 -
兼容JDK9 -
Spring 5.0框架自带了通用的日志封装 Spring5移除了Log4jConfigListener,官方建议使用Log4j2 Spring5框架整合Log4j
- 导入jar包
- 创建log4j.xml
<?xml version="1.0" encoding="utf-8"?>
<configuration status="INFO">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLauout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</console>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
-
持续实例化via构造函数(修改了异常处理) -
Spring 5.0框架自带了通用的日志封装 -
spring-jcl替代了通用的日志,仍然支持可重写 -
自动检测log4j 2.x, SLF4J, JUL(java.util.Logging)而不是其他的支持 -
访问Resuouce时提供getFile或和isFile防御式抽象 -
基于NIO的readableChannel也提供了这个新特性
核心容器
- 支持候选组件索引(也可以支持环境变量扫描)
- 支持@Nullable注解
@Nullable 注解可以使用在属性、方法和方法参数- 使用在属性上面,属性值可以为空
- 使用在方法上面,方法返回值可以为空
- 使用在方法参数上面,参数值可以为空
- 函数式风格GenericApplicationContext/AnnotationConfigApplicationContext
- 基本支持bean API注册
- 在接口层面使用CGLIB动态代理的时候,提供事物,缓存,异步注解检测
- XML配置作用域流式
- Spring WebMVC
- 全部的Servlet 3.1 签名支持在Spring-provied Filter实现
- 在Spring MVC Controller方法里支持Servlet4.0 PushBuilder参数
- 多个不可变对象的数据绑定(Kotlin/Lombok/@ConstructorPorties)
- 支持jackson2.9
- 支持JSON绑定API
- 支持protobuf3
- 支持Reactor3.1 Flux和Mono
SpringWebFlux
- 新的spring-webflux模块,一个基于reactive的spring-webmvc,完全的异步非阻塞,旨在使用enent-loop执行模型和传统的线程池模型。
- Reactive说明在spring-core比如编码和解码
- spring-core相关的基础设施,比如Encode 和Decoder可以用来编码和解码数据流;DataBuffer 可以使用java ByteBuffer或者Netty ByteBuf;ReactiveAdapterRegistry可以对相关的库提供传输层支持。
- 在spring-web包里包含HttpMessageReade和HttpMessageWrite
测试方面的改进
- 完成了对JUnit 5’s Juptier编程和拓展模块在Spring TestContext框架
- SpringExtension:是JUnit多个可拓展API的一个实现,提供了对现存Spring TestContext Framework的支持,使用@ExtendWith(SpringExtension.class)注解引用。
- @SpringJunitConfig:一个复合注解
- @ExtendWith(SpringExtension.class) 来源于Junit Jupit
- @ContextConfiguration 来源于Srping TestContext框架
- @DisabledIf 如果提供的该属性值为true的表达或占位符,信号:注解的测试类或测试方法被禁用
- 在Spring TestContext框架中支持并行测试
- 具体细节查看Test 章节 通过SpringRunner在Sring TestContext框架中支持TestNG, Junit5,新的执行之前和之后测试回调。
- 在testexecutionlistener API和testcontextmanager新beforetestexecution()和aftertestexecution()回调。MockHttpServletRequest新增了getContentAsByteArray()和getContentAsString()方法来访问请求体
- 如果字符编码被设置为mock请求,在print()和log()方法中可以打印Spring MVC Test的redirectedUrl()和forwardedUrl()方法支持带变量表达式URL模板。
- XMLUnit 升级到了2.3版本。
|