Spring源码-从源码层面讲解声明式事务的运行流程

TxTest开始执行事务方法:

public class TxTest {public static void main(String[] args) throws SQLException {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:\\code");ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");BookService bookService = context.getBean("bookService", BookService.class);bookService.checkout("zhangsan",1);}
}

CglibAopProxy执行
bookService.checkout(“zhangsan”,1);真正执行的时候是调用的代理对象,BookService E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIBdbd50224.class
在这里插入图片描述
执行checkout跳转到CglibAopProxy的intercept

在这里插入图片描述

此时跳转到动态代理执行拦截器逻辑,,有两个拦截器。

ReflectiveMethodInvocation的proceed
ReflectiveMethodInvocation是ProxyMethodInvocation的子类。AOP拦截的执行入口类

	/*** 递归获取通知,然后执行* @return* @throws Throwable*/@Override@Nullablepublic Object proceed() throws Throwable {// We start with an index of -1 and increment early.// 从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target的函数,这个函数是通过反射机制完成的// 具体实现在AopUtils.invokeJoinpointUsingReflection方法中if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}// 获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// Evaluate dynamic method matcher here: static part will already have// been evaluated and found to match.// 这里对拦截器进行动态匹配的判断,这里是对pointcut触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {return dm.interceptor.invoke(this);}else {// Dynamic matching failed.// Skip this interceptor and invoke the next in the chain.// 如果不匹配,那么proceed会被递归调用,知道所有的拦截器都被运行过位置return proceed();}}else {// It's an interceptor, so we just invoke it: The pointcut will have// been evaluated statically before this object was constructed.// 普通拦截器,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}}

ExposeInvocationInterceptor拦截器方法调用
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);执行这个方法到拦截器:
ExposeInvocationInterceptor就是用来传递MethodInvocation的。在后续的任何下调用链环节,只要需要用到当前的MethodInvocation就通过ExposeInvocationInterceptor.currentInvocation()静态方法获得
在这里插入图片描述
执行ExposeInvocationInterceptor类的invoke方法中的return mi.proceed();方法又到ReflectiveMethodInvocation的proceed找第二个拦截器准备开始执行TransactionInterceptor的invoke方法

在这里插入图片描述
TransactionInterceptor开始执行
TransactionInterceptor是事务拦截器,实现了方法拦截器MethodInterceptor
第二个拦截器开始执行TransactionInterceptor的方法:invoke,最终跳转到TransactionAspectSupport的invokeWithinTransaction方法

	public Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.// 获取我们的代理对象的class属性  这里获取到的是bookservice对象Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction.../*** 以事务的方式调用目标方法* 在这埋了一个钩子函数 用来回调目标方法的*/return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);}

方法被注解修饰或者
<tx:attributes> <tx:method name="checkout" propagation="REQUIRED" /> <tx:method name="updateStock" propagation="REQUIRES_NEW" /> </tx:attributes>
被这个标签修饰 就是事务方法了

TransactionAspectSupport的invokeWithinTransaction方法源码:

invokeWithinTransaction方法开始执行

	/**对于基于around-advice的子类的通用委托,委托给其他几个模板*/@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.// 获取我们的事务属性源对象TransactionAttributeSource tas = getTransactionAttributeSource();// 通过事务属性源对象获取到当前方法的事务属性信息final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);// 获取我们配置的事务管理器对象  获取的是这个标签修饰的对象 <beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean>final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {throw new TransactionUsageException("Unsupported annotated transaction on suspending function detected: " + method +". Use TransactionalOperator.transactional extensions instead.");}ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +method.getReturnType());}return new ReactiveTransactionSupport(adapter);});return txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);}PlatformTransactionManager ptm = asPlatformTransactionManager(tm);// 获取连接点的唯一标识  类名+方法名  com.study.spring.tx.xml.service.BookService.checkoutfinal String joinpointIdentification = methodIdentification(method, targetClass, txAttr);// 声明式事务处理if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.// 创建TransactionInfoTransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.// 执行被增强方法,调用具体的处理逻辑retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exception// 异常回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {//清除事务信息,恢复线程私有的老的事务信息cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}//成功后提交,会进行资源储量,连接释放,恢复挂起事务等操作commitTransactionAfterReturning(txInfo);return retVal;}else {// 编程式事务处理Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}}

1.获取我们的事务属性源对象以及通过事务属性源对象获取到当前方法的事务属性信息
invokeWithinTransaction方法内部:getTransactionAttributeSource()方法返回的TransactionAttributeSource
这里用的是接口实现类NameMatchTransactionAttributeSource

