Spring基础和源码剖析

第一部分 Spring 概述

第1节 Spring 简介

Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IOC 和 AOP 为内核,提供了展现层SpringMVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已经成为使用最多的 Java EE 企业应用开源框架。

Spring 指的是Spring Framework(spring 框架)。

第2节 Spring 的优势

  • 方便解耦,简化开发
    通过Spring的AOP功能,方便进行横向切面的编程,许多不容易实现传统OOP实现的功能可以通过AOP轻松应付。

  • AOP编程的支持
    @Transactional
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  • 声明式事务的支持
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  • 方便程序的测试
    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随处可做的事情。

  • 方便集成各种优秀框架
    Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。

  • 降低JavaEE API的使用难度
    Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度为降低。

  • 源码是经典的 Java 学习范例
    Spring的源代码设计精妙、结构清晰、匠心独运,处处体现着大师对Java设计模式灵活运用以及对Java技术的深造诣。它的源代码是Java技术的最佳实践的范例。

第3节 Spring 的核心结构

主要包括几个大模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和 Test 模块,如下图所示

在这里插入图片描述

  • 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之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了非常好的实现(Java)

第1节 IoC

1.1 什么是IoC?

IoC Inversion of Control (控制反转/反转控制),注意它是一个技术思想,不是一个技术实现。

传统开发方式:比如类A依赖于类B,往往会在类A中new一个B的对象。

IoC思想下开发方式:我们不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IoC容器要即可

我们丧失了一个权利(创建、管理对象的权利),得到了一个福利(不用考虑对象的创建、管理等一系列事情)

为什么叫做控制反转?

控制:指的是对象创建(实例化、管理)的权利

反转:控制权交给外部环境了(spring框架、IoC容器)

在这里插入图片描述

1.2 IoC解决了什么问题

IoC解决对象之间的耦合问题
在这里插入图片描述

1.3 IoC和DI的区别

DI:Dependancy Injection(依赖注入)
怎么理解:
IOC和DI描述的是同一件事情,只不过⻆度不一样罢了
在这里插入图片描述

第2节 AOP

2.1 什么是AOP

AOP: Aspect oriented Programming 面向切面编程/面向接口编程
AOP是OOP的延续,从OOP说起
OOP三个特征:封装、继承和多态
oop是一种垂直继承体系

在这里插入图片描述

OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如下面的在顶级父类Animal中的多个方法中相同位置出现了重复代码,OOP就解决不了

在这里插入图片描述
横切逻辑代码

在这里插入图片描述

横切逻辑代码存在什么问题:

  • 横切代码重复问题
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便

AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析代码拆分
在这里插入图片描述

2.2 AOP在解决什么问题

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复

2.3 为什么叫做面向切面编程

「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑
「面」:横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个
面的概念在里面

第三部分 手写实现 IoC 和 AOP

上一部分我们理解了 IoC 和 AOP 思想,我们先不考虑 Spring 是如何实现这两个思想的,此处准备了一个『银行转账』的案例,分析该案例在代码层次有什么问题 ?

第1节 银行转账案例界面

在这里插入图片描述

第2节 银行转账案例表结构

在这里插入图片描述

第3节 银行转账案例代码调用关系

在这里插入图片描述

第4节 银行转账案例关键代码

  • TransferServlet
package com.lagou.edu.servlet;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
 // 1. 实例化service层对象
 private TransferService transferService = new TransferServiceImpl();
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 doPost(req,resp);
 }
 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
 // 设置请求体的字符编码
	 req.setCharacterEncoding("UTF-8");
	 String fromCardNo = req.getParameter("fromCardNo");
	 String toCardNo = req.getParameter("toCardNo");
	 String moneyStr = req.getParameter("money");
	 int money = Integer.parseInt(moneyStr);
	 Result result = new Result();
 	try {
		 // 2. 调⽤service层⽅法
		 transferService.transfer(fromCardNo,toCardNo,money);
		 result.setStatus("200");
	 } catch (Exception e) {
	 e.printStackTrace();
	 result.setStatus("201");
	 result.setMessage(e.toString());
 }
 // 响应
 	resp.setContentType("application/json;charset=utf-8");
 	resp.getWriter().print(JsonUtils.object2Json(result));
 }
}
  • TransferService接口及实现类
package com.lagou.edu.service;
public interface TransferService {
 void transfer(String fromCardNo,String toCardNo,int money) throws Exception; 
}

