目录

一、spring事务的使用

 1、xml配置使用spring的事务

2、xml配置配合注解使用spring的事务

3、服务实例类

4、结果

二、事务标签解析

1、自定义标签解析

2、纯xml使用spring事务 标签解析

2.0、 TxNamespaceHandler

2.1、parser()方法

2.2、parseInternal()方法

2.3、获取事务BeanDefintion对应的class

2.4、doParse方法     

3、纯xml使用spring事务 标签解析

3.0、AopNamespaceHandler

3.1、parser()方法

4、注解形式使用spring事务

4.1、parse()方法

三、匹配事务代理增强器

    1、事务分析入口

DefaultBeanFactoryPointcutAdvisor实例结构

BeanFactoryTransactionAttributeSourceAdvisor 实例结构

2、查找匹配的代理增强器

2.1、findAdvisorsThatCanApply方法

2.2、canApply方法

2.3、AspectJExpressionPointcut的matches方法

2.4、TransactionAttributeSourcePointcut的matches方法

2.5、computeTransactionAttribute方法

2.6、解析@Transactional注解

四、对象操作数据过程中的事务应用

1、invoke()方法

2、invokeWithinTransaction方法

2.1、进行事务操作的准备阶段

2.2、声明式事务管理

2.3、编程式事务处理

3、开启事务

3.1、具体的开启事务

3.2、doBegin()方法

3.3、内嵌事务处理handleExistingTransaction方法

4、回滚事务

 4.1、回滚操作

5、提交事务

 5.1、processCommit()方法 

 


前言:事务是我们日常开发过程中面对数据库的curd操作常接触的概念,这里不对事务的基础概念做介绍,如果读者想要了解事务相关的基础概念请参考博文的这两篇文章  1、mysql相关事务的介绍以及应用     2、spring事务的应用,spring提供了两种类型的事务使用方式,一种是编程式事务另一种则是声明式事务。在我们日常开发中因为声明式事务的使用简单而被大部分企业开发所采用,spring声明式事务让我们从复杂的事务处理中得以解脱,我们不需要关注诸如回滚,提交等事务细节(由spring帮忙处理),只需要配置相关的事务(xml配置或者注解形式)既可以使用spring的事务,让开发人员有更多的时间去关注业务逻辑。下面我们就来一步步分析下spring事务相关的原理。

  话不多说,还是以一个基础示例开启对spring事务的分析,这里重点分析声明式事务处理。

一、spring事务的使用

 1、xml配置使用spring的事务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 扫描service包下所有使用注解的类型 --><context:component-scan base-package="com.soecode.lyf.service" /><!-- 配置事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 注入数据库连接池 --><property name="dataSource" ref="dataSource" /></bean><!--  配置事务通知  会被spring转换为代理增强实力 --><tx:advice id="TestAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="save*" read-only="false" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="del*" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="update*" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="add*" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="find*" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="get*" propagation="REQUIRED" rollback-for="Throwable"/><tx:method name="apply*" propagation="REQUIRED" rollback-for="Throwable"/></tx:attributes></tx:advice><!--  配置参与事务的类 --><aop:config><aop:pointcut id="allTestServiceMethod" expression="execution(* com.soecode.lyf.service.*.*(..))"/><aop:advisor pointcut-ref="allTestServiceMethod" advice-ref="TestAdvice" /></aop:config>
</beans>

   2、xml配置配合注解使用spring的事务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 扫描service包下所有使用注解的类型 --><context:component-scan base-package="com.soecode.lyf.service" /><!-- 配置事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 注入数据库连接池 --><property name="dataSource" ref="dataSource" /></bean><!-- 配置基于注解的声明式事务 --><tx:annotation-driven transaction-manager="transactionManager" />
</beans>

      3、服务实例类

//服务接口实例
@Service
public class BookServiceImpl implements BookService {@AutowiredBookMapper bookMapper;@Override//对于只使用xml配置的事务 则这里不需要使用@Transactional注解 //只需要在xml配置<tx:advice>中的method 中的name能匹配到该方法即可使用spring的事务//<tx:method name="save*" read-only="false" propagation="REQUIRED" rollback-for="Throwable"/>//对于使用注解的方式 则需要使用@Transactional修饰方法、类或者类的接口//rollbackFor属性显示的指明方法遇到什么异常后进行回滚操作 不写默认//遇到RuntimeException异常后触发回滚//@Transactional(rollbackFor = RuntimeException.class)public void saveBook(Book book)  {book.setAuthor("梁晓声");book.setName("知青");book.setDescption("70年代人上山下乡");book.setEvaluateNumber(9999);book.setPrice("25.86");book.setPublicationDate("1994-10-21");book.setPublisHouse("北京石榴庄出版社");book.setScore(9.5);bookMapper.insertSelective(book);//抛出对应的异常 从而触发spring事务的回滚操作throw new RuntimeException("保存书籍失败");}
}

4、结果

           保存信息到数据库操作出现异常则数据库事务回滚 新的数据没有保存到数据库中这里验证了事务的存在。

二、事务标签解析

1、自定义标签解析

          针对我们前面分析的spring 自定义标签的解析以及针对AOP自定义标签解析(spring自定义标签解析、aop原理分析)我们可以得出经验针对如上的<tx:advice/ > <aop:config/> 有对应的标签处理器 TxNamespaceHandler、AopNamespaceHandler

同时<tx:annotation-driven/> 也对应TxNamespaceHandler类 ,下面来分析两个标签处理器做了哪些操作。

2、纯xml使用spring事务 <tx:advice> 标签解析

  2.0、<tx:advice/ > TxNamespaceHandler

           在TxNamespaceHandler的init()方法中注册了一个标签解析类TxAdviceBeanDefinitionParser,具体的针对<tx:advice/> 标签的解析在该类的parser()方法中。

  2.1、parser()方法

     TxAdviceBeanDefinitionParser类是用来处理<tx:advice> 相关的自定义标签的,其中的parser()方法(该方法在父类中)主要是将自定义标签转换为对应的BeanDefintion实例注册到spring环境中,为了在以后获取bean实例做基础。

@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {//解析advisor标签 并转换为BeanDefintion	 AbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {//获取beanDefintion的唯一标识String id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error();}//获取beanDefintion的别名String[] aliases = null;if (shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}//对BeanDefintion进行一次包装BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);//注册beanDefintionHolderregisterBeanDefinition(holder, parserContext.getRegistry());//发布事件if (shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {parserContext.getReaderContext().error(ex.getMessage(), element);return null;}}return definition;
}

上面的解析操作核心是parseInternal()具体的解析是在该方法中,由不同的子类进行自定义扩展,其他的获取id,获取别名,注册、发布操作都是创建BeanDefintion的通用操作。下面分析parseInternal()方法

  2.2、parseInternal()方法

          在该方法中主要将<tx:advice>整个标签解析成初始的GenericBeanDefinition,涉及到BeanDefintion的 parentName,scope

lazyinit class等属性其中好包括对其子类<tx:attributes> <tx:method/>的解析。其他属性设置简单这里不再叙述,我们主要关注class属性的设置 和doParser()方法,因为class属性是一个BeanDefintion的核心属性,明确了class属性,我们也就知道了spring最终获取到的是具体哪个类的实例或者代理对象,方便我们分析期实现的功能。doParser()方法对应主要对子标签进行解析,我们需要了解一下子标签对应的事务功能。