/*** 事务属性源,就是事务注解的一些属性,也用来解析事务注解属性*/
public interface TransactionAttributeSource {default boolean isCandidateClass(Class<?> targetClass) {return true;}@NullableTransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);
}

获取我们的事务属性源对象和通过事务属性源对象获取到当前方法的事务属性信息:这里获取到的是在配置文件中配置的方法的隔离级别传播属性等
在这里插入图片描述

2.获取我们配置的事务管理器对象
在这里插入图片描述

determineTransactionManager
该方法返回值是TransactionManager接口实现类DataSourceTransactionManager

{@link org.springframework.transaction。PlatformTransactionManager}实现单个JDBC {@link javax.sql.DataSource}。
这个类能够在任何环境中使用任何JDBC驱动程序工作,只要设置使用{@code javax.sql。DataSource}作为{@code Connection}工厂机制。
将来自指定数据源的JDBC连接绑定到当前线程,可能允许每个数据源有一个线程绑定的连接。
<p><b>注意:此事务管理器操作的数据源需要返回独立的连接。
<b> connection可能来自一个池(典型情况),但是DataSource不能返回线程作用域的请求作用域的Connections或类似的东西。
此事务管理器将根据指定的传播行为将Connections与线程绑定的事务本身关联起来。
它假设即使在正在进行的事务期间也可以获得单独的、独立的Connection<p>应用程序代码需要通过{@link DataSourceUtilsgetConnection(DataSource)}
而不是标准的Java ee风格的{@link DataSourcegetConnection()}调用来检索JDBC连接。
Spring类,如{@link org.springframework.jdbc.core。JdbcTemplate隐式地使用此策略。
如果不与此事务管理器结合使用,{@link DataSourceUtils}查找策略的行为与本机DataSource查找完全相同;因此,它可以以便携式方式使用。
<p>或者,您可以允许应用程序代码使用标准Java ee风格的查找模式{@link DataSourcegetConnection()},
例如,对于根本不知道Spring的遗留代码。
在这种情况下,为你的目标数据源定义一个{@link TransactionAwareDataSourceProxy},
并将该代理数据源传递给你的dao,它将在访问它时自动参与spring管理的事务。
<p>支持自定义隔离级别,以及作为适当的JDBC语句超时应用的超时。
要支持后者,应用程序代码必须使用{@link org.springframework.jdbc.core。JdbcTemplate},
为每个创建的JDBC语句调用{@link DataSourceUtilsapplyTransactionTimeout},
或者通过{@link TransactionAwareDataSourceProxy}来自动创建超时感知的JDBC连接和语句。
<p>考虑为您的目标数据源定义一个{@link LazyConnectionDataSourceProxy},将此事务管理器和dao都指向它。
这将优化“空”事务的处理,即没有执行任何JDBC语句的事务。
在执行语句之前,LazyConnectionDataSourceProxy不会从目标数据源获取实际的JDBC连接,
并将指定的事务设置惰性地应用于目标连接。
<p>这个事务管理器通过JDBC 3.0 {@link java.sql支持嵌套事务。保存点}机制。
{@link setNestedTransactionAllowed "nestedTransactionAllowed"}标志默认为"true"
,因为嵌套事务将在支持保存点的JDBC驱动程序(Oracle JDBC驱动程序)上不受限制地工作。
<p>这个事务管理器可以用来代替{@link org.springframework.transaction.jta。JtaTransactionManager}在单一资源的情况下,
因为它不需要支持JTA的容器,通常与本地定义的JDBC数据源(例如Apache Commons DBCP连接池)结合使用。
在本地策略和JTA环境之间切换只是一个配置问题!
<p>4.3.4开始,这个事务管理器在注册的事务同步(如果同步通常是活动的)上触发flush回调,
假设资源在底层JDBC {@code Connection}上操作。这允许类似于{@code JtaTransactionManager}的设置,
特别是对于惰性注册的ORM资源(例如Hibernate {@code Session})public class DataSourceTransactionManager extends AbstractPlatformTransactionManagerimplements ResourceTransactionManager, InitializingBean {}

3.createTransactionIfNecessary创建TransactionInfo

@SuppressWarnings("serial")protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.// 如果没有名称指定则使用方法唯一标识,并使用DelegatingTransactionAttribute封装txAttrif (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {//委托的事务属性@Overridepublic String getName() {return joinpointIdentification;}};}//boolean hasSavepoint();  嵌套事务 指定回滚的位置 TransactionStatus status = null;if (txAttr != null) {if (tm != null) {// 获取TransactionStatus事务状态信息status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}// 根据指定的属性与status准备一个TransactionInfo,return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);}

事务状态TransactionStatus
设置保存点在嵌套事务中可以指定回滚的位置

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {/*** 是否有保存点*/boolean hasSavepoint();/*** 将会话刷新到数据库中*/@Overridevoid flush();}

通过设置传播特性设置嵌套事务的回滚
TransactionExecution

/*** Common representation of the current state of a transaction.* Serves as base interface for {@link TransactionStatus} as well as* {@link ReactiveTransaction}.** @author Juergen Hoeller* @since 5.2*/
public interface TransactionExecution {/*** 是否为新事务*/boolean isNewTransaction();/*** 设置为只回滚*/void setRollbackOnly();/*** 是否为只回滚*/boolean isRollbackOnly();/*** 当前事务是否已经完成*/boolean isCompleted();}

// 获取事务
在这里插入图片描述

	protected Object doGetTransaction() {DataSourceTransactionObject txObject = new DataSourceTransactionObject();txObject.setSavepointAllowed(isNestedTransactionAllowed());ConnectionHolder conHolder =(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());txObject.setConnectionHolder(conHolder, false);return txObject;}

DataSourceTransactionObject

	private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {private boolean newConnectionHolder;//新链接的包装器private boolean mustRestoreAutoCommit;//重新存储的自动提交 对自动提交的设置 public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) {super.setConnectionHolder(connectionHolder);this.newConnectionHolder = newConnectionHolder;}public boolean isNewConnectionHolder() {return this.newConnectionHolder;}public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) {this.mustRestoreAutoCommit = mustRestoreAutoCommit;}public boolean isMustRestoreAutoCommit() {return this.mustRestoreAutoCommit;}public void setRollbackOnly() {getConnectionHolder().setRollbackOnly();}@Overridepublic boolean isRollbackOnly() {return getConnectionHolder().isRollbackOnly();}@Overridepublic void flush() {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationUtils.triggerFlush();}}}

JdbcTransactionObjectSupport

/*** Convenient base class for JDBC-aware transaction objects. Can contain a* {@link ConnectionHolder} with a JDBC {@code Connection}, and implements the* {@link SavepointManager} interface based on that {@code ConnectionHolder}.** <p>Allows for programmatic management of JDBC {@link java.sql.Savepoint Savepoints}.* Spring's {@link org.springframework.transaction.support.DefaultTransactionStatus}* automatically delegates to this, as it autodetects transaction objects which* implement the {@link SavepointManager} interface.** @author Juergen Hoeller* @since 1.1* @see DataSourceTransactionManager*/
public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject {private static final Log logger = LogFactory.getLog(JdbcTransactionObjectSupport.class);@Nullableprivate ConnectionHolder connectionHolder;//连接包装器@Nullableprivate Integer previousIsolationLevel;//之前设置的隔离级别private boolean readOnly = false;//是否可读private boolean savepointAllowed = false;//是否允许设置保存点/*** Set the ConnectionHolder for this transaction object.*/public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) {this.connectionHolder = connectionHolder;}/*** Return the ConnectionHolder for this transaction object.*/public ConnectionHolder getConnectionHolder() {Assert.state(this.connectionHolder != null, "No ConnectionHolder available");return this.connectionHolder;}/*** Check whether this transaction object has a ConnectionHolder.*/public boolean hasConnectionHolder() {return (this.connectionHolder != null);}/*** Set the previous isolation level to retain, if any.*/public void setPreviousIsolationLevel(@Nullable Integer previousIsolationLevel) {this.previousIsolationLevel = previousIsolationLevel;}/*** Return the retained previous isolation level, if any.*/@Nullablepublic Integer getPreviousIsolationLevel() {return this.previousIsolationLevel;}/*** Set the read-only status of this transaction.* The default is {@code false}.* @since 5.2.1*/public void setReadOnly(boolean readOnly) {this.readOnly = readOnly;}/*** Return the read-only status of this transaction.* @since 5.2.1*/public boolean isReadOnly() {return this.readOnly;}/*** Set whether savepoints are allowed within this transaction.* The default is {@code false}.*/public void setSavepointAllowed(boolean savepointAllowed) {this.savepointAllowed = savepointAllowed;}/*** Return whether savepoints are allowed within this transaction.*/public boolean isSavepointAllowed() {return this.savepointAllowed;}@Overridepublic void flush() {// no-op}//---------------------------------------------------------------------// Implementation of SavepointManager//---------------------------------------------------------------------/*** This implementation creates a JDBC 3.0 Savepoint and returns it.* @see java.sql.Connection#setSavepoint*/@Overridepublic Object createSavepoint() throws TransactionException {ConnectionHolder conHolder = getConnectionHolderForSavepoint();try {if (!conHolder.supportsSavepoints()) {throw new NestedTransactionNotSupportedException("Cannot create a nested transaction because savepoints are not supported by your JDBC driver");}if (conHolder.isRollbackOnly()) {throw new CannotCreateTransactionException("Cannot create savepoint for transaction which is already marked as rollback-only");}return conHolder.createSavepoint();}catch (SQLException ex) {throw new CannotCreateTransactionException("Could not create JDBC savepoint", ex);}}/*** This implementation rolls back to the given JDBC 3.0 Savepoint.* @see java.sql.Connection#rollback(java.sql.Savepoint)*/@Overridepublic void rollbackToSavepoint(Object savepoint) throws TransactionException {ConnectionHolder conHolder = getConnectionHolderForSavepoint();try {conHolder.getConnection().rollback((Savepoint) savepoint);conHolder.resetRollbackOnly();}catch (Throwable ex) {throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);}}/*** This implementation releases the given JDBC 3.0 Savepoint.* @see java.sql.Connection#releaseSavepoint*/@Overridepublic void releaseSavepoint(Object savepoint) throws TransactionException {ConnectionHolder conHolder = getConnectionHolderForSavepoint();try {conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);}catch (Throwable ex) {logger.debug("Could not explicitly release JDBC savepoint", ex);}}protected ConnectionHolder getConnectionHolderForSavepoint() throws TransactionException {if (!isSavepointAllowed()) {throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions");}if (!hasConnectionHolder()) {throw new TransactionUsageException("Cannot create nested transaction when not exposing a JDBC transaction");}return getConnectionHolder();}}

开始获取事务

在这里插入图片描述
只对嵌套事务的保存点生效nested

	/*** Return whether nested transactions are allowed.*/public final boolean isNestedTransactionAllowed() {return this.nestedTransactionAllowed;}

nestedTransactionAllowed哪里赋值的?
DataSourceTransactionManager实例化的时候:

在这里插入图片描述
ConnectionHolder:

包装JDBC连接的资源持有者。DataSourceTransactionManager将该类的实例绑定到线程,用于特定的javax.sql.DataSource。继承基类对嵌套JDBC事务和引用计数功能的仅回滚支持。
public class ConnectionHolder extends ResourceHolderSupport 

TransactionSynchronizationManager:

管理每个线程的资源和事务同步的中心委托。由资源管理代码使用,而不是由典型的应用程序代码使用。支持每个键一个资源,不覆盖,即在为同一键设置新资源之前需要删除一个资源。如果同步是活动的,则支持事务同步列表。资源管理代码应该通过getResource检查线程绑定的资源,例如JDBC连接或Hibernate会话。这样的代码通常不应该将资源绑定到线程,因为这是事务管理器的责任。另一个选项是,如果事务同步是活动的,则在第一次使用时惰性绑定,用于执行跨越任意数量资源的事务。事务同步必须由事务管理器通过initSynchronization()clearSynchronization()激活和取消激活。AbstractPlatformTransactionManager自动支持此功能,因此所有标准的Spring事务管理器都支持此功能,例如org.springframework.transaction.jta. jtattransactionmanager和org.springframework.jdbc.datasource.DataSourceTransactionManager。资源管理代码应该只在这个管理器处于活动状态时注册同步,这可以通过isSynchronizationActive来检查;它应该立即执行资源清理。如果事务同步不是活动的,则要么没有当前事务,要么事务管理器不支持事务同步。例如,同步用于在JTA事务中始终返回相同的资源,例如,对于任何给定的数据源或SessionFactory,分别使用JDBC连接或Hibernate会话。
public abstract class TransactionSynchronizationManager

TransactionSynchronizationManager.getResource(obtainDataSource()):

	/**检索绑定到当前线程的给定键的资源。参数:key -要检查的键(通常是资源工厂)返回:绑定到当前线程的值(通常是活动的资源对象),如果没有则为空参见:ResourceTransactionManager.getResourceFactory()*/@Nullablepublic static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +Thread.currentThread().getName() + "]");}return value;}

