Spring
1.简介
- 开源框架
- 简化企业级开发
- IOC+AOP容器
优点:
- 非侵入式
- 可以整合其他框架
- 可以管理对象、依赖注入
- 减少依赖,解耦
2.HelloWorld
-
目标: 使用 Spring 创建对象, 为属性赋值
-
创建 Student 类
-
创建 Spring 配置文件
<!-- 使用bean元素定义一个由IOC容器创建的对象 --> <!-- class属性指定用于创建bean的全类名 --> <!-- id属性指定用于引用bean实例的标识 --> <bean id="student" class="com.atguigu.helloworld.bean.Student"> <!-- 使用property子元素为bean的属性赋值 --> <property name="studentId" value="1001"/> <property name="stuName" value="Tom2015"/> <property name="age" value="20"/> </bean>
-
测试: 通过 Spring 的 IOC 容器创建 Student 类实例
//1.创建IOC容器对象 ApplicationContext iocContainer = new ClassPathXmlApplicationContext("helloworld.xml"); //2.根据id值获取bean实例对象 Student student = (Student) iocContainer.getBean("student");
3.IOC
1.IOC(Inversion of control)
控制反转,不用自己new对象,由框架去创建,核心是反射机制。
2.DI(Dependency Injection)
依赖注入,不需要开发人员手动为属性赋值,Spring通过配置文件或注解方式为属性方式
spring通过DI实现了IOC
3.IOC 容器在 Spring 中的实现
1.实现
- 在通过 IOC 容器读取 Bean 的实例之前, 需要先将 IOC 容器本身实例化。
- Spring 提供了 IOC 容器的两种实现方式
- BeanFactory: IOC 容器的基本实现, 是 Spring 内部的基础设施, 是面向Spring 本身的, 不是提供给开发人员使用的。
- ApplicationContext: BeanFactory 的子接口, 提供了更多高级特性。 面向Spring 的 使 用 者 , 几乎所有场合都使用 ApplicationContext 而 不 是 底 层 的BeanFactory
2.FactoryBean
Spring 中有两种类型的 bean, 一种是普通 bean, 另一种是工厂 bean, 即 FactoryBean。
工厂 bean 跟普通 bean 不同, 其返回的对象不是指定类的一个实例, 其返回的是该工厂 bean 的 getObject 方法所返回的对象。
工厂 bean 必须实现 org.springframework.beans.factory.FactoryBean 接口。
<bean id="product" class="com.atguigu.spring.bean.ProductFactory">
<property name="productName" value="Mp3" />
</bean>
3.ApplicationContext 的主要实现类
- ClassPathXmlApplicationContext: 对应类路径下的 XML 格式的配置文件
- FileSystemXmlApplicationContext: 对应文件系统中的 XML 格式的配置文件
- 在初始化时就创建单例的 bean, 也可以通过配置的方式指定创建的 Bean 是多实例的。
4.bean
1.给 bean 的属性赋值 (实现DI的三种方式 )
-
构造器注入(Spring 自动匹配合适的构造器)
<bean id="book" class="com.atguigu.spring.bean.Book" > <constructor-arg value= "10010"/> <constructor-arg value= "Book01"/> </bean >
-
setter方法注入
- 接口注入
2.bean 属性可以使用的值
<bean class="com.atguigu.spring.bean.Book" id="bookNull" >
<!--1.字面量:基本类型、字符串 -->
<property name= "bookId" value ="2000"/>
<property name= "categoryList">
<!-- 2.List 集合 -->
<list>
<value> 历史</value >
<value> 军事</value >
</list>
</property>
<!-- 3.map -->
<property name="bookMap">
<map>
<entry>
<key>
<value>bookKey01</value>
</key>
<ref bean="book01"/>
</entry>
</map>
</property>
</bean >
3.bean的作用域
1. singleton:单例、是所有bean的默认作用域,在Spring IOC容器中仅存一个bean实例
2. prototype:多例,每次调用getBean()都会返回一个bean实例
3. request:一次请求后会被回收
4. session:session过期后,bean会随之失效
5. global- session
4.bean的生命周期
1. 通过构造器或工厂方法创建bean实例
2. 为bean属性设置值,和对其他bean的引用
3. 调用bean的init()
4. 使用bean
5. 容器关闭时,销毁bean,调用destroy()
拓展方法管理生命周期@PostConstruct、@PreDestroy
5.自动装配
1.定义
- 手动装配: 以 value 或 ref 的方式明确指定属性值都是手动装配。
- 自动装配: 根据指定的装配规则, 不需要明确指定, Spring 自动将匹配的属性值注入bean 中。
2.装配模式
- 根据类型自动装配: 将类型匹配的 bean 作为属性注入到另一个 bean 中。 若 IOC 容器中有多个与目标 bean 类型一致的 bean, Spring 将无法判定哪个 bean 最合适该属性, 所以不能执行自动装配
- 根据名称自动装配: 必须将目标 bean 的名称和属性名设置的完全相同
6.spring的三种配置bean 方式
1.xml
applicationContext.xml,配置bean,加载配置文件,获取spring容器,获取对象
<beans>
<bean id="myService" class="com.somnus.services.MyServiceImpl"/>
</beans>
2.基于 Java 配置的方式
@Configuration
public class AppConfig{
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
3.注解
1.使用注解标识组件
//功能等效,语义不同,便于开发人员理解区分
@Component
@Controller
@Service
@Repository
2.扫描组件
只是配置了组件扫描,并不会创建类的对象,需要给类加注解
<context:component-scan base-package="com.atguigu.component"/>
3.组件装配
// 根据类型自动装配
@Autowired
// 根据名称自动装配
@Qualifier(" ")
// java自带的`@Resource`=@Autowired+@Qualifier(" ")
@Resource
5.SpEL (Spring Expression Language)
1.使用#{…}作为定界符
所有在大框号中的字符都将被认为是 SpEL 表达式。
<bean id="emp04" class="com.atguigu.parent.bean.Employee">
<!--1.字面量:整数、小数、字符串、Boolean-->
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<!--2.引用其他 bean-->
<property name="detp" value="#{dept}"/>
<!--3.引用其他 bean 的属性值作为自己某个属性的值-->
<property name="deptName" value="#{dept.deptName}"/>
</bean>
<!-- 创建一个对象, 在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.atguigu.spel.bean.SalaryGenerator"/>
<bean id="employee" class="com.atguigu.spel.bean.Employee">
<!-- 通过对象方法的返回值为属性赋值 -->
<property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>
2运算符
- 算术运算符: +、 -、 *、 /、 %、 ^
- 字符串连接: +
- 比较运算符: <、 >、 ==、 <=、 >=、 lt、 gt、 eq、 le、 ge
- 逻辑运算符: and, or, not, |
- 三目运算符: 判断条件?判断结果为 true 时的取值:判断结果为 false 时的取值
- 正则表达式: matches
6.AOP
Aspect-Oriented Programming,面向切面编程,是oop的补充,通过预编译方式和运行期动态代理实现
业务隔离,解耦,便于开发维护。
使用了设计模式中的代理模式
1.pom
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
2.说明
切面:@Aspect
切点:@Pointcut
通知注解:
// 连接点:JointPoint
@Before
@After
//方法抛出异常后执行
@AfterThrowing
// 方法返回结果后执行,不管是否异常
@AfterReturning
// 环绕方法执行,连接点:ProceedingJointPoint、是JointPoint的子接口
@Around
Spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。
3.例子
package com.dyn.basepro.frame.aspect;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
*@description: 请求log打印
*@author: dyn
*@create: 2020-04-08 21:05
*/
@Aspect
@Configuration
// 切面优先级:值越小,优先级越高
@Order(0)
@Slf4j
public class RequestInfoLogAspect {
@Pointcut("execution(* com.dyn.*.*.controller.*Controller.*(..))")
public void excudeService() {}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String method = request.getMethod();
String uri = request.getRequestURI();
String paramString = null;
try {
paramString = JSONUtil.toJsonPrettyStr(pjp.getArgs());
} catch (Exception e) {
}
String jpclass = pjp.getTarget().getClass().toString();
String jpmethod = pjp.getSignature().getName();
log.info("Controller接收请求,URI:{},method:{},params:{},处理类为:{},处理方法为:{}", uri, method, paramString, jpclass, jpmethod);
Object result = pjp.proceed();
log.info("Controller接收结束,Controller:{}方法{}的返回值是:{}", jpclass, jpmethod, JSONUtil.toJsonPrettyStr(result));
return result;
}
}
7.事务
1.Spring事务管理机制
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
- PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理
- TransactionDefinition: 事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。
- TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。
1.PlatformTransactionManager:事务管理器
该接口提供三个方法:
- commit:提交事务
- rollback:回滚事务
- getTransaction:获取事务状态
DataSourceTransactionManager是事务管理器的主要实现,针对JdbcTemplate、MyBatis 事务控制 ,使用Connection(连接)进行事务控制 :
- 开启事务 connection.setAutoCommit(false);
- 提交事务 connection.commit();
- 回滚事务 connection.rollback();
2.TransactionDefinition:事务定义信息
1.事务隔离ACID特性
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):要么都成功,要么都失败。
- 隔离性(Isolation):并发执行的各个事务之间不能互相干扰。
- 持久性(Durability):事务一旦提交,它对数据库中的数据的改变就应该是永久性的。
2.数据库事务并发问题
假设现在有两个事务: Transaction01 和 Transaction02 并发执行。
1.脏读
一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
①Transaction01 将某条记录的 AGE 值从 20 修改为 30。
②Transaction02 读取了 Transaction01 更新后的值: 30。
③Transaction01 回滚, AGE 值恢复到了 20。
④Transaction02 读取到的 30 就是一个无效的值。
2.不可重复读
在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
①Transaction01 读取了 AGE 值为 20。
②Transaction02 将 AGE 值修改为 30。
③Transaction01 再次读取 AGE 值为 30, 和第一次读取不一致。
3.幻读
一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
①Transaction01 读取了 STUDENT 表中的一部分数据。
②Transaction02 向 STUDENT 表中插入了新的行。
③Transaction01 读取了 STUDENT 表时, 多出了一些行。
3.事务隔离级别
default:使用后端数据库默认的隔离级别(spring中的的选择项)
事务隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 | MySQL | Oracle |
---|---|---|---|---|---|---|
read_uncommitted 读未提交 | 允许你读取还未提交的改变了的数据 | √ | √ | √ | √ | × |
read_committed 读已提交 | 允许在并发事务已经提交后读取 | × | √ | √ | √ | √(默认) |
repeatable_read 可重复读 | 对相同字段的多次读取是一致的,除非数据被事务本身改变 | × | × | √ | √(默认) | × |
serializable 串行 | 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。 这在所有的隔离级别中是最慢的 它是典型的通过完全锁定在事务中涉及的数据表来完成的。 | × | × | × | √ | √ |
Mysql可重复读实现原理分析
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
一个事务只需要在启动的时候,找到所有已经提交的事务 ID 的最大值,记为 up_limit_id;
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。
假设事务A id为100,B为101,C为102。现在已经提交了的事务id 为99,最大数据版本为90,那么对于事务A我只认不大于up_limit_id的数据版本,只认90的数据,其他事务修改数据产生更大的数据版本我不认。看下图
Q2将回滚到自己要的数据版本90实现了一致性读。
彩蛋
Q1的查询结果是啥,如果按照上述的规则Q1所在的事务的up_limit_id为99,那么它也只认数据版本为90的数据,这么认为就错了,判断可见性有俩个规则,上述为其中一个,另一个为“自己修改的”,所以Q1最终查询结果为3.
4.事务的传播行为PropagationBehavior
-
7种传播行为
- PROPAGATION_REQUIRED-----支持当前事务,如果不存在 就新建一个
- PROPAGATION_SUPPORTS-----支持当前事务,如果不存在,就不使用事务
- PROPAGATION_MANDATORY----支持当前事务,如果不存在,抛出异常
- PROPAGATION_REQUIRES_NEW-----如果有事务存在,挂起当前事务,创建一个新的事务
- PROPAGATION_NOT_SUPPORTED------以非事务方式运行,如果有事务存在,挂起当前事务
- PROPAGATION_NEVER-----以非事务方式运行,如果有事务存在,抛出异常
- PROPAGATION_NESTED-----如果当前事务存在,则嵌套事务执行
只对DataSourceTransactionManager 起效
-
三大类传播行为
- PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY
支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 。
事务默认传播行为 REQUIRED。最常用的。 - PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。
常用的事务传播行为:PROPAGATION_REQUIRES_NEW - PROPAGATION_NESTED
嵌套事务 ,只对DataSourceTransactionManager有效 ,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点
- PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY
-
常用的三种传播行为
- PROPAGATION_REQUIRED(默认值)
支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 - PROPAGATION_REQUIRES_NEW
不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。 - PROPAGATION_NESTED
嵌套事务 ,只对DataSourceTransactionManager有效 ,
底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点
Connection conn = null; try { conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.executeUpdate("update person set name='888' where id=1"); Savepoint savepoint = conn.setSavepoint(); try{ conn.createStatement().executeUpdate("update person set name='222' where sid=2"); }catch(Exception ex){ conn.rollback(savepoint); } stmt.executeUpdate("delete from person where id=9"); conn.commit(); stmt.close(); } catch (Exception e) { conn.rollback(); }finally{ try { if(null!=conn && !conn.isClosed()) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
- PROPAGATION_REQUIRED(默认值)
5.该接口主要提供的方法:
- getIsolationLevel:隔离级别获取
- getPropagationBehavior:传播行为获取
- getTimeout:获取超时时间(事务的有效期)
- isReadOnly:是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写),事务管理器能够根据这个返回值进行优化。
3.TransactionStatus:事务具体运行状态
- flush()----------------给hibernate使用,底层发出sql的
- hasSavepoint()-------判断是否有保留点
- isCompleted()--------判断事务是否结束
- isNewTransaction()—判断当前事务是否是新开的一个事务。
- isRollbackOnly()------判断事务是否只能回滚
- setRollbackOnly()-----设置事务是否回滚
2.事务管理两种方式
1.编程式的事务管理
通过TransactionTemplate手动管理事务
在实际应用中很少使用,因要修改原来的代码,加入事务管理代码 (侵入性 )
2.XML或注解配置
Spring的声明式事务是通过AOP实现的(环绕通知)、开发中经常使用(码侵入性最小)–推荐使用!
1.xml配置
1.service层
public class UserServiceImpl implements UserService {
@Override
public void change(String outName, String intoName, Double money) {
}
}
2.applicationContext.xml
<!-- 2.配置事务通知 -->
<!-- 人为设定id=“txAdvice” -->
<!-- ransaction-manager 默认值就是 "transactionManager" 国际通用名就是transactionManager,该处可以不写 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置属性的定义 -->
<tx:attributes>
<!-- 配置具体方法的相关事务属性
isolation="DEFAULT" 事务的隔离级别,默认是按数据库的隔离级别来
propagation="REQUIRED" 事务的传播行为,默认是同一个事务
timeout="-1" 事务的超时时间,默认值使用数据库的超时时间。
read-only="false" 事务是否只读,默认可读写。
rollback-for 遇到哪些异常就回滚,其他的都不回滚
no-rollback-for 遇到哪些异常不回滚,其他的都回滚。和上面互斥的
-->
<tx:method name="change" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<!-- 支持通配符 要求service中 方法名字必须符合下面的规则 -->
<!-- read-only="false" 只可读,默认值false(可读可写) -->
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
2.注解配置
// 1.@Transactional 会对该类中的所有方法自动加上事务
@Transactional
@Service(value = "userService")
public class UserServiceImpl implements UserService {
}
//2.启动类添加@EnableTransactionManagement
3.xml和注解的选择
- XML方式,集中式维护,统一放置到applicationContext.xml文件中,缺点在于配置文件中的内容太多。
- 使用@Transactional注解进行事务管理,配置太分散,使用XML进行事务管理,属性集中配置,便于管理和维护
8.常用注解
9.Spring中都用到了哪些设计模式
- 代理模式:AOP
- 单例模式:Spring管理的bean模式都是单例的
- 工厂模式:BeanFactory用来创建对象的实例
- 模板方法:RestTemplate(Http请求工具)
10.开发中主要用到了Spring什么技术
- IOC容器管理bean
- AOP实现日志打印
- 异常处理,@ControllerAdvice、@ExceptionHandler
- 事务@Transactional
- 文件解析:MultipartResolver
- 整合其他框架:@Slf4j