//具体创建<tx:advice>对应的BeanDefintion的方法
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {//创建一个GenericBeanDefinition实例 包装在BeanDefintionBUilder实例中BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();//空实现 String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}//获取并设置对应的Beandefintion的class属性Class<?> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}//没有class 则尝试获取标签中的className,设置为Beandefintion的className属性else {String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));//设置scopeif (parserContext.isNested()) {// Inner bean definition must receive same scope as containing bean.builder.setScope(parserContext.getContainingBeanDefinition().getScope());}//设置beanDefintion的懒加载if (parserContext.isDefaultLazyInit()) {// Default-lazy-init applies to custom bean definitions as well.builder.setLazyInit(true);}//对于<tx:advice>中的子标签进行解析doParse(element, parserContext, builder);//构建BeanDefintion成功并返回return builder.getBeanDefinition();
}

2.3、获取事务BeanDefintion对应的class

@Override
protected Class<?> getBeanClass(Element element) {return TransactionInterceptor.class;
}

   代码很简单<tx:advice> 最终被转换为class属性为TransactionInterceptor的类,虽然代码简单但是这个类是我们业务使用事务的核心类,下面我们简单分析一下该类他实现了MethodInterceptor的invoke()方法,在这里类发挥作用的时候一定是调用其invoke()方法来实现事务的相关处理的。该类支撑着整个事务功能的架构,逻辑还是相对复杂的。我们先按下不表,在后面应用过程中在详述。

字段

描述

ThreadLocal<TransactionInfo> transactionInfoHolder

ThreadLocal包装的TransactionInfo,该类是包含所有事务相关的全部属性

  • PlatformTransactionManager 事务管理器
  • TransactionAttribute事务的一些基础信息
  • joinpointIdentification 需要进行事务处理的业务方法
  • TransactionStatus 事务的一些状态信息,如是否一个新的事务、是否已被标记为回滚

这些属性构成了TransactionInfo对象

ConcurrentHashMap<Object, PlatformTransactionManager> transactionManagerCache
PlatformTransactionManager 事务管理器缓存列表
TransactionAttributeSource
策略接口 提供了一个获取TransactionAttribute事务对象的
getTransactionAttribute()方法
BeanFactory beanFactory
spring工厂对象

  2.4、doParse方法     

//对<tx:attribute> 子标签进行解析
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {//设置事务transactionManager 到TransactionInterceptor对应的beanDefintion中builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));//获取tx:attribute 子标签List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);//tx:attribute 子标签个数大于1if (txAttributes.size() > 1) {//错误 元素attribute属性标签最多一个}//解析tx:attribute 子标签else if (txAttributes.size() == 1) {// Using attributes source.Element attributeSourceElement = txAttributes.get(0);RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);}//如果没有 tx:attribute 子标签 则设置beanDefintion的transactionAttributeSource默认属性为//org.springframework.transaction.annotation.AnnotationTransactionAttributeSource//class对应的BeanDefintion 该类中包含对应的TransactionAttribute对象事务属性else {builder.addPropertyValue("transactionAttributeSource",new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));}
}//具体的解析<tx:attribute>
private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {获取<tx:attribute>标签其中<tx:method>子标签List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);//创建一个集合用于存放<tx:method> 转换的RuleBasedTransactionAttribute 对象ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());transactionAttributeMap.setSource(parserContext.extractSource(attrEle));//遍历<tx:method>子标签for (Element methodEle : methods) {//解析<tx:method>中的name,传播行为 隔离级别,超时时间,是否只读//异常回滚RollbackFor,异常不会滚noRollbackFor属性//包装成RuleBasedTransactionAttribute类String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);TypedStringValue nameHolder = new TypedStringValue(name);nameHolder.setSource(parserContext.extractSource(methodEle));RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);if (StringUtils.hasText(propagation)) {attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);}if (StringUtils.hasText(isolation)) {attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);}if (StringUtils.hasText(timeout)) {try {attribute.setTimeout(Integer.parseInt(timeout));}catch (NumberFormatException ex) {//错误日志处理	}if (StringUtils.hasText(readOnly)) {attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));}List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);}if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);}attribute.setRollbackRules(rollbackRules);//将解析后转换成RuleBasedTransactionAttribute 放入map集合中transactionAttributeMap.put(nameHolder, attribute);}//构造RootBeanDefinition  将transactionAttributeMap集合作为属性nameMap //填充到attributeSourceDefinition中RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);return attributeSourceDefinition;
}

 上面的方法很多但是逻辑简单主要来说就是解析<tx:method> 标签列表将其转换为RuleBasedTransactionAttribute事务属性对象,并将其作为属性nameMap包装在新创建class属性为NameMatchTransactionAttributeSource

的RootBeanDefinition中,同时这个创建的RootBeanDefinition 继续作为属性transactionAttributeSource存放在TransactionInterceptor对应的BeanDefintion中。总结给TransactionInterceptor对应的BeanDefintion设置RuleBasedTransactionAttribute事务列表,主要作用是判断业务方法是否满足RuleBasedTransactionAttribute事务列表,只有满足该RuleBasedTransactionAttribute事务列表中的其中一个对象则说明业务方法需要使用事务。

就拿我们上面的示例来说 解析<tx:method> 会转换七个事务类,例如save* 对应的事务类 del*对应的事务类,我的业务方法是saveBook() 则匹配save* 对应的事务类,说明该业务方法需要使用事务。

到现在为止<tx:advice> 解析已经完成。

最终生成一个两个主要的BeanDefintion :

    TransactionInterceptor对应的beanDefintion类

    NameMatchTransactionAttributeSource对应的beanDefintion类

3、纯xml使用spring事务 <aop:config> 标签解析

       3.0、AopNamespaceHandler

            与上面<tx:advice> 标签解析类似<aop:config> 标签解析使用的是AopNamespaceHadndler的init()方法注册的ConfigBeanDefinitionParser类来进行<aop:config>标签的解析。下面我们来分析ConfigBeanDefinitionParser的parser()方法的作用。

         3.1、parser()方法