doGetResource:
在这里插入图片描述
获取事务从当前线程

private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");

此时还没建立任何的连接:
在这里插入图片描述

在这里插入图片描述
如果外层有事务在这里就会获取到
在这里插入图片描述
开启事务:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
** doBegin(transaction, definition);**

在这里插入图片描述

	@Overrideprotected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = obtainDataSource().getConnection();//先获取数据源 然后去DruidDataSource获取连接if (logger.isDebugEnabled()) {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);txObject.setReadOnly(definition.isReadOnly());// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,// so we don't want to do it unnecessarily (for example if we've explicitly// configured the connection pool to set it already).if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// Bind the connection holder to the thread.if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, obtainDataSource());txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}}

Connection newCon = obtainDataSource().getConnection();//先获取数据源 然后去DruidDataSource获取连接
在这里插入图片描述
prepareTransactionalConnection设置只读事务

	/**在事务开始后立即准备事务连接。如果“enforceReadOnly”标志被设置为true并且事务定义为只读事务,则默认实现执行“SET TRANSACTION READ ONLY”语句。“SET TRANSACTION READ ONLY”可以被Oracle, MySQL和Postgres理解,也可以在其他数据库中使用。如果您想调整此处理,请相应地覆盖此方法。*/protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)throws SQLException {if (isEnforceReadOnly() && definition.isReadOnly()) {try (Statement stmt = con.createStatement()) {stmt.executeUpdate("SET TRANSACTION READ ONLY");}}}
	/*** Start a new transaction.*/private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);doBegin(transaction, definition);//上面已经讲完prepareSynchronization(status, definition);return status;}
	/*** Initialize transaction synchronization as appropriate.根据需要初始化事务同步。*/protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {if (status.isNewSynchronization()) {TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?definition.getIsolationLevel() : null);TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());TransactionSynchronizationManager.initSynchronization();}}
	/**必要时根据给定的TransactionAttribute创建事务。允许调用者通过TransactionAttributeSource执行自定义的TransactionAttribute查找。Params: txAttr—TransactionAttribute(可能为null) joinpointIdentification—完全限定的方法名(用于监视和记录目的)返回:一个TransactionInfo对象,无论是否创建了事务。TransactionInfo上的hastrtransaction()方法可用于判断是否创建了事务。参见:getTransactionAttributeSource()*/@SuppressWarnings("serial")protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}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");}}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);}

