文章目录
第一部分 Spring概述
第1节 Spring简介
- 分层 : 模块式开发
- full-stack(全栈) : 基本包含所有业务
- 轻量级 : 起步占用资源较少,易上手
- 开源框架 : 免费
以IOC和AOP为内核,提供了展现层SpringMVC和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库
第2节 Spring优势
- 方便解耦,简化开发
- 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的
过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更
专注于上层的应⽤。
- 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的
- AOP编程的⽀持(AOP是一种思想,并不是由Spring提出,只是Spring将这思想实现)
- 通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过
AOP轻松应付。
- 通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过
- 声明式事务的支持(AOP实现)
- @Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼
开发效率和质量。
- @Transactional
- 方便测试
- 可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的
事情。
- 可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的
- 方便集成各种优秀框架
- Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、
Quartz等)的直接⽀持。
- Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、
- 降低JavaEE API的使⽤难度
- Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤
难度⼤为降低。
- Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤
- 源码是经典的 Java 学习范例
- Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对
Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。
- Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对
第3节 Spring的核心结构
Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数
据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块
和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零
侵⼊的轻量级框架。
- Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中
bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。
基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼
容器之上。 - ⾯向切⾯编程(AOP)/Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应
⽤系统中开发切⾯的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。横切逻辑代码和业务代码分离 - 数据访问与集成(Data Access/Integration)
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专
注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问
提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由
JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。 - Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅
案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。 - Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测
试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
第二部分 核心思想
注意点 :IOC和AOP是两种思想,并不是由Spring提出,只是Spring在技术层面将这两种思想实现
第1节 IOC[控制反转]
1.1 什么是IOC?
IOC : Inversion of Control(控制反转/反转控制)
描述的事情 : 创建对象和管理对象
传统开发方式 : 比如类A依赖于类B,往往会在类A中添加一个B类型的属性,在使用时需要通过new对象、构造器、set方法等进行属性的赋值
IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对
象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
传统方式存在的问题及IOC容器的解决?
- 在正常开发业务中,除了**实体类(与数据库表相互映射)、vo类(对实体类的封装,返回给前端的类)、dto类(接收前端传入的信息并封装成类)**等是被多次创建。其它所有类都是只需要一个对象即可,如果自己编写单例模式会造成代码量的增加造成不必要的工作量,而IOC容器很好的帮助我们解决这个问题,从IOC容器中取出某个类的对象只有一个,满足了我们的需求、减少了工作量
- 代码冗余、耦合度高,在面向接口开发中,我们往往是接口 对象 = new 实现类();如果我们想要更改接口的实现类会很麻烦。我们想到的解决办法就是xml文件+反射来优化这种问题,而Spring的IOC容器也就是使用了这种思想,并在其基础上增添了其它功能
为什么叫做控制反转?
控制:指的是对对象创建和管理的权利
反转:控制权交给IOC容器去管理
1.2 IOC解决了什么问题
- 在正常开发业务中,除了**实体类(与数据库表相互映射)、vo类(对实体类的封装,返回给前端的类)、dto类(接收前端传入的信息并封装成类)**等是被多次创建。其它所有类都是只需要一个对象即可,如果自己编写单例模式会造成代码量的增加造成不必要的工作量,而IOC容器很好的帮助我们解决这个问题,从IOC容器中取出某个类的对象只有一个,满足了我们的需求、减少了工作量
- 代码冗余、耦合度高,在面向接口开发中,我们往往是接口 对象 = new 实现类();如果我们想要更改接口的实现类会很麻烦。我们想到的解决办法就是xml文件+反射来优化这种问题,而Spring的IOC容器也就是使用了这种思想,并在其基础上增添了其它功能
1.3 IOC和DI之前的区别
DI : Dependancy Injection依赖注入
IOC : 将对象注入容器
IOC和DI描述的都是容器对对象的创建和维护,只是描述的角度不同而已
IOC是站在对象的角度,对象实例化及其管理的权利反转给了容器
DI是站在容器的角度,容器会主动把添加到容器的对象之间的依赖关系
第2节 AOP
2.1 什么是AOP
AOP : Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
AOP是OOP(面向对象)的延续
OOP的三大特征 : 封装、继承和多态
OOP是一种垂直继承体系
OOP编程思想可以解决大多数的代码重复问题,但是只能解决相同行为的类的抽取,也就是说OOP的这种思想主要解决的是类之间的重复问题,那么如果是方法之间的重复问题该如何去解决呢?AOP思想的诞生巧妙解决了这种问题
public class AOPInstance {
public void eatNoddles(){
System.out.println("点菜等待");
//业务代码
System.out.println("吃面条");
System.out.println("结账走人");
}
public void eatRice(){
System.out.println("点菜等待");
//业务代码
System.out.println("吃米饭");
System.out.println("结账走人");
}
}
上述代码我们可以看到,在不同方法中存在相同且重复、与业务没有关系的代码块,我们称这种代码块是横切逻辑代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
横切逻辑代码 :存在的问题
- 横切逻辑代码重复
- 横切逻辑代码和业务代码混杂在一起,耦合度高,不易维护
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
2.2 AOP在解决什么问题
在不改变原有代码的情况下,提取横切逻辑代码,在外部进行配置给业务逻辑
2.3 为什么叫做面向切面编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个
⾯的概念在⾥⾯
第三部分 手写实现IOC和AOP
原始开发模式存在的问题 : 1.new关键字使得类与类的耦合在一起,当需要修改类与类的关系时影响范围大;2.在服务层对数据库操作时不能控制事务
原始操作
-
数据库表结构
-
实体类
public class User { private Integer id; private String account; private Integer money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } }
-
数据交互层
public interface TransferDao { User queryUserByAccount(String account) throws Exception; int updateUserByAccount(User user) throws Exception; }
public class TransferDaoImpl implements dao.TransferDao { public User queryUserByAccount(String account) throws Exception { //从连接池获取连接 Connection con = DruidUtils.getInstance().getConnection(); String sql = "select * from user where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setString(1,account); ResultSet resultSet = preparedStatement.executeQuery(); User user = new User(); while(resultSet.next()) { user.setId(resultSet.getInt("id")); user.setAccount(resultSet.getString("account")); user.setMoney(resultSet.getInt("money")); } resultSet.close(); preparedStatement.close(); con.close(); return user; } public int updateUserByAccount(User user) throws Exception { //从连接池获取连接 Connection con = DruidUtils.getInstance().getConnection(); String sql = "update user set money=? where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setInt(1,user.getMoney()); preparedStatement.setString(2,user.getAccount()); int i = preparedStatement.executeUpdate(); preparedStatement.close(); con.close(); return i; } }
-
业务层
public interface TransferService { void transfer(String fromId,String toId,int money) throws Exception; }
public class TransferServiceImpl implements TransferService { private TransferDao transfer = new TransferDaoImpl(); public void transfer(String fromAccount, String toAccount, int money) throws Exception { User fromUser = transfer.queryUserByAccount(fromAccount); User toUser = transfer.queryUserByAccount(toAccount); fromUser.setMoney(fromUser.getMoney()-money); toUser.setMoney(toUser.getMoney()+money); transfer.updateUserByAccount(fromUser); transfer.updateUserByAccount(toUser); } }
-
工具类
public class DruidUtils { private DruidUtils(){ } private static DruidDataSource druidDataSource = new DruidDataSource(); static { druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/src_spring?serverTimezone=Asia/Shanghai"); druidDataSource.setUsername("root"); druidDataSource.setPassword("root"); } public static DruidDataSource getInstance() { return druidDataSource; } }
原始操作存在的问题
问题一 : new关键字将Dao层实现类TransferDaoImpl和service实现类TransferServiceImpl耦合在一起,当需要切换Dao层实现类时必须要修改service层实现类代码,不符合面向接口开发的最优原则
问题二 : service层没有事务管理,出现异常不能回滚,会造成数据错乱,问题很严重
问题解决思路
- 针对问题一
- new关键字创建对象时,不能灵活更改对象的具体类型也不能从外部获得信息进而创建对象。对此,我们想到了反射技术,反射既可以灵活的更改对象的具体类型也能依据外部信息创建对象
- 使用工厂模式
- 继续优化,我们在一个类型中想要实例化其它类对象,我们不用new关键字,也是通过解析配置文件+工厂+set方法对该属性的赋值
- 针对问题二
- 数据库的事务管理最终是由Connection对象管理
- 在一次服务中可能会创建多个Connection对象,所以不能将这些操作归在一个事务中去
- 将Connection和当前线程绑定(即保证⼀个线程只有⼀个Connection,这样操作才针对的是同⼀个 Connection,进⽽控制的是同⼀个事务)
具体实现
问题一
-
创建外部配置信息文件beans.xml
<?xml version="1.0" encoding="utf-8" ?> <beans> <bean id="transferDao" class="dao.impl.TransferDaoImpl"></bean> <bean id="transferService" class="service.impl.TransferServiceImpl"> <property name="transfer" ref="transferDao"></property> </bean> </beans>
-
创建工厂类BeanFactory
public class BeanFactory { private BeanFactory(){ } /* 工厂的两个任务 1.解析xml文件生成对象并存储 2.对外的方法根据Id获取对象 */ private static Map<String,Object> objects = new HashMap<String, Object>(); static{ //1.将文件以流的方式加载到内存中 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"); SAXReader saxReader = new SAXReader(); try{ //2.获得文档对象 Document document = saxReader.read(in); //3.获得根标签对象 Element rootElement = document.getRootElement(); //4.获得每一个bean标签 List<Element> list = rootElement.selectNodes("//bean"); // 实例化bean对象 for (int i = 0; i < list.size(); i++) { Element element = list.get(i); //5.获得当前bean标签中的id 作为map集合中的key String id = element.attributeValue("id"); //6.获得当前bean标签中的全类名 String clazz = element.attributeValue("class"); Class<?> aClass = Class.forName(clazz); //7.根据全类名创建对象 Object o = aClass.newInstance(); //8. key : id,value : Object进入集合 objects.put(id,o); } //9.获取每一个property标签 List<Element> propertyNodes = rootElement.selectNodes("//property"); for (int i = 0; i < propertyNodes.size(); i++) { Element element = propertyNodes.get(i); //10.获取当前属性名 String name = element.attributeValue("name"); //11.获取赋值的对象 String ref = element.attributeValue("ref"); //12.获取父标签 String parentId = element.getParent().attributeValue("id"); //13.获取父对象 Object parentObject = objects.get(parentId); //14.获得所有的方法 Method[] methods = parentObject.getClass().getMethods(); for (int j = 0; j < methods.length; j++) { Method method = methods[j]; //15.注入进去 if(("set" + name).equalsIgnoreCase(method.getName())) { Object propertyObject = objects.get(ref); method.invoke(parentObject,propertyObject); } } //16.重新加入集合 objects.put(parentId,parentObject); } }catch (Exception e){ e.printStackTrace(); } } public static Object getBean(String id){ return objects.get(id); } }
-
添加set方法
private TransferDao transfer = new dao.impl.TransferDaoImpl(); public void setTransfer(TransferDao transfer) { this.transfer = transfer; }
-
更改原有属性赋值方法
private static TransferService transferService = (TransferService)BeanFactory.getBean("transferService");
问题二
-
增加ConnectionUtils
public class ConnectionUtils { /*private ConnectionUtils() { } private static ConnectionUtils connectionUtils = new ConnectionUtils(); public static ConnectionUtils getInstance() { return connectionUtils; }*/ private ThreadLocal<Connection> threadLocal = new ThreadLocal(); // 存储当前线程的连接 /** * 从当前线程获取连接 */ public Connection getCurrentThreadConn() throws SQLException { /** * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程 */ Connection connection = threadLocal.get(); if(connection == null) { // 从连接池拿连接并绑定到线程 connection = DruidUtils.getInstance().getConnection(); // 绑定到当前线程 threadLocal.set(connection); } return connection; } }
-
增加 TransactionManager 事务管理器类
public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /*private TransactionManager(){ } private static TransactionManager transactionManager = new TransactionManager(); public static TransactionManager getInstance() { return transactionManager; }*/ // 开启手动事务控制 public void beginTransaction() throws SQLException { connectionUtils.getCurrentThreadConn().setAutoCommit(false); } // 提交事务 public void commit() throws SQLException { connectionUtils.getCurrentThreadConn().commit(); } // 回滚事务 public void rollback() throws SQLException { connectionUtils.getCurrentThreadConn().rollback(); } }
-
增加 ProxyFactory 代理⼯⼚类
public class ProxyFactory { private TransactionManager transactionManager; public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public Object getProxy(final Object target) { return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 开启事务 transactionManager.beginTransaction(); // 调⽤原有业务逻辑 result = method.invoke(target, args); // 提交事务 transactionManager.commit(); } catch (Exception e) { e.printStackTrace(); // 回滚事务 transactionManager.rollback(); // 异常向上抛出,便于servlet中捕获 throw e.getCause(); } return result; } }); } }
-
修改 beans.xml
<?xml version="1.0" encoding="utf-8" ?> <beans> <bean id="transferDao" class="dao.impl.TransferDaoImpl"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <bean id="transferService" class="service.impl.TransferServiceImpl"> <property name="transfer" ref="transferDao"></property> </bean> <!--配置新增的三个Bean--> <bean id="connectionUtils" class="utils.ConnectionUtils"></bean> <!--事务管理器--> <bean id="transactionManager" class="utils.TransactionManager"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <!--代理对象⼯⼚--> <bean id="proxyFactory" class="factory.ProxyFactory"> <property name="TransactionManager" ref="transactionManager"/> </bean> </beans>
-
修改dao层实现类代码 : 1.注入ConnectionUtils对象 2.获取连接对象 3.不关闭连接
public class TransferDaoImpl implements dao.TransferDao { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } public User queryUserByAccount(String account) throws Exception { //从连接池获取连接 // Connection con = DruidUtils.getInstance().getConnection(); //从线程中获取连接对象 Connection con = connectionUtils.getCurrentThreadConn(); String sql = "select * from user where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setString(1,account); ResultSet resultSet = preparedStatement.executeQuery(); User user = new User(); while(resultSet.next()) { user.setId(resultSet.getInt("id")); user.setAccount(resultSet.getString("account")); user.setMoney(resultSet.getInt("money")); } resultSet.close(); preparedStatement.close(); //不能关闭 // con.close(); return user; } public int updateUserByAccount(User user) throws Exception { //从连接池获取连接 // Connection con = DruidUtils.getInstance().getConnection(); Connection con = connectionUtils.getCurrentThreadConn(); String sql = "update user set money=? where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setInt(1,user.getMoney()); preparedStatement.setString(2,user.getAccount()); int i = preparedStatement.executeUpdate(); preparedStatement.close(); // con.close(); return i; } }
-
修改测试代码
private static ProxyFactory proxyFactory = (ProxyFactory)BeanFactory.getBean("proxyFactory"); private static TransferService transferService = (TransferService)proxyFactory.getProxy(BeanFactory.getBean("transferService"));
第四部分 Spring IOC应用
第1节 Spring IOC基础
- beans.xml : 定义需要实例化对象类的全限定类名,以及类之间依赖关系描述
- Spring框架的IOC实现 : 1.纯xml形式;2.xml+注解,部分bean使用xml定义,部分bean使用注解定义;3.纯注解模式,所有的bean都是用注解定义
- BeanFactory : IOC容器,通过反射技术来实例化对象并维护对象之间的依赖关系
- 纯xml形式 : javaSE javaWEB(使用监听器加载xml)
- xml+注解
- 纯注解 : javaSE javaWEB(监听器去加载注解配置类)
- ProxyFactory
- ConnectionUtils
- TransactionManager
spring创建容器的几种方式
- 纯xml
- xml+注解(外部资源类)
- 纯注解
1.1 BeanFactory与ApplicationContext的区别
在Spring框架中,BeanFactory是IOC容器的顶级接口,它只用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的一个子接口,ApplicationContext在具备了它功能的基础上做了更多的扩张,比如说国际化支持和资源访问(xml,java配置类等)
启动IOC容器的方式
-
JavaSE环境下启动IOC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
-
Web环境下启动IOC容器
-
xml方式启动容器
<!--配置Spring ioc容器的配置⽂件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!--使⽤监听器启动Spring的IOC容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
-
从配置类启动容器
<!--使⽤注解的⽅式启动ioc容器--> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value> </context-param> <!--配置启动类的全限定类名--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <!--使⽤监听器启动Spring的IOC容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
-
1.2 纯xml模式
-
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 https://www.springframework.org/schema/beans/spring-beans.xsd">
-
实例化对象的三种方式
- ⽅式⼀:使⽤⽆参构造函数在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
-
方式二 : 使用静态方法创建吗,getBean(“工厂标识”)
<!--使⽤静态⽅法创建对象的配置⽅式--> <bean id="工厂标识" class="工厂全类名" factory-method="返回结果是对象的静态方法"></bean>
-
方式三 : 使⽤实例化⽅法创建,getBean(“实体对象标识”)
<!--工厂对象--> <bean id="工厂标识" class="工厂全类名"></bean> <!--实体对象--> <bean id="对象标识" factory-bean="工厂标识" factory-method="工厂中的方法"></bean>
-
Bean的x的生命周期
- 作用范围的改变,在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulAWXg3r-1666610477936)(C:\Users\HuiEr\AppData\Roaming\Typora\typora-user-images\1666249509572.png)]
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)
默认是单例的,如果想要配置成多例模式就要显示的写出来,一般来说不太会使用多例模式
<bean id="userFactoryBean" class="factory.UserFactoryBean" scope="prototype"></bean>
-
不同作用范围的生命周期
单例模式:singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。多例模式:prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
-
Bean标签属性
id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
**class属性:**⽤于指定创建Bean对象的全限定类名。
**name属性:**⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
**factory-bean属性:**⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,
class属性失效。
**factory-method属性:**⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,
则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
**scope属性:**⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,
可以配置为prototype。
**init-method属性:**⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是
⼀个⽆参⽅法。
**destory-method属性:**⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只
能为scope是singleton时起作⽤。 -
DI依赖注入的xml配置
-
依赖注入分类
-
按照注入的方式分类
①构造函数注入,利用带参的构造函数进行注入,不推荐使用这种方式
②set方法注入,利用类成员的set方法实现注入,推荐使用,体现OOP思想中的封装特性
-
按照注入的数据类型分类
①基本类型和String
②其它Bean类型
③复杂类型(集合、数组…),Aarry,List,Set,Map,Properties
-
-
依赖注⼊的配置实现之构造函数注⼊ 顾名思义,就是利⽤构造函数实现对类成员的赋值。它
的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹
配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring
框架会报错。 -
构造器注入常用的标签
- constructor-arg : 根标签
- name : 属性
- index : 索引位置,从1开始,不太常用
- ref : 具体注入的Bean对象的唯一标识
-
set方法注入常用标签
- property : 根标签
- name : 属性名,要和set方法后面拼接的字符串相同(忽略大小写)
- ref : 具体注入的Bean对象的唯一标识
-
1.3 XML与注解相结合模式
需要开启组件扫描
<context:component-scan base-package="扫描的包"/>
注意 :
1)实际企业开发中,纯xml模式使⽤已经很少了
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)第三⽅jar中的bean定义在xml,⽐如连接池,⾃⼰开发的bean定义使⽤注解
- xml标签与注解标签的对应关系
xml形式 | 对应注解形式 |
---|---|
标签 | @Component,@Controller,@Service,@Repository |
scope属性 | @Scope(“”),默认单例,注解加在类上 |
标签的init-method属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
标签的destory-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
注意点 : @Component,@Controller,@Service,@Repository可以添加唯一标识,例如@Component(“唯一标识”),如果没有添加默认使用的是类的首字符小写作为唯一标识
-
DI依赖注入注解实现方式
@Autowired(推荐使⽤)
@Autowired是Spring提供的注解,需要导⼊包
org.springframework.beans.factory.annotation.Autowired。@Autowired采取的策略为按照类型注⼊。
如果一个类型有多个bean时,会造成无法选择具体注入哪一个的情况,这时候我们可以配置@Qualifier(name = “唯一标识”)告诉Spring具体去装配哪个对象。
@Resource
@Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource,在jdk11时被弃用
@Resource 默认按照 ByName ⾃动注⼊。- 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不
到则抛出异常。 - 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异
常。 - 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,
都会抛出异常。 - 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配
注意,如果是在jdk11及以后想要使用Resource则需要引入坐标
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
- 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不
在开发中最多使用的是Autowired方式更具类型注入
1.4 纯注解方式
在上述的xml+注解模式中,容器的启动依旧是需要加载xml文件,所以纯注解的方式就是使用配置类替换原有的xml文件,并使用注解方式将第三方jar包类加入容器
对应注解
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} [SpEl表达式]读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器
第2节 Spring IOC高级特性
2.1 lazy-init 延迟加载
Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前
实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton
bean。
比如:
默认是 : lazy-init=“false”,如果想要开启懒加载只需要将值设置为true即可
<bean id="" calss="" lazy-init="false" />
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器
通过 getBean 索取 bean 时实例化的。
如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例
化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤
时才被实例化的规则。
如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不
会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
使用场景
①开启延迟加载⼀定程度提⾼容器启动和运转性能
②对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占
⽤资源
2.2 FactoryBean 和 BeanFactory
BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,
具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成
某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤
其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤
//实现该接口,可以自定义Bean的创建过程
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例,默认是单例
default boolean isSingleton() {
return true;
}
}
Company类
@Data
public class Company {
private String name;
private String address;
}
CompanyFactoryBean类
public class CompanyFactoryBean implements FactoryBean<Company> {
private String[] arr;
public void setArr(String[] arr) {
this.arr = arr;
}
public Company getObject() throws Exception {
Company company = new Company();
company = new Company();
company.setName(arr[0]);
company.setAddress(arr[1]);
return company;
}
public Class<?> getObjectType() {
return Company.class;
}
public boolean isSingleton() {
return true;
}
}
xml配置
<!--使用工厂生产对象-->
<bean id="company" class="factory.CompanyFactoryBean">
<property name="arr">
<array>
<value>HuiEr</value>
<value>China</value>
</array>
</property>
</bean>
测试,获取FactoryBean工厂产生的对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Company company = (Company)applicationContext.getBean("company");
System.out.println(company);
测试,获取FactoryBean对象,需要在id之前添加’&’
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Company company = (Company)applicationContext.getBean("&company");
System.out.println(company);
2.3 后置处理器
后置处理器的作用域是整个Spiring框架
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和
BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处
理做⼀些事情
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象
SpringBean的⽣命周期
2.3.1 BeanPostProcessor
该处理器在整个Bean创建中起到非常重要的作用,比如AOP实现代理对象就是通过该接口
这是一个接口,该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,初始化方法就是我们定义的init-method方法
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中的所有bean进行处理。如果要对具体的某个bean处理,可以是用方法的参数进行判断
注意 : 该后置处理器是在对象实例化和依赖注入之后
实体类
public class Admin{
public Admin(){
System.out.println("实例化");
}
public void initMethod(){
System.out.println("初始化方法");
}
}
BeanPostProcessor实现类
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Admin){
System.out.println("first");
}
return null;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Admin){
System.out.println("end");
}
return null;
}
}
xml配置
<bean id="myBeanPostProcessor" class="config.MyBeanPostProcessor"></bean>
<bean id="admin" class="domain.entity.Admin" init-method="initMethod"></bean>
测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Admin admin = (Admin) applicationContext.getBean("admin");
/*
结果:
实例化
first
初始化方法
end
*/
2.3.2 BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的工厂进行处理
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor(){
System.out.println("工厂后置处理器初始化");
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("工厂后置处理器方法调用");
}
}
xml配置
<bean id="myBeanFactoryPostProcessor" class="config.MyBeanFactoryPostProcessor"></bean>
测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Admin admin = (Admin) applicationContext.getBean("admin");
/*
结果:
工厂后置处理器初始化
工厂后置处理器方法调用
*/
接口方法的参数 : ConfigurableListableBeanFactory
ConfigurableListableBeanFactory该类的相应方法
void ignoreDependencyType(Class<?> var1);
void ignoreDependencyInterface(Class<?> var1);
void registerResolvableDependency(Class<?> var1, @Nullable Object var2);
boolean isAutowireCandidate(String var1, DependencyDescriptor var2) throws NoSuchBeanDefinitionException;
//读取解析xml文件并且将每一个<bean>标签封装成一个BeanDefinition对象,这个很重要
BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
Iterator<String> getBeanNamesIterator();
void clearMetadataCache();
void freezeConfiguration();
boolean isConfigurationFrozen();
void preInstantiateSingletons() throws BeansException;
BeanDefinition类就是当我们启动容器后,工厂初始化完毕,将所有的标签封装成的对象,该对象中有一切我们在xml文件中配置的信息
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成
BeanDefinition对象
第五部分 Spring IOC源码分析
第1节 SpringIOC容器初始化主要流程
1.1 Spring IOC的容器体系
IOC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring 提供了很多
的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IOC容器 必须遵从
的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具
体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplication则是包含了注解解析等一系列的内容
1.2 Bean生命周期关键时机
①如果未设置延迟加载,Bean的创建时机是在容器初始化过程中完成的
ApplicationContext => BeanFactory => singletonObjects(Map集合,存放着所有容器中的实例化对象)
有些人认为,singletonObjects这个Map集合就是IOC容器。其实并不是,这个Map集合只是解析xml文件后,将id作为key值,将实例化对象作为value的一个缓存集合,也就是说这里面的对象并不是真正的Bean对象,Bean对象需要经过一系列流水线操作,由此可以得出,Bean一定是对象,对象不一定是Bean
②通过对容器中某个实体类的构造器添加断点可以发现,构造函数的调用时机在AbstractApplicationContext类**refresh⽅法的finishBeanFactoryInitialization(beanFactory)处
③分析InitializingBean接口的afterPropertiesSet方法调用时机依然在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory)**处
④分析BeanFactoryPostProcessor(工厂初始化后的后置处理器)初始化和调用情况
在后置处理器的构造器和方法处打断点可以发现BeanFactoryPostProcessor的初始化在AbstractApplicationContext类**refresh⽅法的
invokeBeanFactoryPostProcessors(beanFactory);
而后置处理器的具体处理的方法postProcessBeanFactory调用也在AbstractApplicationContext类refresh⽅法的invokeBeanFactoryPostProcessors**(beanFactory);
所以,工厂后置处理器的初始化和方法的调用都在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory);中
⑤分析 BeanPostProcessor(对象实例化后进行后置处理,注意 : 这里还不是Bean实例) 初始化和调⽤情况
在处理器的构造函数、方法调用处打断点
BeanPostProcessor 初始化在AbstractApplicationContext类**refresh⽅法的registerBeanPostProcessors(beanFactory)**;
postProcessBeforeInitialization 调⽤在AbstractApplicationContext类**refresh**⽅法的
finishBeanFactoryInitialization(beanFactory); 对象初始化方法之前调用
postProcessAfterInitialization 调⽤在AbstractApplicationContext类**refresh**⽅法的
**finishBeanFactoryInitialization(beanFactory);**对象初始化方法之后调用
⑥归纳总结
根据上述分析,我们可以发现在Bean对象的创建流程中,其中被涉及的类是AbstractApplicationContext,被多次调用的方法是**refresh**方法中
关键点 | 代码时机 |
---|---|
实体类的构造方法 | AbstractApplicationContext类**refresh⽅法的finishBeanFactoryInitialization(beanFactory)** |
InitializingBean接口 | AbstractApplicationContext类**refresh⽅法的finishBeanFactoryInitialization(beanFactory)** |
工厂的后置处理器 | AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory); |
对象后置处理器实例化 | AbstractApplicationContext类**refresh⽅法的registerBeanPostProcessors(beanFactory)**; |
AbstractApplicationContext类**refresh⽅法的finishBeanFactoryInitialization(beanFactory)**; |
1.3 Spring IOC容器初始化主流程
关键类 : AbstractApplicationContext
关键方法 : refresh
refresh方法解析
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
//1.预处理
prepareRefresh();
//2.获取BeanFactory工厂,默认实现类DefaultListableBeanFactory
//解析xml文件并将每一个<bean>标签存放在BeanDefition中,并放置在BeanDefinitionRegistry缓存中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//3.BeanFactory的预准备工作,对工厂一些设置
prepareBeanFactory(beanFactory);
try {
//4.BeanFactory准备工作完成后进行的后置处理工作,留给子类使用
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
//5.实例化工厂后置处理器,调用工厂后置处理器的方法
invokeBeanFactoryPostProcessors(beanFactory);
//6.注册Bean的后置处理器
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
//7.初始化MessageSource组件
initMessageSource();
//8.初始化事件派发器
initApplicationEventMulticaster();
//9.子类重写该方法,自定义容器刷新时的逻辑
onRefresh();
//10.注册监听器
registerListeners();
//11.初始化所有单例模式的对象,填充属性,调用初始化方法,调用对象后置处理器等
finishBeanFactoryInitialization(beanFactory);
//12.对context的刷新
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
第2节 BeanFactory创建流程
2.1 获取BeanFactory对象的流程
2.2 BeanDefition加载解析及注册子流程
- Resource定位 : 找到JavaBean信息的xml文件,并将其封装成Resource对象 ,通俗的讲就是读取xml文件并以流的方式存储在内存中
- BeanDefinition载入 : 解析流并将每一个JavaBean封装成BeanDefinition对象,并且将每一个BeanDefinition对象添加到BeanFactory工厂中去
过程分析
//XmlBeanDefinitionReader中的doLoadBeanDefinitions方法关键点
Document doc = this.doLoadDocument(inputSource, resource);//读取xml信息,将xml中信息保存到Document对象
int count = this.registerBeanDefinitions(doc, resource);//解析document对象,封装成BeanDefinition对象并进行注册
//XmlBeanDefinitionReader中的registerBeanDefinitions方法关键点
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();//获取已有的BeanDefinition对象数目
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));//注册
return this.getRegistry().getBeanDefinitionCount() - countBefore;//返回注册后的数目
在注册时我们关注两个地方 : 一个createReaderContext方法,一个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions⽅法
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = this.createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = this.getResourceLoader() != null ? this.getResourceLoader().getClassLoader() : this.getBeanClassLoader();
return new DefaultNamespaceHandlerResolver(cl);
}
此时Spring首先完成了NamespaceHandlerResolver
DefaultBeanDefinitionDocumentReader的registerBeanDefinitions ⽅法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.doRegisterBeanDefinitions(doc.getDocumentElement());
}
DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions⽅法
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
DefaultBeanDefinitionDocumentReader的parseBeanDefinitions⽅法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for(int i = 0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element)node;
if (delegate.isDefaultNamespace(ele)) {
//解析默认bean元素
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
//解析自定义标签
delegate.parseCustomElement(root);
}
}
DefaultBeanDefinitionDocumentReader的parseDefaultElement⽅法
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}
DefaultBeanDefinitionDocumentReader的processBeanDefinition⽅法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//解析并封装
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//完成注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
⾄此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition的
可以在DefaultListableBeanFactory中看到此Map的定义
private final Map<String, BeanDefinition> beanDefinitionMap;
第3节 Bean创建流程
- AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory) 处创建Bean的开始位置
- 进入finishBeanFactoryInitialization中可以看到beanFactory.preInstantiateSingletons();是对所有单例Bean进行实例化
- 继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例
- 继续跟踪下去,进⼊到了AbstractBeanFactory类的doGetBean⽅法
- 接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法
- 进⼊doCreateBean⽅法
第4节 lazy-init延迟加载机制原理
- lazy-init 延迟加载机制分析
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥
第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解
析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个
BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进
⾏初始化并依赖注⼊。
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof
SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new
PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
}else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}
- 总结
- 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
进⾏getBean时候才进⾏初始化并依赖注⼊ - 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经
初始化完成并缓存了起来
- 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
第5节 循环依赖
5.1 什么是循环依赖?
循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean互相持有对⽅,最终形成闭环。⽐如A
依赖于B,B依赖于C,C⼜依赖于A。
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结
条件。
Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注⼊)
- Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决
属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。
5.2 循环依赖处理机制
- 单例bean构造器参数值循环机制(无法解决)
- prototype原型bean循环依赖(无法解决)
例如上图有两个类之间发生了循环依赖,即A的创建首先需要有B,B的创建首先需要有A;注意 : 这里的创建并不是单纯的实例化对象,而是创建成Bean对象
在Spring中的处理流程
- 容器准备创建A对象
- 检查一级缓存池中是否已经存在A,如果存在直接返回,如果不存在继续流程
- 实例化A对象并对外暴露,加入三级缓存
- 给实例化的A对象填充属性,开始处理属性依赖
- 检查一级缓存池、二级缓存、三级缓存中是否已经存在B,如果存在直接返回,如果不存在继续流程
- 实例化B对象并对外暴露,加入三级缓存
- 给实例化的B对象填充属性,开始处理属性依赖
- 检查发现三级缓存池中已经有A的对象,提取A的对象并完成拓展任务,并把A实例化对象添加到二级缓存中去
- B实例化对象可以使用二级缓存池中的A实例化对象完成B的创建,同时删除三级和二级缓存池中的自己,并将B对象放在一级缓存池中
- A对象就可以从一级缓存中拿到B对象,完成A创建,并添加到一级缓存中,同时删除三级和二级缓存池中的自己
第6节 Bean的生命周期
6.1 流程图
Bean ⽣命周期的整个执⾏过程描述:
1)根据配置情况调⽤ Bean 构造⽅法或⼯⼚⽅法实例化 Bean。
2)利⽤依赖注⼊完成 Bean 中所有属性值的配置注⼊。
3)如果 Bean 实现了 BeanNameAware 接⼝,则 Spring 调⽤ Bean 的 setBeanName() ⽅法传⼊当前 Bean 的 id 值。
4)如果 Bean 实现了 BeanFactoryAware 接⼝,则 Spring 调⽤ setBeanFactory() ⽅法传⼊当前⼯⼚实例的引⽤。
5)如果 Bean 实现了 ApplicationContextAware 接⼝,则 Spring 调⽤ setApplicationContext() ⽅法传⼊
当前 ApplicationContext 实例的引⽤。
6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调⽤该接⼝的预初始化⽅法
postProcessBeforeInitialzation() 对 Bean 进⾏加⼯操作,此处⾮常重要,Spring 的 AOP 就是利⽤它实现的。
7)如果 Bean 实现了 InitializingBean 接⼝,则 Spring 将调⽤ afterPropertiesSet() ⽅法。
8)如果在配置⽂件中通过 init-method 属性指定了初始化⽅法,则调⽤该初始化⽅法。
9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调⽤该接⼝的初始化⽅法 postProcessAfterInitialization()。
此时,Bean 已经可以被应⽤系统使⽤了。
10)如果在 中指定了该 Bean 的作⽤范围为 scope=“singleton”,则将该 Bean 放⼊ Spring IoC 的缓存池中,
将触发 Spring 对该 Bean 的⽣命周期管理;如果在 中指定了该 Bean 的作⽤范围为 scope=“prototype”,则将该 Bean 交给调⽤者,调⽤者管理该 Bean 的⽣命周期,Spring 不再管理该 Bean。
11)如果 Bean 实现了 DisposableBean 接⼝,则 Spring 会调⽤ destory() ⽅法将 Spring 中的 Bean 销毁;
如果在配置⽂件中通过 destory-method 属性指定了 Bean 的销毁⽅法,则 Spring 将调⽤该⽅法对 Bean 进⾏销毁。
注意:Spring 为 Bean 提供了细致全⾯的⽣命周期过程,通过实现特定的接⼝或 的属性设置,都可以对 Bean 的⽣命周期过程产⽣影响。
第六部分 Spring AOP应用
AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代
码、事务控制代码、性能监控代码[获得方法的参数以及返回值]。
第1节 AOP相关术语
1.1 业务主线
在上述流程中,AOP思想可以帮助我们解决许多重复的代码,使得代码的独立性增强, 把横切逻辑代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增 强。
1.2 AOP术语
名词 | 解释 |
---|---|
JoinPoint(连接点) | ⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接 点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点 |
PointCut(切入点) | 指定AOP思想想要影响的具体⽅法 |
Advice(通知/增强) | 提取出来的横切逻辑代码,根据添加的时机可以分类 : 前置通知 后置通知 异常通知 最终通知 环绕通知 |
Target(目标) | 代理的目标对象。即被代理的对象 |
Proxy(代理) | 一个类被AOP织入增强后,产生的代理类 |
Weaving(织入) | 把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
Aspect(切面) | 切入点 + 增强 |
Aspect = 切入点 + 增强
= 切入点(锁定具体的方法) + 方位点(锁定方法的特殊时机) + 增强
第2节 Spring中AOP的代理选择
Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现
任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过
我们可以通过配置的⽅式,让Spring强制使⽤CGLIB
第3节 Spring中AOP的配置方式
- 纯xml
- xml + 注解
- 纯注解
第4节 Spring中AOP的具体实现
- 明确切入点
- 明确增强
- 明确切面(切入点+方位点+增强)
4.1 xml模式
Spring是模块式开发的框架,使用AOP就引入AOP的jar
-
原始项目
-
数据库表结构
-
实体类
@Data public class User { private Integer id; private String account; private Integer money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } }
-
Service层
public interface TransferService { void transfer(String fromId, String toId, int money) throws Exception; } public class TransferServiceImpl implements TransferService { private TransferDao transferDao; public void transfer(String fromAccount, String toAccount, int money) throws Exception { User fromUser = transferDao.queryUserByAccount(fromAccount); User toUser = transferDao.queryUserByAccount(toAccount); fromUser.setMoney(fromUser.getMoney()-money); toUser.setMoney(toUser.getMoney()+money); transferDao.updateUserByAccount(fromUser); transferDao.updateUserByAccount(toUser); } public void setTransferDao(TransferDao transferDao) { this.transferDao = transferDao; } }
-
Dao层
public interface TransferDao { User queryUserByAccount(String account) throws Exception; int updateUserByAccount(User user) throws Exception; } public class TransferDaoImpl implements dao.TransferDao { public User queryUserByAccount(String account) throws Exception { Connection con = DruidUtils.getInstance().getConnection(); String sql = "select * from user where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setString(1,account); ResultSet resultSet = preparedStatement.executeQuery(); User user = new User(); while(resultSet.next()) { user.setId(resultSet.getInt("id")); user.setAccount(resultSet.getString("account")); user.setMoney(resultSet.getInt("money")); } resultSet.close(); preparedStatement.close(); con.close(); return user; } public int updateUserByAccount(User user) throws Exception { Connection con = DruidUtils.getInstance().getConnection(); String sql = "update user set money=? where account=?"; PreparedStatement preparedStatement = con.prepareStatement(sql); preparedStatement.setInt(1,user.getMoney()); preparedStatement.setString(2,user.getAccount()); int i = preparedStatement.executeUpdate(); preparedStatement.close(); con.close(); return i; } }
-
工具类
public class DruidUtils { private DruidUtils(){ } private static DruidDataSource druidDataSource = new DruidDataSource(); static { druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/src_spring?serverTimezone=Asia/Shanghai"); druidDataSource.setUsername("root"); druidDataSource.setPassword("root"); } public static DruidDataSource getInstance() { return druidDataSource; } }
-
-
坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
-
编写横切逻辑代码,并封装成类中的方法
public class LogAspect { //before public void before(){ System.out.println("===========before==========="); } //正常结束 public void after(){ System.out.println("===========after==========="); } //异常产生后 public void exceptionAfter(){ System.out.println("===========exceptionAfter==========="); } }
-
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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--JavaBean--> <bean id="transferDao" class="dao.impl.TransferDaoImpl"/> <bean id="transferService" class="service.impl.TransferServiceImpl"> <property name="transferDao" ref="transferDao"/> </bean> <!--横切逻辑代码类--> <bean id="logAspect" class="aspect.LogAspect"/> <!--根--> <aop:config> <!--切面--> <aop:aspect id="logAdvice" ref="logAspect"> <!--前置通知--> <aop:before method="before" pointcut="execution(public * service.impl.TransferServiceImpl.*(..))"></aop:before> <!--后置通知--> <aop:after method="after" pointcut="execution(public * service.impl.TransferServiceImpl.*(..))"></aop:after> </aop:aspect> </aop:config> </beans>
-
测试
ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); TransferService bean = app.getBean(TransferService.class); bean.transfer("李明","鲍勃",100); /* 结果 : ===========before=========== 信息: {dataSource-1} inited ===========after=========== */
-
细节
-
切点表达式 : execution(被增强的方法);
-
概念及作用
切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。
-
关于AspectJ
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架
中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。
-
示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表) 访问修饰符可以省略 返回值可以使⽤*,表示任意返回值 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个 包名可以使⽤..表示当前包及其⼦包 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法 * ...(com.lagou.pojo.Account) 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int 引⽤类型必须写全限定类名:java.lang.String 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 * *..*.*(*) 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型 * *..*.*(..) 全通配⽅式: * *..*.*(..)
-
-
改变代理⽅式的配置
Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择 的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝ 的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。
⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提 供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤ 基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种
-
<aop:config proxy-target-class="true">
-
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP 的⽀持--> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
-
-
五种通知方式
- 前置通知
- 正常执行时通知
- 异常通知
- 最终通知
- 环绕通知
-
4.2 xml+注解模式
-
xml中开启扫描注解
<!--开启spring对注解aop的⽀持--> <aop:aspectj-autoproxy/> <context:component-scan base-package="aspect"/>
-
在横切逻辑类上中添加相应的注解
@Component @Aspect public class LogAspect { @Pointcut("execution(public * service.impl.TransferServiceImpl.*(..))") public void pt(){} //before @Before("pt()") public void before(){ System.out.println("===========before==========="); } //正常结束 @After("pt()") public void after(){ System.out.println("===========after==========="); } //异常 public void exceptionAfter(){ System.out.println("===========exceptionAfter==========="); } }
4.3 纯注解模式
在使用xml+注解模式开发中,xml只配置了开启注解扫描
<!--开启组件扫描-->
<aop:aspectj-autoproxy/>
<context:component-scan base-package="aspect"/>
在纯注解模式中,我们只需要使用配置类去替换它即可
@Configuration
@ComponentScan("com.huier")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
第5节 Spring声明式事务的支持
- 编程式事务 : 在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
- 声明式事务 : 通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务
5.1 事务回顾
在传统的三层架构中,我们的业务层和数据交互层是分开的,在一次业务处理中我们可能会调用多个数据交互层的方法。我们期望在执行中,要么全部成功,要么全部失败,保证了数据的整体性
这个需求和我们的生活紧密相关,例如在转账中,A向B转账,但是系统出现了错误导致A的钱已经被扣,而B没有收到A的转账,这是一个巨大的隐患
5.1.2 事务的四大特征
-
原⼦性(Atomicity) 原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都
不发⽣。 在一次事务中,所有的操作是一个整体,要么同时成功,要么同时失败
-
⼀致性(Consistency) 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
事务就是对数据库数据的操作,而一致性是指,显示的数据和我们期望的数据一致 -
隔离性(Isolation) 事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,
每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
事务在没有提交时表示这个操作还没有真正进行,数据并没有改变
-
持久性(Durability)
持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障
也不应该对其有任何影响。
5.1.3 事务的隔离级别(MySQL)
隔离级别就是对数据库操作中一些问题的解决策略
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted(读未提交) 、Read committed(读提交) 、Repeatable read(可重复读取) 、Serializable(可序列化) 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读
三种问题的解释
-
脏读 : ⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
-
不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)场景:
员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭
财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务
员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读
-
虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条
数不⼀样)场景:
事务1查询所有⼯资为1w的员⼯的总数,查询出来了10个⼈,此时事务尚未关闭
事务2财务⼈员发起,新来员⼯,⼯资1w,向表中插⼊了2条数据,并且提交了事务
事务1再次查询⼯资为1w的员⼯个数,发现有12个⼈
四种隔离级别分别解决的问题
- Read uncommitted(读未提交):解决同时操作数据,但是产生脏读(读取到未提交的数据)
如果在一个事务中已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据 - Read committed(读提交):解决同时操作数据、脏读,但是产生了如果事务还没有结束读到不同的数据
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。 - Repeatable read(可重复读取):解决同时操作数据、脏读、可重复读,但是产生了幻读
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读(一个事务在前后两次查询同一个范围的时候、后一次查询看到了前一次查询未看到的行) - Serializable(可序化):解决同时操作数据、脏读、可重复读、幻读提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低,像Serializeble这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况来,在MYSQL数据库中默认的隔离级别是Repeatable read(可重复读)
在MySQL数据库中查看当前事务的隔离级别
SELECT @@tx_isolation;
在MYSQL数据库中设置事务的隔离级别
set session transaction isolation level xxx; (设置的是当前mysql连接会话的,并不是永久改变的)
设置事务隔离级别,一定要在事务开启之前
5.1.4 事务的传播行为(@Transactional)
事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本
身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏
为
A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为
PROPAGATION_REQUIRED | 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务,加⼊到这个事务中。这是最常⻅的选择。 |
---|---|
PROPAGATION_SUPPORTS | ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。 |
PROPAGATION_MANDATORY | 使⽤当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。 |
5.2 Spring中事务的API
- mybatis: sqlSession.commit();
- hibernate: session.commit();
PlatformTransactionManager
public interface PlatformTransactionManager {
//获取事务状态
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
void rollback(TransactionStatus status) throws TransactionException;
}
作用
此接⼝是Spring的事务管理器核⼼接口。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层
⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。在Spring框架中,也为我们
内置了⼀些具体策略,例如:DataSourceTransactionManager , HibernateTransactionManager 等
等。( 和 HibernateTransactionManager 事务管理器在 spring-orm-5.1.12.RELEASE.jar 中)
Spring JdbcTemplate(数据库操作⼯具)、Mybatis(mybatis-spring.jar—>DataSourceTransactionManager
Hibernate框架 ——————> HibernateTransactionManager
DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代
理)来将事务控制逻辑织⼊到业务代码
5.3 Spring声明式事务配置
-
纯xml模式
-
导入jar
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency>
-
xml配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--定制事务细节,传播⾏为、隔离级别等--> <tx:attributes> <!--⼀般性配置--> <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/> <!--针对查询的覆盖性配置--> <tx:method name="query*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <aop:config> <!--advice-ref指向增强=横切逻辑+⽅位--> <aop:advisor advice-ref="txAdvice" pointcut="execution()"/> </aop:config>
-
-
基于xml+注解
-
xml配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManage r"> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启spring对注解事务的⽀持--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
在接⼝、类或者⽅法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
-
-
纯注解
-
Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个
注解替换掉xml配置⽂件中的 <tx:annotation-driven transaction
manager=“transactionManager”/> 配置。
在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可
@EnableTransactionManagement//开启spring注解事务的⽀持
-