/**
*<aop:config> 标签解析
*   <aop:config>
*		<aop:pointcut/>
*		<aop:advisor /> 一般用于事务处理
*	    <aop:aspect />  用于单纯的aop处理
*   </aop:config>
**/
public BeanDefinition parse(Element element, ParserContext parserContext) {//根据标签名创建CompositeComponentDefinition  目前只分析到其用于//解析标签时候的入栈出栈操作 CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);//该方法和分析springAOP类似这里注册一个class为//AspectJAwareAdvisorAutoProxyCreator的beanDefintion//该类是一个后置处理器类,用于在业务对象创建的时候根据情况进行创建代理操作//创建代理的时候回将事务的功能织入到业务对象的匹配的方法中。这个是核心configureAutoProxyCreator(parserContext, element);//对AOP的子标签进行解析 子标签包括<aop:pointcut>、<aop:advisor> <aop:aspect>List<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);//<aop:pointcut> 解析 //创建class属性为AspectJExpressionPointcut的BeanDefintion并注册if (POINTCUT.equals(localName)) {parsePointcut(elt, parserContext);}//解析<aop:advisor>//创建class属性为DefaultBeanFactoryPointcutAdvisor的BeanDefintion并注册else if (ADVISOR.equals(localName)) {parseAdvisor(elt, parserContext);}//解析<aop:aspect> 原理和上面子标签解析方式一致 但是比较复杂//因为该标签主要用于aop的使用,此处我们主要关注事务处理else if (ASPECT.equals(localName)) {parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;}

    该方法中主要是针对<aop:config> 子标签进行解析

      <tx:ponitcut> 创建并注册class属性为AspectJExpressionPointcut的BeanDefintion

      <tx:advisor> 创建class并注册属性为DefaultBeanFactoryPointcutAdvisor的BeanDefintion

同时还注册了一个AspectJAwareAdvisorAutoProxyCreator bean后置处理器,用于创建代理对象

总结:对于纯xml配置主要注册了四个重要的BeanDefintion对象

1、AspectJAwareAdvisorAutoProxyCreator 后置处理器 
2、AspectJExpressionPointcut 切入点表达式 用于匹配业务方式是否使用事务
3、TransactionInterceptor  事务拦截器
4、DefaultBeanFactoryPointcutAdvisor 代理增强器这些类的主要作用是啥我们先留一个悬念,在事务调用过程中我们一一讲解。

4、注解形式使用spring事务

     4.0、<tx:annotation-driven/ > AopNamespaceHandler

           在AopNamespaceHandler的init()方法中注册了一个AnnotationDrivenBeanDefinitionParser class 用于 解析 <tx:annotation-driven /> 标签,下面我们来分析一下AnnotationDrivenBeanDefinitionParser的parse()方法

<!-- 配置基于注解的声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />

   4.1、parse()方法

    public BeanDefinition parse(Element element, ParserContext parserContext) {//<tx:annotation-driven> 默认的mode属性为proxy String mode = element.getAttribute("mode");if ("aspectj".equals(mode)) {// mode="aspectj"registerTransactionAspect(element, parserContext);}else {//事务相关的操作走该条件分支AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);}return null;}/*** 这里注册三个事务相关的BeanDefintion* AnnotationTransactionAttributeSource对应的BeanDefintion*     获取对应的事务对象TransactionAttribute* TransactionInterceptor对应的BeanDefintion*    该实例对象的propertyValues中包含*      AnnotationTransactionAttributeSource对应的BeanDefintion*      transactionManager对应的事务管理器bean实例* BeanFactoryTransactionAttributeSourceAdvisor对应的beanDefintion*        该实例对象的propertyValues中包含*           AnnotationTransactionAttributeSource对应的BeanDefintion*           TransactionInterceptor对应的BeanDefintion**  同时将如上的三个BeanDefintion 包装到CompositeComponentDefinition的内部ComponentDefinition列表中*///注冊事务处理的相关BeanDefintionpublic static void configureAutoProxyCreator(Element element, ParserContext parserContext) {//注册InfrastructureAdvisorAutoProxyCreator 对应的beanDefinition类 作用不详AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {Object eleSource = parserContext.extractSource(element);//创建并注册AnnotationTransactionAttributeSource对应的BeanDefintion// 创建TransactionAttributeSource对应的BeanDefintionRootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");sourceDef.setSource(eleSource);//设置BeanDefintion的role (完全spring内部使用)sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);//注册创建的BeanDefintionString sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);//创建并注册TransactionInterceptor对应的BeanDefintion// 创建TransactionInterceptor对应的BeanDefintionRootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);interceptorDef.setSource(eleSource);//设置BeanDefintion的role (完全spring内部使用)interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);//添加transactionManager事务管理器到interceptorDef的PropertyValues属性//实际获取<tx:annotation-driven transaction-manager="" >中的transaction-manage属性,默认值:beanName = transactionManagerregisterTransactionManager(element, interceptorDef);//添加AnnotationTransactionAttributeSource对应的BeanDefintion到interceptorDef的PropertyValues属性interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));//注册创建的BeanDefintionString interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);//创建并注册BeanFactoryTransactionAttributeSourceAdvisor对应的BeanDefintion//创建BeanFactoryTransactionAttributeSourceAdvisor对应的BeanDefintionRootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);advisorDef.setSource(eleSource);//设置BeanDefintion的role (完全spring内部使用)advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);//添加 AnnotationTransactionAttributeSource对应的BeanDefintion 到advisorDef的PropertyValues属性advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));//添加 TransactionInterceptor对应的BeanDefintion 到advisorDef的PropertyValues属性advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);//如果有order排序 添加到advisorDef的PropertyValues属性if (element.hasAttribute("order")) {advisorDef.getPropertyValues().add("order", element.getAttribute("order"));}//注册创建的BeanDefintionparserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);//将如上的三个BeanDefintion组件注册包装成CompositeComponentDefinition到DefaultListableBeanFactory中CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));parserContext.registerComponent(compositeDef);}}

总结:对于通过注解形式使用事务主要注册了四个重要的BeanDefintion对象

1、InfrastructureAdvisorAutoProxyCreator 后置处理器
2、AnnotationTransactionAttributeSource  里面包含事务对象(注解形式的事务)
3、TransactionInterceptor 拦截器该实例对象的propertyValues中包含AnnotationTransactionAttributeSource对应的BeanDefintiontransactionManager对应的事务管理器bean实例
4、BeanFactoryTransactionAttributeSourceAdvisor 代理增强类该实例对象的propertyValues中包含AnnotationTransactionAttributeSource对应的BeanDefintionTransactionInterceptor对应的BeanDefintion

这些类的主要作用是啥我们先留一个悬念,在事务调用过程中我们一一讲解。

xml形式使用事务注解的形式使用事务相同点不同点
AspectJAwareAdvisorAutoProxyCreator InfrastructureAdvisorAutoProxyCreator
两者均继承了AbstractAdvisorAutoProxyCreator该类,且均为后置处理器,在其他业务bean创建过程中,根据条件创建代理对象
一个用于处理xml配置的后置处理器,一个是应用于注解的后置处理器,核心方法相似。

NameMatchTransactionAttributeSource

AnnotationTransactionAttributeSource 两者均是用来判断业务方法是否需要添加事务支持的

前置是使用名字通配符匹配的形式,判断业务方法是否匹配表达式 从而判断是否使用事务代理,后置是使用其中的SpringTransactionAnnotationParser事务注解解析业务接口、类、方法上@Transactional注解

根据业务方式是否有该对象来判断书是否使用事务代理

TransactionInterceptor TransactionInterceptor 进行事务开启,回滚,提交操作的核心类 
DefaultBeanFactoryPointcutAdvisor BeanFactoryTransactionAttributeSourceAdvisor 均继承AbstractBeanFactoryPointcutAdvisor
我们在第一个类后置处理器的应用中 查找使用该代理增强类来进行代理增强,这两个类均包含上面TransactionInterceptor实例

前者使用AspectJExpressionPointcut 判断是否使用事务

后者使AnnotationTransactionAttributeSource 来判断是否使用事务

三、匹配事务代理增强器

    1、事务分析入口

            根据我们对springAOP功能的分析,可以知道上面两种使用事务的方式分别注册了两个代理增器类型DefaultBeanFactoryPointcutAdvisor、BeanFactoryTransactionAttributeSourceAdvisor ,这两个类为后置处理器,这两个类都继承自AbstractAutoProxyCreator类,在spring生成业务bean实例的时候会调用其postProcessAfterInitialization()方法,分析到这里和我们相关的AOP的分析一致:

  • 获取所有代理增强器
  • 查找到与业务类实例匹配的代理增强器
  • 根据jdk动态代理或者cglib代理创建代理对象

DefaultBeanFactoryPointcutAdvisor实例结构

BeanFactoryTransactionAttributeSourceAdvisor 实例结构

2、查找匹配的代理增强器

      代理增强类可能有很多,如何判断当前业务bean实例需要使用某个代理增强类做代理增强呢?可能我们需要分析一下AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass)方法来看看其中的缘由。

   2.1、findAdvisorsThatCanApply方法

    

 /*** 查询与对应的业务方法匹配的代理增强* @param candidateAdvisors  所有候选的代理增强类* @param clazz 业务bean class属性* @return  与业务bean匹配的代理增强*/
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {//候选代理增强为空 直接返回if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();//对于IntroductionAdvisor 这个是spring的新概念 叫做引入增强或者说叫做引介增强//引入增强(Introduction Advice)的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,//使其具备A接口的功能。(非常强大有木有,A不需要动代码,就能有别的功能,吊炸天有木有)//大家可以自行百度spring的引入增强//添加引入增强类型的代理增强for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}//判断是否有引入增强boolean hasIntroductions = !eligibleAdvisors.isEmpty();//继续对非引入增强其他类型的代理增强进行处理//比如BeforeAdvide(前置增强)、AfterAdvice(后置增强)、ThrowsAdvice(异常增强)、RoundAdvice(环绕增强)for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {// already processedcontinue;}//普通代理增强的判断是否匹配if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;}

  上面的方式针对所有的代理增强实例和目标class进行匹配,分成两种类型 一种是引入增强处理,一种是普通增强的处理。最终匹配的判断是在canApply()方法,所以话不多说我们来分析canApply()方法

2.2、canApply方法

   

/**
* 具体的匹配逻辑
* @param pc 注解形式的事务处理切入点对象 TransactionAttributeSourcePointcut
*        xml形式的事务处理切入点对象 AspectJExpressionPointcut
* @param targetClass 业务class属性
* @param hasIntroductions 是否有引入增强
* @return
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {Assert.notNull(pc, "Pointcut must not be null");if (!pc.getClassFilter().matches(targetClass)) {return false;}//从切入点获取方法匹配器 去匹配当前类 是否匹配切入点// 普通增强使用该MethodMatcherMethodMatcher methodMatcher = pc.getMethodMatcher();// 引入增强使用该IntroductionAwareMethodMatcher 去进行匹配IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}//获取目标类的所有实现的接口class(java支持多接口 所以此处为列表)Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));classes.add(targetClass);//遍历所有接口类的所有方法for (Class<?> clazz : classes) {Method[] methods = clazz.getMethods();for (Method method : methods) {//使用引入方法匹配器 或者普通方法匹配器去进行匹配操作 有任何一个方法匹配成功 则说明该代理增强适用于targetClassif ((introductionAwareMethodMatcher != null &&introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

    该方法主要是从切入点对象中获取对应的方法匹配器,去对class中的所有方法迭代匹配,只要有一个方法匹配成功则说明该代理增强器是适用于该目标对象的。这里的匹配分成两种类型 一种是方法匹配器是MethodMatcher的接口实现,一种是方法匹配器是IntroductionAwareMethodMatcher的接口实现。这里补充说明一下我们开头的两种适用方式上 一种纯XML使用事务 使用的切入点是AspectJExpressionPointcut实例则调用其中的matches()方法,一种注解使用事务 使用的切入点是TransactionAttributeSourcePointcut实例则调用其中的matches()方法。下面我们对mathches()方法进行分析

2.3、AspectJExpressionPointcut的matches方法

//调用AspectJExpressionPointcut 的matches()方法
//AspectJExpressionPointcut 是IntroductionAwareMethodMatcher实现类
//走introductionAwareMethodMatcher.matches(method, targetClass)方式
public boolean matches(Method method, Class<?> targetClass, boolean beanHasIntroductions) {//切入点校验checkReadyToMatch();//针对method 可能是接口方法 targetClass却是实现类的情况 获取实现类的目标方法Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);//getShadowMatch方法的调用繁杂,有点类似Class的匹配,最终也会调用到Pointcut的match(Shadow shadow)方法//匹配方法是否符合切入点表达式ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);// 如果始终匹配则返回trueif (shadowMatch.alwaysMatches()) {return true;}//不匹配返回falseelse if (shadowMatch.neverMatches()) {return false;}else {// the maybe caseif (beanHasIntroductions) {return true;}//如果上面的匹配结果不确定 则需要进行实际的验证//比如args(...)则验证方法的参数是否有指定的类型,@args则验证方法的参数是否标识了指定的注解RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass));}
}

  该方法只要使用我们在xml中声明的aop:pointcut  转换的切入点对象中的切入点表达式对目标方法进行匹配查找,如果匹配成功则返回true,到此为止有关xml的所匹配的代理增强的获取完成。

 

2.4、TransactionAttributeSourcePointcut的matches方法

//调用TransactionAttributeSourcePointcut 的matches()方法
//TransactionAttributeSourcePointcut 是MethodMatcher实现类
//走methodMatcher.matches(method, targetClass)方式
public boolean matches(Method method, Class<?> targetClass) {if (TransactionalProxy.class.isAssignableFrom(targetClass)) {return false;}//TransactionAttributeSource 获取TransactionAttribute事务属性对象源//其getTransactionAttribute 获取TransactionAttribute事务属性对象TransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

这里TransactionAttributeSourcePointcut 将匹配操作委托给其属性TransactionAttribute实现类 这里具体调用AbstractFallbackTransactionAttributeSourcegetTransactionAttribute方法来判断如果可以获取到事务对象则说明代理增强匹配目标对象。下面我们来看看AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法。

/**
* 该方法主要是用来获取我们修饰在类或者方法上的@Transactional注解将其包装成TransactionAttribute
* 比如 @Transactional中的传播行为,隔离级别,是否只读,失败回滚等
*/
//TransactionAttributeSource 类的TransactionAttribute事务属性public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {// 先从缓存中获取 有直接返回TransactionAttribute对象Object cacheKey = getCacheKey(method, targetClass);Object cached = this.attributeCache.get(cacheKey);if (cached != null) {if (cached == NULL_TRANSACTION_ATTRIBUTE) {return null;}else {return (TransactionAttribute) cached;}}else {//没有缓存则需要去手动解析方法或者目标类上的@Transaction注解TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass);// 如果没有放入缓存中为默认实现 默认实现是null对象if (txAtt == null) {this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);}else {if (logger.isDebugEnabled()) {//打印日志}//否则放入解析出来的TransactionAttribute到缓存中this.attributeCache.put(cacheKey, txAtt);}return txAtt;}
}

该方法只是对事务的获取进行了缓存的处理,没有实质的内容可言,主要的获取事务对象逻辑则由computeTransactionAttribute方法进行处理。

2.5、computeTransactionAttribute方法

/*** 解析目标方法或者类上修饰的@Transactional属性*/
private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {// 方法如果不允许访问 返回nullif (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}// 获取targetClass目标类的真实class因为targetClass可能是CGLIB代理类(需要获取其中的父class)Class<?> userClass = ClassUtils.getUserClass(targetClass);// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.//如果目标方法是接口上的,这里会获取目标对象上的目标方法而非原始接口上的目标方法Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);// If we are dealing with method with generic parameters, find the original method.//对于泛型方法 需要查找其原始的方法specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);//从上面获取的方法中获取@Transactional修饰的属性信息TransactionAttribute txAtt = findTransactionAttribute(specificMethod);if (txAtt != null) {return txAtt;}//方法获取不到注解信息则从方法对应的类上获取@Transactional修饰的属性信息txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());if (txAtt != null) {return txAtt;}//类上也获取不到注解信息 则看看其中的接口方法 或者接口上是否有@Transactional修饰的属性信息if (specificMethod != method) {// 从接口方法对应的类上获取@Transactional修饰的属性信息txAtt = findTransactionAttribute(method);if (txAtt != null) {return txAtt;}//从接口上获取@Transactional修饰的属性信息return findTransactionAttribute(method.getDeclaringClass());}return null;}

 该方法中主要针对目标对象的方法获取期中的@Transactional注解转换为事务对象,因为我们有的时候注解修饰在类上所有的方法自动应用事务,也可以修饰在方法上,更有可能是在其接口或者接口方法上,为了实现这种需求,上述代码主要先从目标方法上尝试获取事务,否则从该方法对应的类上,还没有从接口方法上获取,还是没有从接口上获取注解,最终都没有才任务该方法不需要使用事务功能,否则中间任何一个环境有事务则就返回事务,并认为该目标对象需要使用事务,上述具体的解析注解操作是在findTransactionAttribute方法上的我们来分析一下该方法。

2.6、解析@Transactional注解

//最终是由AnnotationTransactionAttributeSource类的determineTransactionAttribute()方法
//来具体解析@Transactional注解
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {return determineTransactionAttribute(clazz);
}//AnnotationTransactionAttributeSource解析@Transactional注解委托给了TransactionAnnotationParser
//AnnotationTransactionAttributeSource解析构造的时候默认使用了SpringTransactionAnnotationParser来进行处理
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {for (TransactionAnnotationParser annotationParser : this.annotationParsers) {TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);if (attr != null) {return attr;}}return null;
}

上面的代码最终表名AnnotationTransactionAttributeSource解析@Transactional注解委托给了TransactionAnnotationParser
默认使用了SpringTransactionAnnotationParser来进行处理。解析@@Transactional注解操作比较简单,这里不在详述

下面附上具体解析@Transactional注解的方法。至此注解形式使用事务中有关获取事务的代理增强到这里就完成了。

//调用SpringTransactionAnnotationParser进行解析@Transactional注解
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {//Transactional注解包装成AnnotationAttributesAnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName());if (ann != null) {//具体解析@Transactional注解return parseTransactionAnnotation(ann);}else {return null;}
}protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();//事务传播行为Propagation propagation = attributes.getEnum("propagation");rbta.setPropagationBehavior(propagation.value());//事务隔离级别Isolation isolation = attributes.getEnum("isolation");rbta.setIsolationLevel(isolation.value());//事务超时时间rbta.setTimeout(attributes.getNumber("timeout").intValue());//是否只读rbta.setReadOnly(attributes.getBoolean("readOnly"));rbta.setQualifier(attributes.getString("value"));//出现异常进行异常处理并进行回滚操作ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();Class<?>[] rbf = attributes.getClassArray("rollbackFor");for (Class<?> rbRule : rbf) {RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);rollBackRules.add(rule);}String[] rbfc = attributes.getStringArray("rollbackForClassName");for (String rbRule : rbfc) {RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);rollBackRules.add(rule);}//出现异常只进行异常处理不进行回滚操作Class<?>[] nrbf = attributes.getClassArray("noRollbackFor");for (Class<?> rbRule : nrbf) {NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);rollBackRules.add(rule);}String[] nrbfc = attributes.getStringArray("noRollbackForClassName");for (String rbRule : nrbfc) {NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);rollBackRules.add(rule);}rbta.getRollbackRules().addAll(rollBackRules);return rbta;}

 

上面讲述了很多,好像我们已经忘记最初的目标,这里总结一下:该小节主要是讲述如何xml和注解形式使用事务的过程中如何确定木目标对象需要使用事务的代理增强进行后续的创建代理对象的操作。

 

四、对象操作数据过程中的事务应用

   通过上面的大量的分析,我们已经确定了 目标对象获取到事务的代理增强后会创建对应的代理对象,将事务的开启,回滚、提交,退回保存点灯事务相关操作和我们业务代码以AOP的形式结合起来,既然说都了AOP模式说明业务功能和事务功能是分开的,这里有关我们事务的功能是由哪一个类来提供呢?在二、中我们已经分析了spring应用事务的过程中创建了若干个BeanDefintion。上面的分析中大多都提到了。但是我们还想忘记了一个类TransactionInterceptor 不错该类就是我们可以使用事务的核心类,里面包含事务的相关功能,该类实现了MethodInterceptor 说明该类调用的过程中一定会调用其invoke(),我们就从该类的invoke()方法出来来揭开spring事务的神秘面纱。

1、invoke()方法

//TransactionInterceptor  事务的处理核心
//主要的事务处理是调用invoke()方法
public Object invoke(final MethodInvocation invocation) throws Throwable {//获取MethodInvocation的目标类 即我们业务需要进行事务处理的方法Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);//所有的事务处理都在该方法中处理return invokeWithinTransaction(invocation.getMethod(), targetClass, new TransactionAspectSupport.InvocationCallback() {@Overridepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}});
}

       该方法主要获取了业务方法对应的拦截类MethodInvocation 获取其方法对应的目标类,具体的事务操作交由其父类TransactionAspectSupport的invokeWithinTransaction()下面我们来关注一下该方法。

2、invokeWithinTransaction方法

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation)throws Throwable {// 获取事务属性信息final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);// 获取事务管理器 这里是我们配置的DataSourceTransactionManagerfinal PlatformTransactionManager tm = determineTransactionManager(txAttr);//拼接事务的切入点比如 com.soecode.lyf.service.impl.saveBook()final String joinpointIdentification = methodIdentification(method, targetClass);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {//开启事务操作TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary
(tm, txAttr, joinpointIdentification);Object retVal = null;try {//执行切入点对应的方法 即执行业务方法 saveBook()业务方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {//出现异常事务回滚操作completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}//程序正常执行的提交操作commitTransactionAfterReturning(txInfo);return retVal;}else {//编程式事务处理方式//该方式下的事务处理只是暴露出一个接口用于执行事物处理过程中有关业务方法的回调//其他有关事物开启,提交回滚操作是编写在业务代码中的try {Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,new TransactionCallback<Object>() {@Overridepublic Object doInTransaction(TransactionStatus status) {TransactionAspectSupport.TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);try {return invocation.proceedWithInvocation();}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new TransactionAspectSupport.ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.return new TransactionAspectSupport.ThrowableHolder(ex);}}finally {cleanupTransactionInfo(txInfo);}}});Check result: It might indicate a Throwable to rethrow.if (result instanceof TransactionAspectSupport.ThrowableHolder) {throw ((TransactionAspectSupport.ThrowableHolder) result).getThrowable();}else {return result;}}catch (TransactionAspectSupport.ThrowableHolderException ex) {throw ex.getCause();}}
}

这个方法包含了我们事务操作的核心方法,主要分成三个大部分(自己理解啊)

  1. 事务操作准备阶段
  2. 声明式事务处理(事务开启提交等操作由spring管理)
  3. 编程式事务管理(事务开启提交等操作需要程序显示编写)

   2.1、进行事务操作的准备阶段

           这里主要构造三个主要对象

  • TransactionAttribute 匹配目标方法的事务属性对象

       事务属性对象是指该对象是对事务隔离级别,传播行为,超时,是否只读,异常回滚,异常不回滚等事务属性进行抽象的事务对象。

所谓的匹配目标:我们需要确定该目标方法上有对应的事务,主要的方式实现是如下代码:

getTransactionAttributeSource().getTransactionAttribute(method, targetClass)
  •         注解形式的获取事务主要是通过AnnotationTransactionAttributeSource类的getTransactionAttribute()方法获取目标对象的@Transactional注解将其转换为对应目标对象的事务对象
  •         xml形式使用事务则是通过NameMatchTransactionAttributeSource类的getTransactionAttribute()方法通过名字的模糊匹配获取对应的我们之前使用<tx:method name="del*" propagation="REQUIRED" rollback-for="Throwable"/> 修饰的事务信息

两者获取到的事务对象均为RuleBasedTransactionAttribute实例对象

  • PlatformTransactionManager 

  spring事务的事务管理器,主要提供事务开启,回滚,提交三个核心功能,这里的事务管理器实例是我们配置的spring默认的DataSourceTransactionManager,所以后面事务操作我们会分析该实例 。

 

  • String joinpointIdentification 

        提供目标方法的全限定方法名,主要用于日志记录等语义化操作。

  • TransactionStatus

    事务的一些状态信息,如是否一个新的事务、是否已被标记为回滚,用于在事务开启,提交回滚时候的状态流转。

  • TransactionInfo

        将如上对象包装到改类中,包含事务的所有相关属性对象

 

  2.2、声明式事务管理

        事务操作核心只有三个,数据库操作开始的时候开启事务,执行业务操作代码,没有异常则正常提交事务,有异常事务回滚

我们先使用列表的形式将代码片段罗列出来去分别分析。

开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
执行业务代码
retVal = invocation.proceedWithInvocation();
提交事务
commitTransactionAfterReturning(txInfo);
异常会馆
completeTransactionAfterThrowing(txInfo, ex);

 

2.3、编程式事务处理

    因为编程式事务是需要代码显示编码处理所以spring对于编程式事务处理使用CallbackPreferringPlatformTransactionManager

 执行对应的业务方法而已之所以篇幅比较长,主要是在异常处理环节。没啥说的不详述了。

 

3、开启事务

    声明式事务处理中createTransactionIfNecessary()方法中开启事务

/**
* 开启事务 最终包装成TransactionInfo
* @param tm   PlatformTransactionManager 事务管理
* @param txAttr 事务的一些基础信息 超时时间、隔离级别、传播属性
* @param joinpointIdentification 业务方法的切入点 需要添加事务处理的业务方法
* @return
*/
protected TransactionAspectSupport.TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIde
//将TransactionAttribute 包装成 DelegatingTransactionAttribute 里面包换事务相关属性信息
//比如事务的隔离级别,传播行为,是否只读,超时时间,异常回调方法等
if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};
}
//开启事务,返回TransactionStatus事务的一些状态信息,
//如是否一个新的事务、是否已被标记为回滚
TransactionStatus status = null;
if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}
}
//将PlatformTransactionManager 事务管理器
// TransactionAttribute事务的一些基础信息
//joinpointIdentification 需要进行事务处理的业务方法
// TransactionStatus 事务的一些状态信息,如是否一个新的事务、是否已被标记为回滚
//将如上的对象包装成TransactionInfo对象
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
  • 将TransactionAttribute 包装成DelegatingTransactionAttribute对象
  • 开启事务 tm.getTransaction(txAttr)
  • 将将PlatformTransactionManager 事务管理器 TransactionAttribute事务对象joinpointIdentification 业务方 TransactionStatus 事务状态对象包装成TransactionInfo对象 并绑定当前线程。

  3.1、具体的开启事务