prepareTransactionInfo

	/**为给定的属性和状态对象准备一个TransactionInfo。Params: txAttr—TransactionAttribute(可能为null) joinpointIdentification—完全限定的方法名称(用于监视和记录目的)状态—当前事务的    TransactionStatus返回:准备好的TransactionInfo对象*/protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {// We need a transaction for this method...if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}// The transaction manager will flag an error if an incompatible tx already exists.如果不兼容的tx已经存在,事务管理器将标记一个错误。txInfo.newTransactionStatus(status);}else {// The TransactionInfo.hasTransaction() method will return false. We created it only// to preserve the integrity of the ThreadLocal stack maintained in this class.hastrtransaction()方法将返回false。我们创建它只是为了保持在这个类中维护的ThreadLocal堆栈的完整性。if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}//我们总是将TransactionInfo绑定到线程,即使我们没有在这里创建新事务。这保证了TransactionInfo堆栈将被正确管理,即使这个方面没有创建任何事务。// We always bind the TransactionInfo to the thread, even if we didn't create// a new transaction here. This guarantees that the TransactionInfo stack// will be managed correctly even if no transaction was created by this aspect.txInfo.bindToThread();return txInfo;}

bindToThread

	private void bindToThread() {// Expose current TransactionStatus, preserving any existing TransactionStatus// for restoration after this transaction is complete.公开当前的TransactionStatus,保留任何现有的TransactionStatus,以便在此事务完成后恢复。this.oldTransactionInfo = transactionInfoHolder.get();transactionInfoHolder.set(this);}

TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);方法执行结束之后表示跟事务相关的所有信息都已经准备好了,接下来开始执行增强方法:
在这里插入图片描述
执行增强方法路径截图如下:最终指向service的方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
bookDao也是一个代理对象,所以还是会执行到CglibAopProxy的intercept方法,最终还是会TransactionInterceptor的invokeWithinTransaction
在这里插入图片描述
在这里插入图片描述
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);执行到这个方法里的时候不会再创建新的事务因为从上一次创建的对象放到缓存里了直接取,并且propagation=“REQUIRED”
在这里插入图片描述
create方法一直到doGetResource:

