Spring 概述
1.1Spring 简介
Spring 官⽅⽹址:http://spring.io/
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 SpringMVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
1.2Spring 发展历程
-
1997年 IBM 提出了EJB的思想;
1998年,SUN 制定开发标准规范EJB1.0;
1999年,EJB 1.1发布;
2001年,EJB 2.0发布;
2003年,EJB 2.1发布;
2006年,EJB 3.0发布; -
Rod Johnson(spring之⽗)
Expert One-to-One J2EE Design and Development(2002) 阐述了J2EE使⽤EJB开发设计的优点及解决⽅案
Expert One-to-One J2EE Development without EJB(2004) 阐述了J2EE开发不使⽤EJB的解决⽅式(Spring雏形) -
2017 年 9 ⽉份发布了 Spring 的最新版本 Spring 5.0 通⽤版(GA)
1.3Spring 的优势
- ⽅便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。 - AOP编程的⽀持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过AOP轻松应付。 - 声明式事务的⽀持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼开发效率和质量。 - ⽅便程序的测试
可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的事情。 - ⽅便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接⽀持。 - 降低JavaEE API的使⽤难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低。 - 源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。
1.4Spring 的核心结构
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对象实现。
1.5Spring 框架版本
核心思想理解
IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了⾮常好的实现。
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出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业务逻辑中,达到和原来⼀样的效果,这个是⽐较难的
2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
2.3 为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯
⼿写实现 IoC 和 AOP(案例:银⾏转账)
1.转账问题分析
问题⼀:在上述案例实现中,service 层实现类在使⽤ dao 层对象时,直接在TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类JdbcAccountDaoImpl 耦合在了⼀起,如果说技术架构发⽣⼀些变动,dao 层的实现要使⽤其它技术,⽐如 Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发的意义将⼤打折扣?
问题⼆:service 层代码没有竟然还没有进⾏事务控制 ?!如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在⾦融业务。
2.问题⼀解决分析
针对问题⼀思考(New关键字导致的耦合问题):
- 实例化对象的⽅式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml中)
- 考虑使⽤设计模式中的⼯⼚模式解耦合,另外项⽬中往往有很多对象需要实例化,那就在⼯⼚中使⽤反 射技术实例化对象,⼯⼚模式很合适
(1).pom.xml中:
<!--dom4j依赖:解析xml-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--xpath表达式依赖:快速定位xml中的元素-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
(2).beans.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl">
<!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"> </bean>
</beans>
(3).创建一个工厂类:
public class BeanFactory {
/**
* 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
* 任务二:对外提供获取实例对象的接口(根据id获取)
*/
private static Map<String,Object> map = new HashMap<>(); // 存储对象
static {
// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
// 加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 解析xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> beanList = rootElement.selectNodes("//bean"); // 两个//: 从匹配的当前节点选择文档中的节点,不考虑他们的位置
for (int i = 0; i < beanList.size(); i++) {
Element element = beanList.get(i);
// 处理每个bean元素,获取到该元素的id 和 class 属性
String id = element.attributeValue("id"); // accountDao
String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
// 通过反射技术实例化对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance(); // 实例化之后的对象
// 存储到map中待用
map.put(id,o);
}
// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
// 有property子元素的bean就有传值需求
List<Element> propertyList = rootElement.selectNodes("//property");
// 解析property,获取父元素
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
// 找到当前需要被处理依赖关系的bean
Element parent = element.getParent();
// 调用父元素对象的反射功能
String parentId = parent.attributeValue("id");
Object parentObject = map.get(parentId);
// 遍历父对象中的所有方法,找到"set" + name
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + name)) {
// 该方法就是 setAccountDao(AccountDao accountDao)
method.invoke(parentObject,map.get(ref));
}
}
// 把处理之后的parentObject重新放到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();
}
}
// 任务二:对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id) {
return map.get(id);
}
}
(4)TransferServlet.java中:
private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
TransferServiceImpl.java中:
//private AccountDao accountDao = new JdbcAccountDaoImpl();
// private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
// 最佳状态
private AccountDao accountDao;
// 构造函数传值/set方法传值
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
2.问题二解决分析
2.1 代理模式的引入
AOP的底层机制就是动态代理。
- 静态代理
- 可以使得我们的真实角色更加纯粹 .
- 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 .
- 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .
- 缺点 : 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
/**
* 接口:租房
*/
public interface IRentingHouse {
void rentHosue();
}
public class RentingHouseImpl implements IRentingHouse {
@Override
public void rentHosue() {
System.out.println("我要租用一室一厅的房子");
}
}
public class RentingHouseProxy implements IRentingHouse {
private IRentingHouse rentingHouse;
public RentingHouseProxy(IRentingHouse rentingHouse) {
this.rentingHouse = rentingHouse;
}
@Override
public void rentHosue() {
System.out.println("中介(代理)收取服务费3000元");
rentingHouse.rentHosue();
System.out.println("客户信息卖了3毛钱");
}
}
public static void main(String[] args) {
IRentingHouse rentingHouse = new RentingHouseImpl();
// 自己要租用一个一室一厅的房子
// rentingHouse.rentHosue();
RentingHouseProxy rentingHouseProxy = new RentingHouseProxy(rentingHouse);
rentingHouseProxy.rentHosue();
}
-
动态代理
-
动态代理的角色和静态代理的一样 .动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
(1).基于接口的动态代理----JDK动态代理
/**
* 接口:租房
* jdk动态代理/cglib动态代理
*/
public interface IRentingHouse {
void rentHosue();
}
/**
*
* 代理对象工厂:生成代理对象的
*/
public class ProxyFactory {
private ProxyFactory(){
}
private static ProxyFactory proxyFactory = new ProxyFactory();
public static ProxyFactory getInstance() {
return proxyFactory;
}
/**
* Jdk动态代理
* @param obj 委托对象
* @return 代理对象
*/
public Object getJdkProxy(Object obj) {
// 获取代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 写增强逻辑
System.out.println("中介(代理)收取服务费3000元");
// 调用原有业务逻辑
result = method.invoke(obj,args);
System.out.println("客户信息卖了3毛钱");
return result;
}
});
}
/**
* 使用cglib动态代理生成代理对象
* @param obj 委托对象
* @return
*/
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
System.out.println("中介(代理)收取服务费3000元");
result = method.invoke(obj,objects);
System.out.println("客户信息卖了3毛钱");
return result;
}
});
}
}
public class JdkProxy {
public static void main(String[] args) {
IRentingHouse rentingHouse = new RentingHouseImpl(); // 委托对象---委托方
// 从代理对象工厂获取代理对象
IRentingHouse jdkProxy = (IRentingHouse) ProxyFactory.getInstance().getJdkProxy(rentingHouse);
jdkProxy.rentHosue();
}
}
(2).基于类的动态代理–cglib
<!--引入cglib依赖-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
public class CglibProxy {
public static void main(String[] args) {
RentingHouseImpl rentingHouse = new RentingHouseImpl(); // 委托对象
// 获取rentingHouse对象的代理对象,
// Enhancer类似于JDK动态代理中的Proxy
// 通过实现接口MethodInterceptor能够对各个方法进行拦截增强,类似于JDK动态代理中的InvocationHandler
// 使用工厂来获取代理对象
RentingHouseImpl cglibProxy = (RentingHouseImpl) ProxyFactory.getInstance().getCglibProxy(rentingHouse);
cglibProxy.rentHosue();
}
}
2.1 事务控制的处理
service 层没有添加事务控制,怎么办?
- 没有事务就添加上事务控制,⼿动控制 JDBC 的Connection 事务,但要注意将Connection和当前线程绑定(即保证⼀个线程只有⼀个Connection,这样操作才针对的是同⼀个 Connection,进⽽控制的是同⼀个事务)。
- ThreadLocal用于绑定当前线程独自维护的变量。
(1)增加 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;
}
}
(2).增加 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();
}
}
(3).增加 ProxyFactory 代理⼯⼚类
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/*private ProxyFactory(){
}
private static ProxyFactory proxyFactory = new ProxyFactory();
public static ProxyFactory getInstance() {
return proxyFactory;
}*/
/**
* Jdk动态代理
* @param obj 委托对象
* @return 代理对象
*/
public Object getJdkProxy(Object obj) {
// 获取代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.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(obj,args);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
/**
* 使用cglib动态代理生成代理对象
* @param obj 委托对象
* @return
*/
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,objects);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
}
(4).修改 beans.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
<!--id标识对象,class是类的全限定类名-->
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl">
<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>
(5).修改 JdbcAccountDaoImpl
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void init() {
System.out.println("初始化方法.....");
}
public void destory() {
System.out.println("销毁方法......");
}
@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;
}
}
(6).修改TransferServiceImpl:
public class TransferServiceImpl implements TransferService {
//private AccountDao accountDao = new JdbcAccountDaoImpl();
// private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
// 最佳状态
private AccountDao accountDao;
// 构造函数传值/set方法传值
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
/*try{
// 开启事务(关闭事务的自动提交)
TransactionManager.getInstance().beginTransaction();*/
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(to);
int c = 1/0;
accountDao.updateAccountByCardNo(from);
/* // 提交事务
TransactionManager.getInstance().commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
TransactionManager.getInstance().rollback();
// 抛出异常便于上层servlet捕获
throw e;
}*/
}
}
(7).修改 TransferServlet
@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.1BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等。
在pom.xml中引入:
<!--引入Spring IoC容器功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
启动 IoC 容器的⽅式:
- Java环境下启动IoC容器
(1).ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 不推荐使用
//ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");
// 第一次getBean该对象
Object accountPojo = applicationContext.getBean("accountPojo");
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
(2).FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件(3).AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
- Web环境下启动IoC容器
<!--引入spring web功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
(1).从xml启动容器
<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>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
在servlet中获取被spring创建的对象:
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
transferService = (TransferService) webApplicationContext.getBean("transferService");
(2).从配置类启动容器
1.2纯xml模式
- xml ⽂件头
<?xml version="1.0" encoding="UTF-8"?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<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的三种⽅式
⽅式⼀:使⽤⽆参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理:
⽅式⼆:使⽤静态⽅法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情 况。
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
⽅式三:使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
- Bean的作用范围及⽣命周期
作⽤范围的改变:在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围(主要有五种)。
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
application | 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。
<!--scope:定义bean的作用范围
singleton:单例,IOC容器中只有一个该类对象,默认为singleton
prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象,Spring只创建对象,不管理对象
-->
<!--配置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时起作⽤。
- DI 依赖注入的xml配置
按照注⼊的⽅式分类:
(1).构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
(2).set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
按照注⼊的数据类型分类:
(1)基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
(2)其他Bean类型:注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。
(3)复杂类型(集合类型): 注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private int sex;
private float money;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setName(String name) {
this.name = name;
}
public void setSex(int sex) {
this.sex = sex;
}
public void setMoney(float money) {
this.money = money;
}
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
private String[] myArray;
private Map<String,String> myMap;
private Set<String> mySet;
private Properties myProperties;
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性-->
<!--<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>-->
<!--<constructor-arg index="0" ref="connectionUtils"/>
<constructor-arg index="1" value="zhangsan"/>
<constructor-arg index="2" value="1"/>
<constructor-arg index="3" value="100.5"/>-->
<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.6"/>
<!--set注入注入复杂数据类型-->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
</bean>
在使⽤构造函数注⼊时,涉及的标签是constructor-arg,该标签有如下属性:
name:⽤于给构造函数中指定名称的参数赋值。
index:⽤于给构造函数中指定索引位置的参数赋值。
value:⽤于指定基本类型或者String类型的数据。
ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
依赖注⼊的配置实现之set⽅法注⼊:利⽤字段的set⽅法实现赋值的注⼊⽅式
在使⽤set⽅法注⼊时,需要使⽤property标签,该标签属性如下:
name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)value:指定注⼊的数据。它⽀持基本类型和String类型。
ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。
复杂数据类型注入,指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。
1.3 xml与注解相结合模式
注意:
1)实际企业开发中,纯xml模式使⽤已经很少了
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)哪些bean的定义写在xml中,哪些bean的定义使⽤注解:
第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在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定义使⽤注解
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采取的策略为按照类型注⼊。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
@Qualifier告诉Spring具体去装配哪个对象:
public class TransferServiceImpl {
// @Autowired 按照类型注入 ,如果按照类型无法唯一锁定对象,可以结合@Qualifier指定具体的id
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao;
}
- @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>
applicationContext.xml被spring框架加载,启动Spring Ioc容器。注解也得被识别,需要配置一个注解的扫描:
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"/>
1.4 纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动
// @Configuration 注解表明当前类是一个配置类
@Configuration
@ComponentScan({
"com.lagou.edu"})
@PropertySource({
"classpath:jdbc.properties"})
/*@Import()*/
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
Java SE环境下启动IoC容器:
@Test
public void testIoC() throws Exception {
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
}
Web环境下启动IoC容器:
```xml
<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</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>
- @Configuration 注解,表明当前类是⼀个配置类
- @ComponentScan 注解,替代 context:component-scan
- @PropertySource,引⼊外部属性配置⽂件
- @Import 引⼊其他配置类
- @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
- @Bean 将⽅法返回对象加⼊ SpringIOC 容器
2.Spring IOC高级特性
2.1lazy-Init 延迟加载
Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singletonbean。
<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;
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。
FactoryBean 的底层源码:
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
@Nullable
T getObject() throws Exception;
// 返回FactoryBean创建的Bean类型
@Nullable
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
public class Company {
private String name;
private String address;
private int scale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", scale=" + scale +
'}';
}
}
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>
测试,获取FactoryBean产⽣的对象:
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{
name='拉勾', address='中关村', scale=500}
测试,获取FactoryBean,需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09
2.3 后置处理器
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象
Spring IOC源码深度剖析
Spring源码构建:
- github上选择版本是5.1:https://github.com/spring-projects/spring-framework/tree/5.1.x(这是一个gradle工程)
- 安装gradle 5.6.3(类似于maven):https://gradle.org/releases/
- 导⼊(耗费⼀定时间)
- 编译⼯程 [⼯程—>tasks—>compileTestJava](顺序:core-oxm-context-beans-aspects-aop)
1. Spring IoC容器初始化主体流程
1.1 Spring IoC的容器体系
1.2 Bean生命周期关键时机点
1.3 Spring IoC容器初始化主流程
2.BeanFactory创建流程
3.Bean创建流程
4.lazy-init 延迟加载机制原理
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都 会直接报错处理。 -
单例bean通过setXxx或者@Autowired进行循环依赖
Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前。
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
- Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
- ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
- Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
- ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例(ClassA通过spring容器获取到ClassB,完成了对象初始化操作。)
- 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
Spring AOP 应⽤