Spring基础和源码剖析
第一部分 Spring 概述
第1节 Spring 简介
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IOC 和 AOP 为内核,提供了展现层 Spring
MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已
经成为使用最多的 Java EE 企业应用开源框架
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
第2节 Spring 的优势
整个 Spring 优势,传达出一个信号,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 的核心结构
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之前就已经存在,只不过更偏向于理论化,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出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄无声息的把横切逻辑代码应用到原有的业
务逻辑中,达到和原来一样的效果,这个是⽐较难的
2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
2.3 为什么叫做面向切面编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑
「面」:横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个
面的概念在里面
第三部分 手写实现 IoC 和 AOP
上⼀部分我们理解了 IoC 和 AOP 思想,我们先不考虑 Spring 是如何实现这两个思想的,此处准备了一个『银行转账』的案例,请分析该案例在代码层次有什么问题 ?分析之后使⽤我们已有知识解决这些问
题(痛点)。其实这个过程我们就是在一步步分析并⼿写实现 IoC 和 AOP。
第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();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {doPost(req,resp);}@Overrideprotected 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();@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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;}
}
第4节 银行转账案例代码问题分析
(1)问题一:在上述案例实现中,service 层实现类在使用 dao 层对象时,直接在
TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对
象,然而一个 new 关键字却将 TransferServiceImpl 和 dao 层具体的一个实现类
JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao 层的实现要使用其它技术,
比如 Mybatis,思考切换起来的成本?每一个 new 的地方都需要修改源代码,重新编译,面向接口开发
的意义将大打折扣?
(2)问题二:service 层代码没有竟然还没有进行事务控制 ?!如果转账过程中出现异常,将可能导致
数据库数据错乱,后果可能会很严重,尤其在金融业务。
第5节 问题解决思路
-
针对问题一思考:
- 实例化对象的模式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml
中)
- 实例化对象的模式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml
-
考虑使用设计模式中的工厂模式解耦合,另外项目中往往有很多对象需要实例化,那就在工厂中使
用反射技术实例化对象,工厂模式很合适
更进一步,代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现工厂类的字眼,如下
图? 能!声明一个变量并提供 set 方法,在反射的时候将所需要的对象注入进去吧
- 针对问题二思考:
- service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,手动控制 JDBC 的
Connection 事务,但要注意将Connection和当前线程绑定(即保证一个线程只有一个
Connection,这样操作才针对的是同一个 Connection,进而控制的是同一个事务)
- service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,手动控制 JDBC 的
第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(TransactionManagertransactionManager) {this.transactionManager = transactionManager;}public Object getProxy(Object target) {return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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")) ;@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {doPost(req,resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponseresp) 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>
启动 IoC 容器的方式
- Java环境下启动IoC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
// 从类路径下加载配置文件@Testpublic void testClasspathSpringIOC(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");TransferMoney transferMoney = (TransferMoney) applicationContext.getBean("transferMoney");transferMoney.transferMoney();}// 从文件路径下加载配置文件@Testpublic 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.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><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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd">
-
实例化Bean的三种方式
-
方式一:使用无参构造函数
在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有参构造函数,将创建
失败。
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
- 方式二:使用静态方法创建
在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创
建的过程 中会做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法是static修饰的
方法,即是此种情况。
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那
么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new
JDBC4Connection() ,因 为我们要注册驱动,还要提供URL和凭证信息,
用 DriverManager.getConnection 方法来获取连接
那么在实际开发中,尤其早期的项目没有使用Spring框架来管理对象的创建,但是在设计时使用了
工厂模式 解耦,那么当接入spring之后,工厂类创建对象就具有和上述例子相同特征,即可采用
此种方式配置。
<!--使用静态方法创建对象的配置方式-->
<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的X及生命周期
-
作用范围的改变
在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的唯一标识。
依赖注入的配置实现之se方法注入
顾名思义,就是利用字段的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”),默认单例,注解加在类上标签的 |
initmethod属性 | @PostConstruct,注解加在方法上,该方法就是初始化后调用的方法 |
destorymethod属性 | @PreDestory,注解加在方法上,该方法就是销毁前调用的方法 |
-
DI 依赖注入的注解实现方式
@Autowired(推荐使用)@Autowired为Spring提供的注解,需要导入包
org.springframework.beans.factory.annotation.Autowired@Autowired采取的策略为按照类型注入。
public class TransferServiceImpl {@Autowiredprivate AccountDao accountDao; }
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注入进来。这
样会产一个问题,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,
这个时候我们需要配合着@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容器
的单例对象缓存池中MapT 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*/@Overridepublic Company getObject() throws Exception {return new Company(name,age,scale);}@Overridepublic Class<?> getObjectType() {return Company.class;}@Overridepublic 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>
测试:
@Testpublic void testFactoryBean(){System.out.println(companyBean.toString());}
测试,获取FactoryBean,需要在id之前添加“&"
@Testpublic 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 构造器...");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("LagouBean afterPropertiesSet");}
MyBeanFactoryPostProcessor类
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {public MyBeanFactoryPostProcessor() {System.out.println("MyBeanFactoryPostProcessor的实现类构造函数...");}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("postProcessBeanFactory的实现方法调用中...");}
}
MyBeanPostProcessor类
public class MyBeanPostProcessor implements BeanPostProcessor {public MyBeanPostProcessor() {}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if("lagouBean".equals(beanName)){System.out.println("MyBeanPostProcessor 实现类" +"postProcessBeforeInitialization 接口方法被调用中......");}return bean;}@Overridepublic 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接口的BeaninvokeBeanFactoryPostProcessors(beanFactory);// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行registerBeanPostProcessors(beanFactory);// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);initMessageSource();// 第八步:初始化事件派发器initApplicationEventMulticaster();// 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑onRefresh();// 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器beanregisterListeners();/*第十一步:初始化所有剩下的非懒加载的单例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后置处理器
- 创建Bean实例,此时尚未设置属性
第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()) {// 判断是否是 FactoryBeanif (isFactoryBean(beanName)) {final FactoryBean<?> factory = (FactoryBean<?>)getBean(FACTORY_BEAN_PREFIX + beanName);boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceofSmartFactoryBean) {isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { @Overridepublic 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);//创建原型beanprototypeInstance = 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>() {@Overridepublic 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都完成了对象初始化操作,解决了循环依赖问题。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 创建工程
File →new→project→若project SDK爆红,则点击下拉箭头找到Jdk安装地址→next→next→命名 例如:Text →选择安装地址 例如:E:\Text→finish→src→New→Java classpsvm+回车 生成main方法 sout+回车 生成输出语句...
2024/4/25 0:09:02 - C# 枚举 enum(学习心得 17)
枚举是一组命名整型常量。 枚举类型是使用 enum 关键字声明的。 C# 枚举是值类型。 枚举包含自己的值,且不能继承或传递继承。 声明: enum <enum_name> { enumeration list };参数:enum_name 指定枚举的类型名称。 enumeration list 是一个用逗号分隔的标识符列表。枚…...
2024/4/25 0:09:01 - 2020-06-25
Android的架构分析 Android的架构主要有两部分构成,即系统架构与应用架构。Android系统架构,是Android系统的底层框架,其中包含了Android系统的基本运行原理,并且系统主要的开发工作也是在此基础上完成的。Android应用框架,一种是开发默认支持MVC架构,另外一种是目前比较…...
2024/4/26 21:30:32 - 架构师训练营第三周作业
作业1 请在草稿纸上手写一个单例模式的实现代码,拍照提交作业。作业二 请用组合设计模式编写程序,打印输出图 1 的窗口,窗口组件的树结构如图 2 所示,打印输出示例参考图 3。类图如下:...
2024/4/26 16:12:20 - 数据库与Mysql - 10
常见的数据类型 数据分类 数值型 整形 小数:定点数浮点数字符型 较短的文本:CHAR VARCHAR 较长的文本:TEXT BLOB(用来保存较长的二进制数据)日期型 整型#1.如何设置无符号和有符号 默认为有符号类型 若需设为无符号类型 在数据类型后加 UNSIGNED ------------------------- CRE…...
2024/4/14 20:47:04 - Android 彩色上下文菜单 Context
什么是Android进程上下文?它是应用程序环境的全局信息的接口。这是一个抽象类,由Android系统提供。它允许访问特定于应用程序的资源和类,以及调用应用程序级操作,如启动活动,广播和接收意图等。零:需求分析如果有这样一个需求,客户要求将Android中的“进程上下文”字体修…...
2024/4/17 15:52:36 - DirectX11学习笔记(3)法线向量与兰伯特余弦定理
一,学习目标 1.了解如何以数学方式描述平面上的点所“面对”的方向,以使我们确定线与平面之 间的入射角。 2.学习如何正确变换法线向量。 二、法线向量与计算法线向量 平面法线(face normal):是描述多边形所朝方向的单位向量(即,它与多边形上的所有 点相互垂直),如下…...
2024/4/18 9:57:09 - DirectX11学习笔记(4)光照与材质的相互作用
一、学习目标 1.了解光照与材质之间的相互作用。 2.了解局部照明和全局照明之间的区别。 二、光照所产生的效果 下图展示了光照和阴影在表现物体的立体感和体积感时起到的重要作用。左边的球体 没有灯照射,看上去就像是一个扁平的 2D 圆;而右边的球体有灯照射,看上去很立体…...
2024/4/14 20:47:02 - pytorch学习笔记——知识蒸馏(14)
利用大型教师网络辅助小型学生网络进行训练的知识蒸馏方法如今已经被开始用于模型的压缩。鉴于之前的紧凑型神经网络容量的限制,捕获数据集中特征的能力有限;大型教师网络输出的标签富含更多的语义信息,因而我们使用之前的较为复杂的网络作为教师网络,利用之前设计的紧凑型…...
2024/4/14 20:47:00 - DirectX11学习笔记(2)光的种类与颜色的计算方法
h## 一、学习目标 1.了解环境光、漫反射和镜面光之间的区别。 2.了解什么是平行光,点光,聚光灯。 3.了解各种光的光强计算公式。 二、漫反射光 如下图所示的粗糙表面。当光线照射在这样一个表面上时,会在不同的随机方向上散开;我们将这种反射称为漫反射(diffuse reflec…...
2024/4/18 16:04:12 - 用Floyd算法实现图的分簇
问题:用Floyd算法将给定的图(9个节点的连通图)分成三个簇1.什么是分簇? 分簇:将一个图分成很多个子图,但是有要求。 分簇要求:每个子图的含有的节点之间一定是最大密度的。也就是说,每个子图是最小生成树的一部分。 2.实现方法 使用Floyd最小生成树算法来实现: step 1…...
2024/5/2 6:04:19 - LeetCode 53.最大子序和
链接:https://leetcode-cn.com/problems/maximum-subarray Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. Example: Input: [-2,1,-3,4,-1,2,1,-5,4], Output: 6 Explanation:…...
2024/4/25 0:09:02 - DirectX11学习笔记(1)准备工作与预备知识
一、学习目标 1.对 Direct3D 在规划调度 3D 硬件方面所起的作用有一个基本了解。 2.理解 COM 在 Direct3D 运行时起到的作用。 3.学习基本的绘图概念,比如 2D 图像的存储方式、页面翻转、深度缓存和多重采样。 二、Direct3D 概述 Direct3D 是一种底层绘图 API(application…...
2024/4/24 16:33:04 - 【UnityShader】透明效果和渲染顺序
Unity 中实现透明效果的方法:透明度测试; 透明度混合;深度缓冲 深度缓冲,根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的进行比较(如果开启了深度测试),如果它的值距离摄像机更远,说明这个片元不应该被渲…...
2024/4/25 0:08:57 - webrtc 线程模型整理
webrtc 线程模型整理 陈子兴 [WebRTC架构分析]WebRTC 的线程模型 前言 WebRTC 作为一个实时音视频通信系统,包含了信令控制、音视频传输、音视频采集、音视频编码、音视频解码、音视频渲染等所有功能。由于其功能多样性,注定了系统结构的复杂性,再加上音视频需要做很多编码前…...
2024/4/25 0:08:56 - 学习资源分享-一些源码官网、程序官网、博客、教程等网站推荐
网站收录网站名称地址说明修罗BBShttps://bbs.xiuno.com/优秀的轻论坛程序云盘钥匙https://extension.yunpanjingling.com/百度网盘提取码万能钥匙设计导航http://hao.shejidaren.com/精选最好的设计网站大全jQuery插件库http://www.jq22.com/jQuery插件LeetCode(力扣)https:…...
2024/4/25 0:08:56 - Python笔记----使用列表实现二维数组
sum = 0demo_list = []print(请输入一个3*3的矩阵:) for i in range(0, 3):#将列表按range分为N个列表demo_list.append([])for j in range(0, 3):#在N个列表中添加元素n = int(input())demo_list[i].append(n)print(demo_list)for i in range(0, 3):sum += demo_list[i][i]pri…...
2024/4/25 0:08:57 - 【编程思想】01 编写高质量代码、Java 开发中通用的方法和准则
1、【建议】: 不要在常量和变量中出现易混淆的字母基本规范:包名全小写,类名首字母全大写,常量全部大写并用下划线分隔,变量采用驼峰命名法(Camel Case)命名等。为了让程序更容易理解,字母“l”(还包括大写字母“O”)尽量不要和数字混用,以免使阅读者的理解与程序意图…...
2024/4/25 0:08:56 - 可粘贴运行+python多线程测试+opencv采图
3个线程之间的协调同步 这套代码比较简单,复制粘帖时注意改一下代码第33、61、62行的地址,分别改成你的存储文件夹、存储文件夹、转存文件夹 代码功能:共有3 个线程,thread1打开摄像头采图并存储 thread2 打酱油,和另外线程不存在竞争 thread3将thread1存的图转存到另一个…...
2024/4/25 0:08:54 - 第二十章 Caché 算法与数据结构 归并排序
文章目录 第二十章 Cach 算法与数据结构 归并排序思想时间复杂度归并排序的应用算法描述完整示例并归类调用输出第二十章 Cach 算法与数据结构 归并排序 思想 归并排序通过不断的将原数组进行拆分(通常拆分成左右两项),一直到剩下一项,然后分别将拆分的子数组进行合并,此时,…...
2024/4/25 0:08:51
最新文章
- Furion项目的单元测试
.Net项目中如果要对Controller或者服务进行测试一般要用mock创建需要注入的实例, 要配置好这些实例还挺复杂的 在Furion帮我们实现了单元测试的构造函数注入, 让单元测试变得简单, 具体步骤如下 在解决方面里面新增一个xunit的测试项目TestProject1, 测试项目一般单独放在test…...
2024/5/3 23:14:05 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 在 Visual Studio Code (VSCode) 中隐藏以 . 开头的文件
打开VSCode。 按下Ctrl ,快捷键打开设置。您也可以点击屏幕左下角的齿轮图标,然后选择“Settings”。 在设置搜索框中,键入files.exclude。 在找到的Files: Exclude项中,点击Add Pattern按钮来添加一个新的模式,或者直接在搜索…...
2024/5/2 2:37:00 - 17、Lua 文件 I-O
Lua 文件 I/O Lua 文件 I/O简单模式完全模式 Lua 文件 I/O LuaI/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件…...
2024/5/3 5:42:41 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/1 17:30:59 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/2 16:16:39 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/3 23:10:03 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/2 15:04:34 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/30 22:21:04 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/1 4:32:01 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/2 9:07:46 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57