private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
开始执行handleExistingTransaction:

为现有事务创建一个TransactionStatusprivate TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)throws TransactionException {if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {//	int PROPAGATION_NEVER = 5;不支持当前事务;如果存在当前事务,则抛出异常。类似于同名的EJB事务属性。注意,事务同步在PROPAGATION_NEVER作用域中不可用。throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");}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);}if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {if (debugEnabled) {logger.debug("Suspending current transaction, creating new transaction with name [" +definition.getName() + "]");}SuspendedResourcesHolder suspendedResources = suspend(transaction);try {return startTransaction(definition, transaction, debugEnabled, suspendedResources);}catch (RuntimeException | Error beginEx) {resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;}}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()) {// Create savepoint within existing Spring-managed transaction,// through the SavepointManager API implemented by TransactionStatus.// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.//通过TransactionStatus实现的SavepointManager API,在现有的spring管理的事务中创建保存点。通常使用JDBC 3.0保存点。从不激活Spring同步。DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);status.createAndHoldSavepoint();return status;}else {// Nested transaction through nested begin and commit/rollback calls.// Usually only for JTA: Spring synchronization might get activated here// in case of a pre-existing JTA transaction.//通过嵌套的begin和commitrollback调用进行嵌套事务。通常只针对JTA:在存在预先存在的JTA事务的情况下,这里可能会激活Spring同步。return startTransaction(definition, transaction, debugEnabled, null);}}// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.if (debugEnabled) {logger.debug("Participating in existing transaction");}if (isValidateExistingTransaction()) {if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {Constants isoConstants = DefaultTransactionDefinition.constants;throw new IllegalTransactionStateException("Participating transaction with definition [" +definition + "] specifies isolation level which is incompatible with existing transaction: " +(currentIsolationLevel != null ?isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :"(unknown)"));}}if (!definition.isReadOnly()) {if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {throw new IllegalTransactionStateException("Participating transaction with definition [" +definition + "] is not marked as read-only but existing transaction is");}}}boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);}

从这里开始执行dao中的sql,代理方法跳转到目标对象的方法:
在这里插入图片描述
在这里插入图片描述
sql执行之后如果有异常执行completeTransactionAfterThrowing,否则直接执行cleanupTransactionInfo
在这里插入图片描述
cleanupTransactionInfo(txInfo);

	/**
重置TransactionInfo ThreadLocal。在所有情况下都调用这个:异常或正常返回!参数:txInfo -关于当前事务的信息(可能为空)*/protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {if (txInfo != null) {txInfo.restoreThreadLocalStatus();}}

restoreThreadLocalStatus

	private void restoreThreadLocalStatus() {// Use stack to restore old transaction TransactionInfo.// Will be null if none was set.使用堆栈恢复旧事务TransactionInfo。如果未设置,则为空。transactionInfoHolder.set(this.oldTransactionInfo);}

commitTransactionAfterReturning(txInfo);

   //在成功完成调用后执行,但不是在处理异常之后执行。如果我们没有创建一个事务,什么也不做。参数:txInfo -关于当前事务的信息protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");}txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}}

txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());

	/**commit的这个实现处理参与现有事务和编程式回滚请求。委托给isRollbackOnly, doCommit和rollback。参见:TransactionStatus.isRollbackOnly(), doCommit, rollback*/@Overridepublic 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, false);return;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus, true);return;}前面回滚的不执行 就到这里 进行提交processCommit(defStatus);}