//开启事务的主要逻辑
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {//开启事务Object transaction = doGetTransaction();// Cache debug flag to avoid repeated checks.boolean debugEnabled = logger.isDebugEnabled();//如果没有事务属性信息 则创建一个默认TransactionAttribute对象if (definition == null) {definition = new DefaultTransactionDefinition();}//如果当前线程已经存在事务 使用单独的方法处理已经存在的事务//判断依据DataSourceTransactionObject 的链接存在且链接的transactionActive标识为trueif (isExistingTransaction(transaction)) {// Existing transaction found -> check propagation behavior to find out how to behave.return handleExistingTransaction(definition, transaction, debugEnabled);}//对于事务属性的超时检验 超时时间不能为负数if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());}//没有存在的事务情况下,新的事务传播行为是PROPAGATION_MANDATORY 则抛出异常//PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");}//对于事务传播行为PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTEDelse if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {//空挂起一个事务AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);}try {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);//创建DefaultTransactionStatus 事务的一些状态信息对象DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);//构造transaction对象信息 比如数据库连接ConnectionHolder,隔离级别,timeOut等//对于新链接绑定到当前线程doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}catch (RuntimeException ex) {resume(null, suspendedResources);throw ex;}catch (Error err) {resume(null, suspendedResources);throw err;}}else {boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);}
}

    事务的开启涉及两种 :一种是嵌套事务处理,一种是无嵌套事务处理,根据事务不同的传播行为来具体分析