package com.lagou.edu.service.impl;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.dao.impl.JdbcAccountDaoImpl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.TransferService;
/**
* @author 应癫
*/
public class TransferServiceImpl implements TransferService {
	 private AccountDao accountDao = new JdbcAccountDaoImpl();
	 @Override
	 public void transfer(String fromCardNo, String toCardNo, int money)
	throws Exception {
		 Account from = accountDao.queryAccountByCardNo(fromCardNo);
		 Account to = accountDao.queryAccountByCardNo(toCardNo);
		 from.setMoney(from.getMoney()-money);
		 to.setMoney(to.getMoney()+money);
		 accountDao.updateAccountByCardNo(from);
		 accountDao.updateAccountByCardNo(to);
	 }
}
//AccountDao层接口及基于Jdbc的实现类
package com.lagou.edu.dao;
import com.lagou.edu.pojo.Account;
public interface AccountDao {
 	Account queryAccountByCardNo(String cardNo) throws Exception;
 	int updateAccountByCardNo(Account account) throws Exception; }
}
  • JdbcAccountDaoImpl(Jdbc技术实现Dao层接⼝
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author yecxf
*/
public class JdbcAccountDaoImpl implements AccountDao {
	 @Override
	 public Account queryAccountByCardNo(String cardNo) throws Exception {
		 //从连接池获取连接
		 Connection con = DruidUtils.getInstance().getConnection();
		 String sql = "select * from account where cardNo=?";
		 PreparedStatement preparedStatement = con.prepareStatement(sql);
		 preparedStatement.setString(1,cardNo);
		 ResultSet resultSet = preparedStatement.executeQuery();
		 Account account = new Account();
		 while(resultSet.next()) {
		 account.setCardNo(resultSet.getString("cardNo"));
		 account.setName(resultSet.getString("name"));
		 account.setMoney(resultSet.getInt("money"));
	 }
	 resultSet.close();
	 preparedStatement.close();
	 con.close();
	 return account;
 }
 
 @Override
 public int updateAccountByCardNo(Account account) throws Exception {
		 //从连接池获取连接
		 Connection con = DruidUtils.getInstance().getConnection();
		 String sql = "update account set money=? where cardNo=?";
		 PreparedStatement preparedStatement = con.prepareStatement(sql);
		 preparedStatement.setInt(1,account.getMoney());
		 preparedStatement.setString(2,account.getCardNo());
		 int i = preparedStatement.executeUpdate();
		 preparedStatement.close();
		 con.close();
		 return i;
	 }
}

第5节 银行转账案例代码问题分析

在这里插入图片描述
(1)问题一:在上述案例实现中,service 层实现类在使用 dao 层对象时,直接在
TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然而一个 new 关键字却将 TransferServiceImpl 和 dao 层具体的一个实现类
JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao 层的实现要使用其它技术,比如 Mybatis,思考切换起来的成本?每一个 new 的地方都需要修改源代码,重新编译,面向接口开发的意义将大打折扣?

(2)问题二:service 层代码没有竟然还没有进行事务控制 ?如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在金融业务。

第6节 问题解决思路

  • 针对问题一思考:

    • 实例化对象的模式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml中)
  • 考虑使用设计模式中的工厂模式解耦合,另外项目中往往有很多对象需要实例化,那就在工厂中使用反射技术实例化对象,工厂模式合适。

在这里插入图片描述
更进一步,代码中能否只声明所需实例的接口类型,不出现 new 也不出现工厂类的字眼,如下图? 能!声明一个变量并提供 set 方法,在反射的时候将所需要的对象注入进去吧
在这里插入图片描述

  • 针对问题二思考:
    • service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,手动控制 JDBC 的Connection 事务,但要注意将Connection和当前线程绑定(即保证一个线程只有一个Connection,这样操作才针对的是同一个 Connection,进而控制的是同一个事务)

在这里插入图片描述

第6节 案例代码改造

(1)针对问题一的代码改造

  • beans.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
 <bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
 <property name="AccountDao" ref="accountDao"></property>
 </bean>
 <bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
 </bean>
</beans>
  • 增加 BeanFactory.java
package com.lagou.edu.factory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author yecxf
*/
public class BeanFactory {
 /**
 * 接口类的两个任务
 * 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放入map待用
 * 任务二:提供接口方法根据id从map中获取bean(静态方法)
 */
 private static Map<String,Object> map = new HashMap<>();
 static {
	 InputStream resourceAsStream =
	 BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
	 SAXReader saxReader = new SAXReader();
 	try {
		 Document document = saxReader.read(resourceAsStream);
		 Element rootElement = document.getRootElement();
		 List<Element> list = rootElement.selectNodes("//bean");
		 // 实例化bean对象
		 for (int i = 0; i < list.size(); i++) {
			 Element element = list.get(i);
			 String id = element.attributeValue("id");
			 String clazz = element.attributeValue("class");
			 Class<?> aClass = Class.forName(clazz);
			 Object o = aClass.newInstance();
			 map.put(id,o);
 		 }
		 // 维护bean之间的依赖关系
		 List<Element> propertyNodes =
		 rootElement.selectNodes("//property");
		 for (int i = 0; i < propertyNodes.size(); i++) {
		 Element element = propertyNodes.get(i);
		 // 处理property元素
		 String name = element.attributeValue("name");
		 String ref = element.attributeValue("ref");
		 
		 String parentId =
		 element.getParent().attributeValue("id");
		 Object parentObject = map.get(parentId);
		 Method[] methods = parentObject.getClass().getMethods();
		 for (int j = 0; j < methods.length; j++) {
		 Method method = methods[j];
		 if(("set" + name).equalsIgnoreCase(method.getName()))
		{
			 // bean之间的依赖关系(注入bean)
			 Object propertyObject = map.get(ref);
			 method.invoke(parentObject,propertyObject);
		 }
		 }
		 // 维护依赖关系后重新将bean放入map中
		 map.put(parentId,parentObject);
		 }
 	} catch (DocumentException e) {
 		e.printStackTrace();
 	} catch (ClassNotFoundException e) {
		 e.printStackTrace();
 	} catch (IllegalAccessException e) {
		 e.printStackTrace();
	 } catch (InstantiationException e) {
 		e.printStackTrace();
	 } catch (InvocationTargetException e) {
		 e.printStackTrace();
	 }
 }
 public static Object getBean(String id) {
	 return map.get(id);
 }
}
  • 修改 TransferServlet
    在这里插入图片描述
  • 修改 TransferServiceImpl
    在这里插入图片描述