processCommit(defStatus);
处理实际提交。已经检查并应用了仅回滚标志。参数:状态—表示事务的对象。在提交失败的情况下抛出:TransactionException
private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;try {boolean unexpectedRollback = false;prepareForCommit(status);triggerBeforeCommit(status);//提交之前触发的操作triggerBeforeCompletion(status);//触发beforeCompletion回调。beforeCompletionInvoked = true;if (status.hasSavepoint()) {//嵌套事务才有保存点if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}unexpectedRollback = status.isGlobalRollbackOnly();status.releaseHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction commit");}unexpectedRollback = status.isGlobalRollbackOnly();doCommit(status);}else if (isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = status.isGlobalRollbackOnly();}// Throw UnexpectedRollbackException if we have a global rollback-only// marker but still didn't get a corresponding exception from commit.if (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");}}catch (UnexpectedRollbackException ex) {// can only be caused by doCommittriggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);throw ex;}catch (TransactionException ex) {// can only be caused by doCommitif (isRollbackOnCommitFailure()) {doRollbackOnCommitException(status, ex);}else {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);}throw ex;}catch (RuntimeException | Error ex) {if (!beforeCompletionInvoked) {triggerBeforeCompletion(status);}doRollbackOnCommitException(status, ex);throw ex;}// Trigger afterCommit callbacks, with an exception thrown there// propagated to callers but the transaction still considered as committed.try {triggerAfterCommit(status);}finally {triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);}}finally {cleanupAfterCompletion(status);}}

cleanupAfterCompletion(status);

完成后清理,必要时清除同步,并调用doCleanupAfterCompletion。参数:状态-对象表示事务参见:doCleanupAfterCompletionprivate void cleanupAfterCompletion(DefaultTransactionStatus status) {status.setCompleted();if (status.isNewSynchronization()) {TransactionSynchronizationManager.clear();}if (status.isNewTransaction()) {doCleanupAfterCompletion(status.getTransaction());}if (status.getSuspendedResources() != null) {if (status.isDebug()) {logger.debug("Resuming suspended transaction after completion of inner transaction");}Object transaction = (status.hasTransaction() ? status.getTransaction() : null);resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());}}