事务传播行为事务传播行为说明
 REQUIRED(默认属性)如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 
被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。 
MANDATORY 支持当前事务,如果当前没有事务,就抛出异常。 
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。 
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。 
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。 
NESTED 支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。 
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。 
PROPAGATION_NESTED 内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。 
PROPAGATION_REQUIRES_NEW外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。 

如上代码主要实现逻辑如下

  1. 开启事务 doGetTransaction主要获取数据库的链接将其包装在DataSourceTransactionManager.DataSourceTransactionObject中,并返回
  2. 没有事务对象 则创建默认事务对象
  3. 对于嵌套事务处理 单独使用handleExistingTransaction方法处理
  4. 非嵌套事务处理针对不同的事务传播行为判断事务挂起、创建DefaultTransactionStatus 控制当前事务的状态(是否开启亦或回滚状态)
  5. doBegin()构造transaction对象信息 比如数据库连接ConnectionHolder,隔离级别,timeOut等、对于新链接绑定到当前线程
  6. prepareSynchronization方法 设置事务的同步属性

上面的相关功能主要关注点就是doBegin();

  3.2、doBegin()方法

/填充transaction事务
protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;Connection con = null;try {//设置事务的链接if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = this.dataSource.getConnection();if (this.logger.isDebugEnabled()) {this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}//将事务与资源设置为同步txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();//设置事务的隔离级别Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);//取消数据库链接的自动提交,事务交由spring管理if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (this.logger.isDebugEnabled()) {this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}//设置链接的事务为激活状态txObject.getConnectionHolder().setTransactionActive(true);//根据条件设置事务的超时时间int timeout = this.determineTimeout(definition);if (timeout != -1) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}if (txObject.isNewConnectionHolder()) {//将对应的事务绑定到当前线程TransactionSynchronizationManager.bindResource(this.getDataSource(), txObject.getConnectionHolder());}} catch (Throwable var7) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, this.dataSource);txObject.setConnectionHolder((ConnectionHolder)null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);}
}