(2)针对问题二的改造

  • 增加 ConnectionUtils
package com.lagou.edu.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author yecxf
*/
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 事务管理器类
package com.lagou.edu.utils;
import java.sql.SQLException;
/**
* @author yecxf
*/
public class TransactionManager {
	 private ConnectionUtils connectionUtils;
	 public void setConnectionUtils(ConnectionUtils connectionUtils) {
	 this.connectionUtils = connectionUtils;
 }
 // 开启事务
 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 代理工厂类
package com.lagou.edu.factory;
import com.lagou.edu.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author yecxf
*/
public class ProxyFactory {
 private TransactionManager transactionManager;
 public void setTransactionManager(TransactionManager
	transactionManager) {
	 this.transactionManager = transactionManager;
 }
 public Object getProxy(Object target) {
 	return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {
 	@Override
	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⼦标签,每⼀个bean⼦标签都代表一个类的配置--
><beans>
 <!--id标识对象,class是类的全限定类名-->
 <bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
 <property name="ConnectionUtils" ref="connectionUtils"/>
 </bean>
 <bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
 <!--set+ name 之后锁定到传值的set⽅法了,通过反射技术可以接口该用法传入对应
的值-->
 <property name="AccountDao" ref="accountDao"></property>
 </bean>
 <!--配置新增的三个Bean-->
 <bean id="connectionUtils"
class="com.lagou.edu.utils.ConnectionUtils"></bean>
 <!--事务管理器-->
 <bean id="transactionManager"
class="com.lagou.edu.utils.TransactionManager">
 <property name="ConnectionUtils" ref="connectionUtils"/>
 </bean>
 <!--代理对象接口-->
 <bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
 <property name="TransactionManager" ref="transactionManager"/>
 </bean>
</beans>
  • 修改 JdbcAccountDaoImpl
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.ConnectionUtils;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author yecxf
*/
public class JdbcAccountDaoImpl implements AccountDao {
	 private ConnectionUtils connectionUtils;
	 public void setConnectionUtils(ConnectionUtils connectionUtils) {
		 this.connectionUtils = connectionUtils;
	 }
	 @Override
	 public Account queryAccountByCardNo(String cardNo) throws Exception {
		 //从连接池获取连接
		 // Connection con = DruidUtils.getInstance().getConnection();
		 Connection con = connectionUtils.getCurrentThreadConn();
		 String sql = "select * from account where cardNo=?";
		 PreparedStatement preparedStatement = con.prepareStatement(sql);
		 preparedStatement.setString(1,cardNo);
		 ResultSet resultSet = preparedStatement.executeQuery();
		 Account account = new Account();
		 while(resultSet.next()) {
			 account.setCardNo(resultSet.getString("cardNo"));
			 account.setName(resultSet.getString("name"));
			 account.setMoney(resultSet.getInt("money"));
 		}
		 resultSet.close();
		 preparedStatement.close();
		 //con.close();
		 return account;
	 }
	 @Override
	 public int updateAccountByCardNo(Account account) throws Exception {
		 // 从连接池获取连接
		 // 改造为:从当前线程当中获取绑定的connection连接
		 //Connection con = DruidUtils.getInstance().getConnection();
		 Connection con = connectionUtils.getCurrentThreadConn();
		 String sql = "update account set money=? where cardNo=?";
		 PreparedStatement preparedStatement = con.prepareStatement(sql);
		 preparedStatement.setInt(1,account.getMoney());
		 preparedStatement.setString(2,account.getCardNo());
		 int i = preparedStatement.executeUpdate();
		 preparedStatement.close();
		 //con.close();
		 return i;
	 }
}
  • 修改 TransferServlet
package com.lagou.edu.servlet;
import com.lagou.edu.factory.BeanFactory;
import com.lagou.edu.factory.ProxyFactory;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author yecxf
*/
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
	 // 1. 实例化service层对象
	 //private TransferService transferService = new TransferServiceImpl();
	 //private TransferService transferService = (TransferService)
	//BeanFactory.getBean("transferService");
	 // 从⼯⼚获取委托对象(委托对象是增强了事务控制的功能)
	 // ⾸先从BeanFactory获取到proxyFactory代理⼯⼚的实例化对象
	 private ProxyFactory proxyFactory = (ProxyFactory)
	BeanFactory.getBean("proxyFactory");
	
	 private TransferService transferService = (TransferService)
	proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
	
	 @Override
	 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException {
		 doPost(req,resp);
	 }
	 
	 @Override
	 protected void doPost(HttpServletRequest req, HttpServletResponse
	resp) throws ServletException, IOException {
		 // 设置请求体的字符编码
		 req.setCharacterEncoding("UTF-8");
		 String fromCardNo = req.getParameter("fromCardNo");
		 String toCardNo = req.getParameter("toCardNo");
		 String moneyStr = req.getParameter("money");
		 int money = Integer.parseInt(moneyStr);
		 Result result = new Result();
		 try {
			 // 2. 调⽤service层⽅法
			 transferService.transfer(fromCardNo,toCardNo,money);
			 result.setStatus("200");
		 } catch (Exception e) {
			 e.printStackTrace();
			 result.setStatus("201");
			 result.setMessage(e.toString());
	 }
		 // 响应
		 resp.setContentType("application/json;charset=utf-8");
		 resp.getWriter().print(JsonUtils.object2Json(result));
	 }
}

第四部分 Spring IOC 应用

第1节 Spring IoC基础

在这里插入图片描述

1.1 BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是用来定义一些基础功能,定义些基础规范,
ApplicationContext是它的一个子接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能
的。

通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的一级接口,比
BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等

在这里插入图片描述

  • 使用Spring IOC功能,引入jar包
<!--引入Spring IOC功能-->
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.16.RELEASE</version>
</dependency>

导入context后,自动引入的jar包

启动 IoC 容器的方式

  • Java环境下启动IoC容器
  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
  • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
// 从类路径下加载配置文件
    @Test
    public void testClasspathSpringIOC(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        TransferMoney transferMoney = (TransferMoney) applicationContext.getBean("transferMoney");
        transferMoney.transferMoney();
    }

    // 从文件路径下加载配置文件
    @Test
    public void testFilePathSpringIOC(){
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\Users\\Administrator\\IdeaProjects\\SpringDemo\\SpringIOC\\src\\main\\resources\\applicationContext.xml");
        TransferMoney transferMoney = (TransferMoney) applicationContext.getBean("transferMoney");
        transferMoney.transferMoney();
    }
  • Web环境下启动IoC容器
  • 从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 <display-name>Archetype Created Web Application</display-name>
 <!--配置Spring ioc容器的配置件-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <!--使用监听器启动Spring的IOC容器-->
 <listener>
 <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
 </listener>
</web-app>
  • 从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 	<display-name>Archetype Created Web Application</display-name>
 	<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
 <context-param>
	 <param-name>contextClass</param-name>
	 <paramvalue>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>
 	 <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
 </listener>
</web-app>

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">

实例化Bean的三种方式

  • 方式一:使用无参构造函数
    在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
  • 方式二:使用静态方法创建
    在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创 建的过程 中会做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法是static修饰的方法,即是此种情况。
<!--使用静态方法创建对象的配置方式-->
<bean id="userService" class="com.lagou.factory.BeanFactory"
 factory-method="getTransferService"></bean>
  • 方式三:使用实例化方法创建
    此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是类中的一个普通方法。此种方式比静态方法创建的使用几率要高一些。
    在早期开发的项目中,工厂类中的方法有可能是静态的,也有可能是非静方法,当是非静态方法时,即可采用以下配置方式:
<!--使用实例化方法创建对象的配置方式-->
<bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean> 
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>

Bean的作用范围及生命周期

  • 作用范围的改变
    在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。作用范围官方提供的说明如下图:
    在这里插入图片描述

在上图中提供的这些选项中,我们实际开发中用到最多的作用范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置方式参考下面的代码:

<!--配置service对象-->
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
  • 不同作用范围的生命周期
    单例模式:singleton
    对象出生:当创建容器时,对象就被创建了。
    对象活着:只要容器在,对象一直活着。
    对象死亡:当销毁容器时,对象就被销毁了。
    总结:单例模式的bean对象生命周期与容器相同。

    多例模式:prototype
    对象出生:当使⽤对象时,创建新的对象实例。
    对象活着:只要对象在使用中,就一直活着。
    对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
    总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

  • Bean标签属性
    在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的一个对象。换句话说,如果一个对象想让spring管理,在XML的配置中都需要使用此标签配置,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时起作用。

注意:
1)实际企业开发中,纯xml模式使用很少
2)引入注解功能,不需要引入额外的jar
3)xml+注解结合模式,xml文件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)哪些bean的定义写在xml中,哪些bean的定义使用注解?
第三方jar中的bean定义在xml,比如德鲁伊数据库连接池,自己开发的bean定义使用注解

  • xml中标签与注解的对应(IOC)