commitTransactionAfterReturning(txInfo);执行结束之后
在这里插入图片描述

事务的提交

service的checkout方法执行结束开始提交事务
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146590.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

如何将生物序列tokenization为token?

原理讲解 tokenization是自然语言处理领域非常成熟的一项技术&#xff0c;tokenization就是把我们研究的语言转换成计算机能够识别的数字——token。 在生物领域&#xff0c;如何把核苷酸或氨基酸序列tokenization成token呢&#xff1f; 我们可以使用k-mer技术&#xff1a; k-m…

基于 Qwen2-1.5B Lora 微调训练医疗问答任务

一、Qwen2 Lora 微调 Qwen是阿里巴巴集团Qwen团队研发的大语言模型和大型多模态模型系列。Qwen2 是 Qwen1.5 的重大升级。无论是语言模型还是多模态模型&#xff0c;均在大规模多语言和多模态数据上进行预训练&#xff0c;并通过高质量数据进行后期微调以贴近人类偏好。Qwen具…

DELPHI编译软件时带上当前IDE的版本号

如果通过 CompilerVersion 得到的也只是编译器的版本号。 比如&#xff1a;delphi XE12 是 36 &#xff0c;也仅此而己。 我想得到的是IDE的版本号&#xff0c;比如当前最新版本的DELPHI是&#xff1a;Embarcadero RAD Studio 12 Version 29.0.53571.9782 我想得到 29.0.53…

【JAVA开源】基于Vue和SpringBoot的网上超市系统

本文项目编号 T 037 &#xff0c;文末自助获取源码 \color{red}{T037&#xff0c;文末自助获取源码} T037&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

16【Protues51单片机仿真】智能洗衣机倒计时系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 用直流电机转动模拟洗衣机。要求 有弱洗、普通洗、强洗三种模式&#xff0c;可通过按键选择。可以设置洗衣时长&#xff0c;通关按键选择15、30、45、60、90分钟。时间到蜂鸣器报警提示。LCD 显示…

Webui 显卡有显存,会报错:CUDA out of memory

Webui 显卡明明有显存&#xff0c;会报错&#xff1a;CUDA out of memory 网上找了很多资料&#xff0c;都没有能解决这个问题 &#xff0c;后来发现和电脑虚拟内存设置有关&#xff0c;这里记录一下具体的解决方法&#xff1a; 什么是 CUDA Out of Memory 错误&#xff1f; …

SAP B1 Web Client MS Teams App集成连载三

过程/Procedure&#xff1a; 1.在应用商店中&#xff0c;点击启动 SAP Business One 应用。应用详细信息页面显示如下。 In the Apps store, click SAP Business One app to launch it. The app details page is displayed as below 2.在左上角&#xff0c;有一个包含两个选项的…

淘宝扭蛋机小程序,扭蛋机文化下的新体验

在数字化时代中&#xff0c;扭蛋机逐渐从传统的线下机器转移到了线上互联网中&#xff0c;市场得到了创新发展。扭蛋机小程序具有便捷、多样化、个性化的特点&#xff0c;迎合了当下消费者的线上消费习惯&#xff0c;又能够让扭蛋机玩家体验到新鲜有趣的扭蛋。 扭蛋机是一种热…

光伏板缺陷红外检测数据集