我们知道jdbc中 开启手动事务的关键是con.setAutoCommit(false),我们在doBegin()方法中发现了这里将我们获取到的数据库connection链接的自动提交关闭了,将事务操作交由spring管理 。

// 事务提交操作交由spring管理

 txObject.setMustRestoreAutoCommit(true);

//关闭事务自动提交、开启事务手动提交

 con.setAutoCommit(false);

除此之外doBegin()方法还设置了事务的隔离级别,超时时间,事务同步属性,事务激活等属性用于后续事务处理。

3.3、内嵌事务处理handleExistingTransaction方法

//内嵌事务处理
private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)throws TransactionException {//对于传播行为PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");}//对于传播行PROPAGATION_NOT_SUPPORTED为以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {if (debugEnabled) {logger.debug("Suspending current transaction");}//挂起事务 以非事务方式执行Object suspendedResources = suspend(transaction);boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);}//对于传播行为REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {if (debugEnabled) {logger.debug("Suspending current transaction, creating new transaction with name [" +definition.getName() + "]");}//挂起事务AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = suspend(transaction);try {//创建新的事务去执行 新事务创建和普通事务创建一样boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}catch (RuntimeException beginEx) {resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;}catch (Error beginErr) {resumeAfterBeginException(transaction, suspendedResources, beginErr);throw beginErr;}}//对于PROPAGATION_NESTED 	内层事务与外层事务就像两个独立的事务一样,// 一旦内层事务进行了提交后,外层事务不不能对其进行回滚。两个事务互不影响。// 两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {if (!isNestedTransactionAllowed()) {throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - " +"specify 'nestedTransactionAllowed' property with value 'true'");}if (debugEnabled) {logger.debug("Creating nested transaction with name [" + definition.getName() + "]");}//因为在此传播行为下 内嵌事务和外部事务 不互相影响 这里需要为内部事务设置一个保存点//从而使其内部事务和外部事务隔离if (useSavepointForNestedTransaction()) {DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);status.createAndHoldSavepoint();return status;}else {//有的情况不能使用保存点操作 比如JTA  则这里需要使用新建事务来进行处理boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}}// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.if (debugEnabled) {logger.debug("Participating in existing transaction");}//事务校验 失败抛出异常 省略
}

 针对支持内嵌事务的不同传播行为进行分别处理

  •    对于传播行为PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
  •    对于传播行PROPAGATION_NOT_SUPPORTED为以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  •    对于传播行PROPAGATION_REQUIRES_NEW 表示当前事务需要在一个新的事务中进行处理,会将原来的事务挂起,执行当前新的事务,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务.
  •    对于传播行PROPAGATION_NESTED  内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。 这里spring针对嵌入式事务处理采取两种方式
  1. 一种是使用建立保存点的形式作为异常处理的回滚,内部提交后和外部事务互相独立。
  2.  对于无法使用保存点的比如JTA事务则使用创建新事务的方式进行事务处理,注意这里新事务创建不会挂起原事务 所有内部事务还外部事务互相独立。