xml形式对应的注解形式
标签@Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名首字母小写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的方法完全一样,只是为了更清晰的区分而已
标签的scope属性@Scope(“prototype”),默认单例,注解加在类上
标签的init method属性@PostConstruct,注解加在方法上,该方法就是初始化后调用的方法
标签的destory method属性@PreDestory,注解加在方法上,该方法就是销毁前调用的方法
  • DI 依赖注入的注解实现方式
    • 依赖注入分类
      • 按照注入的方式分类
        构造函数注入:顾名思义,就是利用带参构造函数实现对类成员的数据赋值。
        set方法注入:它是通过类成员的set⽅法实现数据的注入。(使用最多的)
    • 按照注入的数据类型分类
      基本类型和String
      注入的数据类型是基本类型或者是字符串类型的数据。
      其他Bean类型
      注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器
      中的。那么针对当前Bean来说,就是其他Bean了。
      复杂类型(集合类型)
      注入的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
  • 依赖注入的配置实现之构造函数注入 顾名思义,就是利用构造函数实现对类成员的赋值。它
    的使用要求是,类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹
    配。同时需要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring
    框架会报错。

在这里插入图片描述
在使用构造函数注入时,涉及的标签是 constructor-arg ,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。
依赖注入的配置实现之set方法注入
顾名思义,就是利用字段的se方法实现赋值的注入的放式。此方式在实际开发中是使用最多的注入方式。