光伏板缺陷红外检测数据集 包含以下4个数据文件&#xff1a; /train&#xff1a;训练集 /valid&#xff1a;验证集 /test&#xff1a;测试集 README.txt&#xff1a;数据说明 【数据说明】检测目标以Pascal VOC格式进行标注&#xff0c;对每个图像进行以下预处理&#xff0c;统…

PCIE集成验证(五)MSI/MSI-X中断

PCI 总线最早采用的中断机制是 INTx&#xff0c;这是基于边带信号的。后续的 PCI/PCI-X版本&#xff0c;为了消除边带信号&#xff0c;降低系统的硬件设计复杂度&#xff0c;逐渐采用了 MSI(Message Signaled Interrupt)/MSI-X&#xff08;消息信号中断&#xff09;的中断机制。…

救生圈检测系统源码分享

救生圈检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Visio…

使用Renesas R7FA8D1BH (Cortex®-M85)和微信小程序App数据传输

目录 概述 1 系统架构 1.1 系统结构 1.2 系统硬件框架结构 1.3 蓝牙模块介绍 2 微信小程序实现 2.1 UI介绍 2.2 代码实现 3 上位机功能实现 3.1 通信协议 3.2 系统测试 4 下位机功能实现 4.1 功能介绍 4.2 代码实现 4.3 源代码文件 5 测试 5.1 编译和下载代码…

微服务基础设施选型

微服务基础设施架构 微服务基础设施架构全貌 微服务 vs SOA (Round 2) 微服务数量越多越复杂 微服务 vs SOA (Round 3) 微服务把服务的粒度变小&#xff0c;进行了标准化拆分。同时也将ESB拆分为了微服务。 微服务基础设施优先级 这里面体现了基础设施的优先级&#xff0c;如…

人工智能之就业方向(The Employment Direction of Artificial Intelligence)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

银河麒麟桌面操作系统V10(SP1)离线升级SSH(OpenSSH)服务

目录 前言 准备工作 准备与目标服务器相同版本的操作系统 准备编译依赖包 下载OpenSSL源码包 下载OpenSSH源码包 升级OpenSSH服务 查看当前版本信息 安装编译依赖包 安装OpenSSL 安装OpenSSH 前言 OpenSSH是一个广泛使用的开源SSH(安全壳)协议的实现,它提供了安…

手机自动化测试环境之夜神模拟器inspector部署验证

1、自动化测试环境部署_总览图检查表流程图 Python需要安装Appium-Python-Clicent去定位元素&#xff1b;Appium是一个中间的服务器&#xff0c;它需要依赖node.js&#xff0c;python的脚本通过appium和手机进行交互&#xff1b;手机app的环境都是java环境&#xff0c;先安装jd…

PMBOK® 第六版 排列活动顺序

目录 读后感—PMBOK第六版 目录 职场中有句玩笑话&#xff1a;“工作是永远做不完的&#xff0c;任何时候都不可能做完。”这里所吐槽的要点就在于工作任务繁多以及工作缺乏秩序。工作确实是做不完的&#xff0c;倘若工作都能完成&#xff0c;那也就不需要工作了。 工作中令人…

【服务器第二期】mobaxterm软件下载及连接

【服务器第二期】mobaxterm软件下载及连接 前言什么是SSH什么是FTP/SFTP mobaxterm软件介绍mobaxterm软件下载SSH登录使用方法1-新建ssh连接方法2-打开已有的ssh连接方法3-通过ssh命令建立连接 SFTP数据传输方法1-建立ssh连接后直接拖拽方法2-建立sftp连接再拖拽方法3-直接使用…

SURILL MILL搭配cnc机器的打样(3维导入 使用)

导入STP文件&#xff0c;然后 选择 &#xff0c;点击 曲面里的 曲面 炸开 (和曲线分开 ) 到处曲面 的面与 面的先分开了 看 实际情况 &#xff0c;接下来 也可以 曲线炸开 来 分解 组合 然后 &#xff0c;此时选择面还是没有生产成线 点击文件 那一行的曲面 绘制 ,借助曲面…

华为云centos7.9按装ambari 2.7.5 hostname 踩坑记录

华为云centos7.9按装ambari 2.7.5踩坑记录 前言升华总结 前言 一般都是废话&#xff0c;本人专业写bug业余运维。起初找了三台不废弃的台式机&#xff0c;开始重装centos系统&#xff0c;开始了HDP3.1.5Ambari2.7.5安装。 推荐一波好文&#xff0c;一路长绿。跑了一段时间没啥…