4、回滚事务

       声明式事务使用completeTransactionAfterThrowing(txInfo, ex) 做事务的异常回滚处理。

//spring事务异常回滚 异常不回滚
<tx:method name="save*" read-only="false" propagation="REQUIRED" rollback-for="RuntimeException" no-rollback-for="Exception"/>
@Transactional(rollbackFor = RuntimeException.class,noRollbackFor = Exception.class)

事务的处理有两种方式 一种是有异常回滚,一种是有异常但是不回滚,只有满足回滚条件才进行数据回滚


protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.hasTransaction()) {//省略日志打印//满足回滚条件的情况进行异常回滚if (txInfo.transactionAttribute.rollbackOn(ex)) {try {//具体的异常回滚操作txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {//省略异常}}else {try {//不满足回滚条件 有异常 也提交事务 不进行回滚操作txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {//省略异常}}}
}

 4.1、回滚操作

private void processRollback(DefaultTransactionStatus status) {try {try {//事务回滚前的调用triggerBeforeCompletion(status);//如果有保存点 则事务回滚到保存点if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Rolling back transaction to savepoint");}status.rollbackToHeldSavepoint();}//新事务 则直接回滚else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction rollback");}doRollback(status);}//对于分布式事务(非独立事务) 这里不做真正的回滚 只是做回滚标记else if (status.hasTransaction()) {if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {if (status.isDebug()) {logger.debug("Participating transaction failed - marking existing transaction as rollback-only");}doSetRollbackOnly(status);}else {if (status.isDebug()) {logger.debug("Participating transaction failed - letting transaction originator decide on rollback");}}}else {logger.debug("Should roll back transaction but cannot - no transaction available");}}catch (RuntimeException ex) {//异常处理}//事务回滚后的调用triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);}finally {//事务回滚后的信息清除cleanupAfterCompletion(status);}
}

 触发事务回滚前的调用

    事务操作之前需要调用对应的触发器

 有保存点回滚到保存点

    对于有保存点的事务回滚到对应的保存点,常用语嵌入式事务,对于嵌入式的事务处理,内嵌的事务异常不会引起外部事物的回滚。保存点的回滚实现方式是根据数据库的底层链接进行的

 新事务直接回滚

    新事务的操作回滚操作也是使用底层数据库链接的底层API来操作。

回滚后的事务信息清除

    设置事务状态为完成,如果当前事务是新的同步状态,则需要将绑定到当前线程的事务信息清除

    结束之前事务的挂起状态 

5、提交事务

//事务的提交操作 根据事务状态进行判断
public final void commit(TransactionStatus status) throws TransactionException {//事务已经完成提交,不能重复提交if (status.isCompleted()) {throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;//对于异常回滚过程中 没有新事务 也没有保存点的 分布式事务 回滚的时候只是保存回滚标识//当某个嵌入事务发生回滚的时候 只是设置事务标识,而等到外部是事务提交的时候 判断出来该事务设置了//回滚标识,由外部事务统一进行事务的提交if (defStatus.isLocalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Transactional code has requested rollback");}processRollback(defStatus);return;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus);// Throw UnexpectedRollbackException only at outermost transaction boundary// or if explicitly asked to.if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}return;}//没有回滚标识则进行提交操作processCommit(defStatus);
}

  事务提交主要对于非独立事务只是标记了回滚标记,这里在整个外部事务提交的时候,会根据回滚标识由外部事务统一进行事务回滚操作。只有没有回滚标识才执行真正的提交操作 processCommit()方法

 5.1、processCommit()方法 

//执行事务的提交操作者
private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;try {prepareForCommit(status);triggerBeforeCommit(status);triggerBeforeCompletion(status);beforeCompletionInvoked = true;boolean globalRollbackOnly = false;if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {globalRollbackOnly = status.isGlobalRollbackOnly();}//事务提交过程中 如果有保存点 提交操作时候清除保存点心if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}status.releaseHeldSavepoint();}//如果是新事务则调用数据库底层链接进行提交操作else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction commit");}doCommit(status);}//省略异常处理}
}

  

    springAOP 的分析到此为止,感谢观看。

 

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. electron自定义-关闭图标按钮事件

    先创建关闭图标 对应的图标代码如下:<i id="minSize" class="layui-icon layui-icon-subtraction" title="最小化窗口" style="font-size: 15px; color: greenyellow;"></i> <i id="fullSize" class=&quo…...

    2024/4/15 6:20:06
  2. 迁移学习方法学习

    目录迁移学习的基础知识迁移学习的概念迁移学习的分类按目标域标签分按学习方法分按特征分类按离线与在线形式分迁移学习的基本方法基于样本的迁移基于特征的迁移基于模型的迁移基于关系的迁移深度迁移学习深度网络的可迁移性最简单的迁移学习——finetunefinetune的使用技巧参…...

    2024/4/18 14:10:53
  3. VUE 指令 记录

    安装 vue : npm install -g @vue/cli创建 vue 项目 : vue create projectname运行vue 项目 (先 cd projectname) : cnpm run serve安装element-ui : vue add element...

    2024/5/4 16:12:46
  4. Git的理解-分支

    这是我理解Git的第二篇文章,在阅读这篇文章之前请先参考第一篇Git的理解-数据模型。让我们从上次离开的地方开始–git的数据模型。只是这一次我们将简化一下,只显示提交对象,并给它们一些符号化的名字,而不是校验和(只是为了更容易遵循),所以我们得到的图是这样的。熟悉…...

    2024/4/20 16:05:46
  5. 一.Shiro简介【Shiro系列教程】

    应用程序安全是一个永远也扯不开的话题,发展到现在,出现了很多安全框架,比较著名的有Spring Security、Apache Shiro,从今天开始我们就开始学习Shiro,打开软件安全的大门! 那到底什么是shiro呢?什么是Apache Shiro?Shiro是一个强大而灵活的开源安全框架,用以处理身份验…...

    2024/4/16 11:59:40
  6. Navicat 远程连接 MySQL 1045错误

    这几天换新空间配置linux服务器,需要Navicat 可视化工具来操作远程数据库出现了1045错误!查看3306 端口是否对外开放,MySQL 默认情况下是不开放对外访问功能netstat -anp | grep 3306linux进入 MySQL 命令界面:运行下列SQL 语句,查看用户是否具有访问权限:use mysql; sele…...

    2024/4/24 9:15:17
  7. Python语音基础操作--12.3基于支持向量机SVM的语音情感识别

    20 个世纪90 年代Vapnik等人提出了支持向量机(SVM) 算法,它是一种基于统计理论的学习方法,其日的是为了改善神经网络学习方法的不足。目前SVM已经广泛应用于数据挖掘、模式识别等领域。支持向量机在机器学习领域有着重要的地位,其集最大间隔的超平面、凸二次规划问题、核分析…...

    2024/4/24 9:15:22
  8. 深入理解Redis、服务器启动过程

    一台Redis服务器从启动到能够接收客户端的命令请求,需要经过一系列的初始化和设置过程,大致需要经过以下几步。 一、服务器状态结构的初始化 服务器状态结构的初始化会创建一个struct rdisServer类型的实例变量server作为服务器的状态,同时为结构中的其他属性设置默认值。由…...

    2024/5/4 22:37:57
  9. MySQL的FIND_IN_SET与GROUP_CONCAT的使用

    FIND_IN_SET:类似于like,但是更加的严谨.有一个任务表taskCREATE TABLE `task` (`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 任务id,`name` varchar(30) NOT NULL COMMENT 任务名称,`hot_uid_list` varchar(512) DEFAULT NULL COMMENT 热门账号表,`task_uid` bigint(11)…...

    2024/4/24 9:15:15
  10. RDD 和 spark执行原理

    1. RDD 概念 RDD(Resilient Distributed Dateset),弹性分布式数据集。 RDD的五大特性:RDD是由一系列的partition组成的。 函数是作用在每一个partition(split)上的。 RDD之间有一系列的依赖关系。 分区器是作用在K,V格式的RDD上。RDD提供一系列最佳的计算位置。RDD理解图:注…...

    2024/4/24 9:15:13
  11. 逻辑地址,页表,mmu等

    页表机制分页转换功能由驻留在内存中的表来描述,该表称为页表(page table),存放在物理地址空间中。页表可看做简单的2^20个物理地址数组。逻辑地址到物理地址的映射功能可以简单地看做进行数组查找。线性地址的高20位构成这个数组的索引值,用于选择对应页面的物理(基)地…...

    2024/4/24 9:15:14
  12. 小米java社招面试记录:MySQL+架构设计+GC+二叉树,带备战思路

    小米一面二分查找(递归和非递归)反转链表(递归和非递归)常用Java集合类HashMap为什么长度是2的n次幂,数据结构,扩容(包括元素移动的细节),线程不安全的问题ConcurrentHashMap怎么保证线程安全, 1.7和1.8有什么变化,为什么要要这么优化CopyOnWriteList怎么保证线程安全, 为什么这…...

    2024/4/24 9:15:11
  13. github项目代码管理Branch(一)

    关于commit https://www.cnblogs.com/twoheads/p/9510843.html 关于branch合并 Github上分支合并 GitBash操作 感觉这个更好理解 GitBash操作 关于branch设置 参见Blog 管理员 master 主 (from master)develop 迭代版本、需求 dev-版本号 (from develop)release 修改deve…...

    2024/4/24 9:15:10
  14. 100个灯泡全按一遍,奇偶数问题【python】【面试】

    100个灯泡全按一遍,奇偶数问题【python】【面试】 100个灯泡按1~100编号,1号同学将编号为1的倍数的灯泡全部摁了一遍(亮),2号同学将编号为2的倍数的灯泡全部摁了一遍(关),3号同学将编号为3的…,100号同学将编号为100的倍数摁了一遍,最后有__10__个灯泡是亮着的. 1)…...

    2024/4/24 9:15:12
  15. ts设置类型为字符串中的其中一种

    type Aie=danger|primary let a:Aie=danger如果a的值不是Aie中的其中一种,则会报错...

    2024/5/1 10:04:18
  16. 刷题2个月,终于进梦寐以求的大厂,数据结构和算法太TM重要了

    最近一个读者和我反馈,他坚持刷题2个月,终于去了他梦寐以求的大厂,薪资涨幅非常可观。并表示目前国内的大厂和一些独角兽,已经越来越效仿硅谷公司的做法,通过面试给定题编程,来考察数据结构和算法的扎实程度。这种方式,即使对于工作多年的开发人员,没有经过训练,很容易…...

    2024/5/1 14:19:33
  17. 素数的判断

    素数的判断 基本代码: 时间复杂度O(n*n1/2) bool isPrime(int n){if(n<=1)return false;if(n%=2)return false;int sqr=(int)sqrt(1.0*n);for(int i=3;i<=sqr;i+=2){if(n%i==0)return false;}/*for(int i=3;i*i<=n;i++){if(n%i==0)return false;}*/return true; }素数…...

    2024/5/1 6:06:25
  18. MVC自动配置原理

    Spring MVC Auto-configuration // Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。 Spring Boot provides auto-configuration for Spring MVC that works well with most applications. // 自动配置在Spring默认设置的基础上添加了以下功能:…...

    2024/5/1 8:21:13
  19. 使用华为云windows服务器搭建一个简单的网站

    1.到华为云官网上购买服务器(如果是学生可以搜索学生套餐进行购买)2.打开控制台3. 配置安全组添加图中的三个规则 Ping 公网IP4.远程登录 【我的资源】->【弹性云服务器】->【远程登陆】 或下载对应RDF文件进行登录。 5.IIS安装接下来就一路【下一步】,然后进行安装 6…...

    2024/5/1 7:23:33
  20. 类加载器-启动类加载器

    启动类加载器用 Bootstrap 类加载器加载类:public class F {static {System.out.println("bootstrap F init");} }执行public class Load5_1 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName(&quo…...

    2024/5/1 14:03:01

最新文章

  1. 主成分分析(PCA)学习

    概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维方法&#xff0c;它通过线性变换将原始数据变换为一组各维度线性无关的表示&#xff0c;通常用于提取数据的主要特征分量。PCA 的目标是从原始数据中提取出最重要的特…...

    2024/5/5 1:45:06
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. Redis分区

    Redis分区是一种数据分片技术&#xff0c;用于将数据分布到多个Redis实例&#xff08;节点&#xff09;上以提高性能和扩展性。分区使得Redis能够处理比单个实例更大的数据集&#xff0c;并允许并行处理客户端请求。 原理&#xff1a; Redis分区通过一致性哈希算法&#xff08;…...

    2024/5/5 1:23:35
  4. Jenkins 使用 Description Setter

    想要的效果如图: 在打包完成之后直接在构件历史的部分展示出来构建的docker镜像名,这样就不需要去找日志了。 首先安装插件 Description Setter, 如何安装就不在此赘述了。 安装完成之后&#xff0c;在构件后操作选项添加一个流程&#xff0c; 有两个字段: regular expressi…...

    2024/5/3 8:55:45
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/4 23:55:17
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/4 18:20:48
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/4 2:59:34
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; 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
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在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