1.3 xml与注解相结合模式

注意:
1)实际企业开发中,纯xml模式使用已经很少了
2)引入注解功能,不需要引用额外的jar
3)xml+注解结合模式,xml文件依然存在,所以,spring IOC容器的启动仍然从加载xml开始

xml形式对应的注解形式
标签@Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后,如果不配置,默认定义这个bean的id为类的类名字首字母小写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的方法完全一样,只是为了更清晰的区分而已
标签的scope属 性@Scope(“prototype”),默认单例,注解加在类上标签的
init-method属性@PostConstruct,注解加在方法上,该方法就是初始化后调用的方法
destory-method属性@PreDestory,注解加在方法上,该方法就是销毁前调用的方法
  • DI 依赖注入的注解实现方式
    @Autowired(推荐使用)

    @Autowired为Spring提供的注解,需要导入包
    org.springframework.beans.factory.annotation.Autowired

    @Autowired采取的策略为按照类型注入。

public class TransferServiceImpl {
 @Autowired
 private AccountDao accountDao; }

如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注入进来。这样会产一个问题,当一个类有多个子类实现的时候,会造成无法选择具体注入哪一个的情况,这个时候我们需要配合着@Qualifier使用。

@Qualifier告诉Spring具体去装配哪个对象。

public class TransferServiceImpl {
 @Autowired
 @Qualifier(name="jdbcAccountDaoImpl") 
 private AccountDao accountDao; }

这个时候我们就可以通过类型和名称定位到我们想注入的对象。

@Resource
@Resource 注解由 J2EE 提供,需要导入包 javax.annotation.Resource。
@Resource 默认按照 ByName 自动注入。

public class TransferService {
 @Resource 
 private AccountDao accountDao;
 @Resource(name="studentDao") 
 private StudentDao studentDao;
 @Resource(type="TeacherDao") 
 private TeacherDao teacherDao;
 @Resource(name="manDao",type="ManDao") 
 private ManDao manDao;
}
  • 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯一匹配的bean进行装配,找不
    到则抛出异常。
  • 如果指定了 name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异
    常。
  • 如果指定了 type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,
    都会抛出异常。
  • 如果既没有指定name,也没有指定type,则自动按照byName方式进行装配;

注意:
@Resource 在 Jdk 11中已经移除,如果要使用,需要单独引用jar包

<dependency>
 <groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
 <version>1.3.2</version>
</dependency>

1.4 纯注解模式

改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动

对应注解
@Configuration 注解,表示当前类是一个配置类

@ComponentScan 注解,替代 context:component-scan

@PropertySource,引入外部属性配置文件

@Import 引入其他配置类

@Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息

@Bean 将方法返回对象加入 SpringIOC 容器

第2节 Spring IOC高级特性

2.1 lazy-Init 延迟加载

Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。
比如:

<bean id="testBean" class="cn.lagou.LazyBean" />

该bean默认的设置为:

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init=“false”,立即加载,表示在spring启动时,即刻进行实例化。
如果不想让某个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第一次向容器通过 getBean 索取 bean 时实例化的。

如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例,而bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第一次调用时才被实例化的规则。

也可以在容器层次中通过在 元素上使用 “default-lazy-init” 属性来控制延时初始化。如下配置:

<beans default-lazy-init="true">
 <!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果一个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化的。

应用场景
(1)开启延迟加载一定程度提升容器启动和运转性能(非主要)
(2)对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该 Bean 就占用资源

2.2 FactoryBean 和 BeanFactory

BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,比如ApplicationContext;
此处我们重点分析FactoryBean

Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以生成某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。

Bean创建的三种方式中的静态方法和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
 @Nullable
 // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
	 T getObject() throws Exception;
 @Nullable
 // 返回FactoryBean创建的Bean类型
 	Class<?> getObjectType();
 // 返回作⽤域是否单例
	 default boolean isSingleton() {
		 return true;
	 }
 }

Company类

package com.lagou.edu.factorybean;

/**
 * author yecxf
 */
public class Company {

    private String name;    // 名字

    private Integer age;  // 创建时间

    private String scale;  // 规模

    public Company(){};

    public Company(String name, Integer age, String scale) {
        this.name = name;
        this.age = age;
        this.scale = scale;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getScale() {
        return scale;
    }

    public void setScale(String scale) {
        this.scale = scale;
    }

}

CompanyFactoryBean类

package com.lagou.edu.factorybean;

import org.springframework.beans.factory.FactoryBean;

public class CompanyFactory implements FactoryBean<Company> {

    private String name;

    private int age;

    private String scale;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getScale() {
        return scale;
    }

    public void setScale(String scale) {
        this.scale = scale;
    }

    /**
     * 创建复杂Bean逻辑
     * @return
     * @throws Exception
     */
    @Override
    public Company getObject() throws Exception {
        return new Company(name,age,scale);
    }

    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

xml配置

<!--    配置复杂Bean   -->
    <bean id="companyBean" class="com.lagou.edu.factorybean.CompanyFactory">
        <property name="name" value="叶存星福的公司"></property>
        <property name="age" value="12"></property>
        <property name="scale" value="123124"></property>
    </bean>

测试:

 @Test
    public void testFactoryBean(){
        System.out.println(companyBean.toString());
    }

测试,获取FactoryBean,需要在id之前添加“&"

    @Test
    public void testGetFactory(){
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CompanyFactory bean = (CompanyFactory) classPathXmlApplicationContext.getBean("&companyBean");
        System.out.println(bean);
    }

2.3 后置处理器

Spring提供了两种后处理bean的扩展口,分别为 BeanPostProcessor 和
BeanFactoryPostProcessor,两者在使用上是有所区别的。
工厂初始化(BeanFactory)—> Bean对象

  • 在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一些事情

  • 在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情;

注意:对象不一定是springBean,但springBean一定是个对象

SpringBean的生命周期

2.3.1 BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
在这里插入图片描述

该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似我们在定义bean时,定义了init-method所指定的方法

定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数,来判断我们将要处理的具体的bean。

注意:处理是发生在Spring容器的实例化和依赖注入之后

2.3.2 BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应
用:PropertyPlaceholderConfigurer
在这里插入图片描述
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
在这里插入图片描述
其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean 的
BeanDefinition对象。然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法
在这里插入图片描述
方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿
到BeanDefinition对象时,我们可以手动修改bean标签中所定义的属性值。

BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,
这个JavaBean 就是 BeanDefinition
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成
BeanDefinition对象

第五部分 Spring IOC源码深度剖析

  • 好处:提升培养代码架构思维
  • 原则
    • 定焦原则:抓主线
    • 宏观原则:站在上帝视⻆,关注源码结构和业务流程(淡化具体某行代码的编写细节)
  • 读源码的方法和技巧
    • 断点(观察调⽤栈)
    • 反调(Find Usages)
    • 经验(spring框架中doXXX,做具体处理的地方)
  • Spring源码构建
    • 下载源码(github)
    • 安装gradle 5.6.3(类似于maven) Idea 2019.1 Jdk 11.0.5
    • 导入(耗费⼀定时间)
    • 编译工序(顺序:core-oxm-context-beans-aspects-aop)
      • 工程—>tasks—>compileTestJava

第1节 Spring IoC容器初始化主体流程

1.1 Spring IoC的容器体系

IoC容器是Spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多
的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从
的一套原则,具体的容器实现可以增加额外的功能,比如我们常看到的ApplicationContext,其下更具
体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等一系列的内容,
AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系
非常聪明,需要使用哪个层次调用哪个层次即可,不必使用功能大而全的。

BeanFactory 顶级接口方法栈如下
在这里插入图片描述
BeanFactory 容器继承体系
在这里插入图片描述
通过其接口设计,我们可以看到我们一贯使用的 ApplicationContext 除了继承BeanFactory的⼦接⼝,
还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
下面我们以 ClasspathXmlApplicationContext 为例,深入源码说明 IoC 容器的初始化流程

1.2 Bean生命周期关键时机点

**思路:**创建一个类 LagouBean ,让其实现一个特殊的接⼝,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。

LagouBean类

public class LagouBean implements InitializingBean {
    public LagouBean() {
        System.out.println("LagouBean 构造器...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("LagouBean afterPropertiesSet");
    }

MyBeanFactoryPostProcessor类

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public MyBeanFactoryPostProcessor() {
        System.out.println("MyBeanFactoryPostProcessor的实现类构造函数...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("postProcessBeanFactory的实现方法调用中...");
    }
}

MyBeanPostProcessor类

public class MyBeanPostProcessor implements BeanPostProcessor {
    public MyBeanPostProcessor() {
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("lagouBean".equals(beanName)){
            System.out.println("MyBeanPostProcessor 实现类" +
                    "postProcessBeforeInitialization 接口方法被调用中......");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("lagouBean".equals(beanName)){
            System.out.println("MyBeanPostProcessor 实现类" +
                    "postProcessAfterInitialization 接口方法被调用中......");
        }
        return bean;
    }

applicationContext.xml

<bean id="lagouBean" class="com.lagou.edu.source.LagouBean"/>
<bean id="myBeanPostProcessor" class="com.lagou.edu.source.MyBeanPostProcessor"/>
<bean id="myBeanFactoryPostProcessor" class="com.lagou.edu.source.MyBeanFactoryPostProcessor"/> 

在这里插入图片描述
(1)分析 Bean 的创建是在容器初始化时还是在 getBean 时
在这里插入图片描述

根据断点调试,我们发现,在未设置延迟加载的前提下,Bean 的创建是在容器初始化过程中完成的。

(2)分析构造函数调用情况

在这里插入图片描述
在这里插入图片描述
通过如上观察,我们发现构造函数的调⽤时机在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory)处;

(3)分析 InitializingBean 之 afterPropertiesSet 初始化方法调用情况
在这里插入图片描述

通过如上观察,我们发现 InitializingBean中afterPropertiesSet 方法的调用时机也是在
AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);

(4)分析BeanFactoryPostProcessor 初始化和调用情况
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈,发现
BeanFactoryPostProcessor 初始化在AbstractApplicationContext类refresh方法的
invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory 调用在AbstractApplicationContext类refresh方法的
invokeBeanFactoryPostProcessors(beanFactory);

(5)分析 BeanPostProcessor 初始化和调用情况
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调方栈,发现
BeanPostProcessor 初始化在AbstractApplicationContext类refresh方法的
registerBeanPostProcessors(beanFactory);

postProcessBeforeInitialization 调用在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory);

postProcessAfterInitialization 调用在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory);

(6)总结
根据上面的调试分析,我们发现 Bean对象创建的几个关键时机点代码层级的调用都在
AbstractApplicationContext 类 的 refresh 方法中,可见这个方法对于Spring IoC 容器初始化来说相当
关键,汇总如下:

关键点触发代码
构造器refresh#finishBeanFactoryInitialization(beanFactory)
BeanFactoryPostProcessor 初始化refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanFactoryPostProcessor 方法调用refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanPostProcessor 初始化registerBeanPostProcessors(beanFactory)
BeanPostProcessor 方法调用refresh#finishBeanFactoryInitialization(beanFactory)

1.3 Spring IoC容器初始化主流程
由上分析可知,Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中
,我们查看 refresh 方法来俯瞰容器创建的主体流程,主体流程下的具体子流程我们后面再来讨论。

@Override
public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
	 // 第一步:刷新前的预处理
 prepareRefresh();
 /*
 	第二步:
 	获取BeanFactory;默认实现是DefaultListableBeanFactory
	 加载BeanDefition 并注册到 BeanDefitionRegistry
 */
 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 // 第三步:BeanFactory的预准备操作(BeanFactory进行一些设置,比如context的类加载器等)
 prepareBeanFactory(beanFactory);
 try {
	 // 第四步:BeanFactory准备工作完成后进行的后置处理操作
	 postProcessBeanFactory(beanFactory);
	 // 第五步:实例化并调用实现了BeanFactoryPostProcessor接口的Bean
	 invokeBeanFactoryPostProcessors(beanFactory);
	 // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
	 registerBeanPostProcessors(beanFactory);
	 // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
	 initMessageSource();
	 // 第八步:初始化事件派发器
	 initApplicationEventMulticaster();
	 // 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑
	 onRefresh();
	 // 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
	 registerListeners();
 /*
 第十一步:
 初始化所有剩下的非懒加载的单例bean
 初始化创建非懒加载方式的单例Bean实例(未设置属性)
 填充属性
 初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
 调用BeanPostProcessor(后置处理器)对实例bean进行后置处
 */
 finishBeanFactoryInitialization(beanFactory);
 /*
 第十二步:
 完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事
件 (ContextRefreshedEvent)
 */
 finishRefresh();
 }
......
 }
}

第2节 BeanFactory创建流程**

2.1 获取BeanFactory子流程

时序图如下
在这里插入图片描述

2.2 BeanDefinition加载解析及注册子流程

(1)该子流程涉及到如下几个关键步骤
Resource定位: 指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。

BeanDefinition载入 : 把用户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。

注册BeanDefinition到 IoC 容器

(2)过程分析
Step 1: 子流程入口在 AbstractRefreshableApplicationContext#refreshBeanFactory 方法中

在这里插入图片描述
Step 2: 依次调用多个类的 loadBeanDefinitions 方法 —> AbstractXmlApplicationContext —>
AbstractBeanDefinitionReader —> XmlBeanDefinitionReader 一直执行到
XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法

在这里插入图片描述
Step 3: 我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions 方法,期间产生了多
次重载调用,我们定位到最后一个
在这里插入图片描述
此处我们关注两个地方:一个createRederContext方法,一个是
DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入
createRederContext 方法看看
在这里插入图片描述
我们可以看到,此处 Spring 首先完成了 NamespaceHandlerResolver 的初始化。

我们再进入 registerBeanDefinitions 方法中追踪,调用了
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 方法

在这里插入图片描述
进入 doRegisterBeanDefinitions 方法
在这里插入图片描述

进入 parseBeanDefinitions 方法
在这里插入图片描述
进入 parseDefaultElement 方法
在这里插入图片描述
进入 processBeanDefinition 方法

在这里插入图片描述
至此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为
BeanDefinition 对象之后放入一个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition
的。

在这里插入图片描述

可以在DefaultListableBeanFactory中看到此Map的定义

在这里插入图片描述

(3)时序图
在这里插入图片描述

第3节 Bean创建流程

  • 通过最开始的关键时机点分析,我们知道Bean创建子流程入口在
    AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory) 处
    在这里插入图片描述
  • 进入finishBeanFactoryInitialization

在这里插入图片描述

  • 继续进入DefaultListableBeanFactory类的preInstantiateSingletons方法,我们找到下面部分的
    代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例

在这里插入图片描述

  • 继续跟踪下去,我们进入到了AbstractBeanFactory类的doGetBean方法,这个方法中的代码很
    多,我们直接找到核心部分

在这里插入图片描述

  • 接着进⼊到AbstractAutowireCapableBeanFactory类的方法,找到以下代码部分

在这里插入图片描述

  • 进入doCreateBean方法看看,该⽅法我们关注两块重点区域
    • 创建Bean实例,此时尚未设置属性
      在这里插入图片描述
    • 给Bean填充属性,调⽤初始化⽅法,应⽤BeanPostProcessor后置处理器
      在这里插入图片描述

第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 已经
    初始化完成并缓存了起来

第5节 Spring IoC循环依赖问题

5.1 什么是循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A
依赖于B,B依赖于C,C又依赖于A。

在这里插入图片描述
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结
条件。

Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注入)
- Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决
属性循环依赖时,spring采用的是提前暴露对象的方法。

5.2 循环依赖处理机制
  • 单例 bean 构造器参数循环依赖(方法解决)
  • prototype 原型 bean循环依赖(方法解决)
    对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依
    赖,Spring都 会直接报错处理。
    AbstractBeanFactory.doGetBean()方法:
if (isPrototypeCurrentlyInCreation(beanName)) {
	 throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
	 Object curVal = this.prototypesCurrentlyInCreation.get();
	 return (curVal != null &&
 	(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
	curVal).contains(beanName))));
}

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进行标记
这个beanName正在被创建,等创建结束之后会删除标记

try {
	 //创建原型bean之前添加标记
	 beforePrototypeCreation(beanName);
	 //创建原型bean
	 prototypeInstance = createBean(beanName, mbd, args);
}
finally {
	 //创建原型bean之后删除标记
	 afterPrototypeCreation(beanName);
}
总结:Spring 不支持原型 bean 的循环依赖。
  • 单例bean通过setXxx或者@Autowired进行循环依赖

Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延
后设置的,但是构造器必须是在获取引用之前

Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露一个ObjectFactory对
象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调用ClassA的setClassB方法
之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&	isSingletonCurrentlyInCreation(beanName));
		 if (earlySingletonExposure) {
	 		if (logger.isDebugEnabled()) {
				 logger.debug("Eagerly caching bean '" + beanName +
					 "' to allow for resolving potential circular references");
			}
		   //将初始化后的对象提前已ObjectFactory对象注⼊到容器中
		   addSingletonFactory(beanName, new ObjectFactory<Object>() {
		   @Override
		   public Object getObject() throws BeansException {
		   return getEarlyBeanReference(beanName, mbd, bean);
		  }
 		});
 	}
  • Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
  • ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring
    容器中。
  • Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
  • ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了
    ClassA,因此可以获取到ClassA实例
    • ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
  • 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值