javascript
【转载】Spring @Async 源码解读。
由于工作中經常需要使用到異步操作,一直在使用@Async, 今天抽空學習了一下它的執行原理,剛好看到一篇寫的很棒的文章,這里轉載過來做個記錄,感謝原作者的無私奉獻。
原文章鏈接地址:https://www.cnblogs.com/dennyzhangdd/p/9026303.html#_label1_4
目錄
- 1.引子
- 2.源碼解析3.總結
?
正文
1.引子
開啟異步任務使用方法:
1).方法上加@Async注解
2).啟動類或者配置類上@EnableAsync
2.源碼解析
雖然spring5已經出來了,但是我們還是使用的spring4,本文就根據spring-context-4.3.14.RELEASE.jar來分析源碼。
2.1.@Async
org.springframework.scheduling.annotation.Async 源碼注釋翻譯:
1 /** 2 * Annotation that marks a method as a candidate for <i>asynchronous</i> execution. 3 * Can also be used at the type level, in which case all of the type's methods are 4 * considered as asynchronous.該注解可以標記一個異步執行的方法,也可以用來標注類,表示類中的所有方法都是異步執行的。 5 * 6 * <p>In terms of target method signatures, any parameter types are supported. 7 * However, the return type is constrained to either {@code void} or 8 * {@link java.util.concurrent.Future}. In the latter case, you may declare the 9 * more specific {@link org.springframework.util.concurrent.ListenableFuture} or 10 * {@link java.util.concurrent.CompletableFuture} types which allow for richer 11 * interaction with the asynchronous task and for immediate composition with 12 * further processing steps.入參隨意,但返回值只能是void或者Future.(ListenableFuture接口/CompletableFuture類)13 * 14 * <p>A {@code Future} handle returned from the proxy will be an actual asynchronous 15 * {@code Future} that can be used to track the result of the asynchronous method 16 * execution. However, since the target method needs to implement the same signature, 17 * it will have to return a temporary {@code Future} handle that just passes a value 18 * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult}, 19 * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}. 20 * Future是代理返回的切實的異步返回,用以追蹤異步方法的返回值。當然也可以使用AsyncResult類(實現ListenableFuture接口)(Spring或者EJB都有)或者CompletableFuture類 21 * @author Juergen Hoeller 22 * @author Chris Beams 23 * @since 3.0 24 * @see AnnotationAsyncExecutionInterceptor 25 * @see AsyncAnnotationAdvisor 26 */ 27 @Target({ElementType.METHOD, ElementType.TYPE}) 28 @Retention(RetentionPolicy.RUNTIME) 29 @Documented 30 public @interface Async { 31 32 /** 33 * A qualifier value for the specified asynchronous operation(s). 34 * <p>May be used to determine the target executor to be used when executing this 35 * method, matching the qualifier value (or the bean name) of a specific 36 * {@link java.util.concurrent.Executor Executor} or 37 * {@link org.springframework.core.task.TaskExecutor TaskExecutor} 38 * bean definition.用以限定執行方法的執行器名稱(自定義):Executor或者TaskExecutor 39 * <p>When specified on a class level {@code @Async} annotation, indicates that the 40 * given executor should be used for all methods within the class. Method level use 41 * of {@code Async#value} always overrides any value set at the class level. 42 * @since 3.1.2 加在類上表示整個類都使用,加在方法上會覆蓋類上的設置 43 */ 44 String value() default ""; 45 46 }
上圖源碼注釋已經寫的很清晰了哈,主要注意3點:
1)返回值:不要返回值直接void;需要返回值用AsyncResult或者CompletableFuture
2)可自定義執行器并指定例如:@Async("otherExecutor")
3)@Async? 必須不同類間調用: A類--》B類.C方法()(@Async注釋在B類/方法中),如果在同一個類中調用,會變同步執行,例如:A類.B()-->A類.@Async C(),原因是:底層實現是代理對注解掃描實現的,B方法上沒有注解,沒有生成相應的代理類。(當然把@Async加到類上也能解決但所有方法都異步了,一般不這么用!)
2.2?@EnableAsync
老規矩咱們直接看類注釋:
?
1 If only one item needs to be customized, null can be returned to keep the default settings. Consider also extending from AsyncConfigurerSupport when possible. 2 Note: In the above example the ThreadPoolTaskExecutor is not a fully managed Spring bean. Add the @Bean annotation to the getAsyncExecutor() method if you want a fully managed bean. In such circumstances it is no longer necessary to manually call the executor.initialize() method as this will be invoked automatically when the bean is initialized. 3 For reference, the example above can be compared to the following Spring XML configuration: 4 <beans> 5 6 <task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/> 7 8 <task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/> 9 10 <bean id="asyncBean" class="com.foo.MyAsyncBean"/> 11 12 <bean id="exceptionHandler" class="com.foo.MyAsyncUncaughtExceptionHandler"/> 13 14 </beans> 15 //注解類和xml基本一致,但是使用注解類還可以自定義線程名前綴(上面的AppConfig-》getAsyncExecutor-》setThreadNamePrefix) 16 The above XML-based and JavaConfig-based examples are equivalent except for the setting of the thread name prefix of the Executor; this is because the <task:executor> element does not expose such an attribute. This demonstrates how the JavaConfig-based approach allows for maximum configurability through direct access to actual componentry. 17 The mode() attribute controls how advice is applied: If the mode is AdviceMode.PROXY (the default), then the other attributes control the behavior of the proxying. Please note that proxy mode allows for interception of calls through the proxy only; local calls within the same class cannot get intercepted that way.//這里就說明了@Async必須在不同方法中調用,即第一部分注意的第三點。 18 Note that if the mode() is set to AdviceMode.ASPECTJ, then the value of the proxyTargetClass() attribute will be ignored. Note also that in this case the spring-aspects module JAR must be present on the classpath, with compile-time weaving or load-time weaving applying the aspect to the affected classes. There is no proxy involved in such a scenario; local calls will be intercepted as well.//當然也可以用Aspect模式織入(需要引入spring-aspects模塊需要的jar)?
?
下面是源碼:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface ? {
/**該屬性用來支持用戶自定義異步注解,默認掃描spring的@Async和EJB3.1的@code @javax.ejb.Asynchronous
* Indicate the 'async' annotation type to be detected at either class
* or method level.
* <p>By default, both Spring's @{@link Async} annotation and the EJB 3.1
* {@code @javax.ejb.Asynchronous} annotation will be detected.
* <p>This attribute exists so that developers can provide their own
* custom annotation type to indicate that a method (or all methods of
* a given class) should be invoked asynchronously.
*/
Class<? extends Annotation> annotation() default Annotation.class;
/**標明是否需要創建CGLIB子類代理,AdviceMode=PROXY時才適用。注意設置為true時,其它spring管理的bean也會升級到CGLIB子類代理
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
* <p><strong>Applicable only if the {@link #mode} is set to {@link AdviceMode#PROXY}</strong>.
* <p>The default is {@code false}.
* <p>Note that setting this attribute to {@code true} will affect <em>all</em>
* Spring-managed beans requiring proxying, not just those marked with {@code @Async}.
* For example, other beans marked with Spring's {@code @Transactional} annotation
* will be upgraded to subclass proxying at the same time. This approach has no
* negative impact in practice unless one is explicitly expecting one type of proxy
* vs. another — for example, in tests.
*/
boolean proxyTargetClass() default false;
/**標明異步通知將會如何實現,默認PROXY,如需支持同一個類中非異步方法調用另一個異步方法,需要設置為ASPECTJ
* Indicate how async advice should be applied.
* <p><b>The default is {@link AdviceMode#PROXY}.</b>
* Please note that proxy mode allows for interception of calls through the proxy
* only. Local calls within the same class cannot get intercepted that way; an
* {@link Async} annotation on such a method within a local call will be ignored
* since Spring's interceptor does not even kick in for such a runtime scenario.
* For a more advanced mode of interception, consider switching this to
* {@link AdviceMode#ASPECTJ}.
*/
AdviceMode mode() default AdviceMode.PROXY;
/**標明異步注解bean處理器應該遵循的執行順序,默認最低的優先級(Integer.MAX_VALUE,值越小優先級越高)
* Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor}
* should be applied.
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
* after all other post-processors, so that it can add an advisor to
* existing proxies rather than double-proxy.
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
執行流程:
如上圖,核心注解就是@Import(AsyncConfigurationSelector.class),一看就是套路ImportSelector接口的selectImports()方法,源碼如下:
1 /**查詢器:基于@EanableAsync中定義的模式AdviceMode加在@Configuration標記的類上,確定抽象異步配置類的實現類 2 * Selects which implementation of {@link AbstractAsyncConfiguration} should be used based 3 * on the value of {@link EnableAsync#mode} on the importing {@code @Configuration} class. 4 * 5 * @author Chris Beams 6 * @since 3.1 7 * @see EnableAsync 8 * @see ProxyAsyncConfiguration 9 */ 10 public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { 11 12 private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = 13 "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"; 14 15 /** 16 * {@inheritDoc} 17 * @return {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration} for 18 * {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()}, respectively 19 */ 20 @Override 21 public String[] selectImports(AdviceMode adviceMode) { 22 switch (adviceMode) { 23 case PROXY://如果配置的PROXY,使用ProxyAsyncConfiguration 24 return new String[] { ProxyAsyncConfiguration.class.getName() }; 25 case ASPECTJ://如果配置的ASPECTJ,使用ProxyAsyncConfiguration 26 return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME }; 27 default: 28 return null; 29 } 30 } 31 32 }?
我們就選一個類ProxyAsyncConfiguration(JDK接口代理)看一下具體實現:
1 /** 2 * {@code @Configuration} class that registers the Spring infrastructure beans necessary 3 * to enable proxy-based asynchronous method execution. 4 * 5 * @author Chris Beams 6 * @author Stephane Nicoll 7 * @since 3.1 8 * @see EnableAsync 9 * @see AsyncConfigurationSelector 10 */ 11 @Configuration 12 @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 13 public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { 14 15 @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) 16 @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 17 public AsyncAnnotationBeanPostProcessor asyncAdvisor() { 18 Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); 19 AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();//新建一個異步注解bean后處理器 20 Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation"); 21 //如果@EnableAsync中用戶自定義了annotation屬性,即異步注解類型,那么設置if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) { 22 bpp.setAsyncAnnotationType(customAsyncAnnotation); 23 } 24 if (this.executor != null) {//Executor:設置線程任務執行器 25 bpp.setExecutor(this.executor); 26 } 27 if (this.exceptionHandler != null) {//AsyncUncaughtExceptionHandler:設置異常處理器 28 bpp.setExceptionHandler(this.exceptionHandler); 29 } 30 bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));//設置是否升級到CGLIB子類代理,默認不開啟 31 bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));//設置執行優先級,默認最后執行 32 return bpp; 33 } 34 35 }
如上圖,ProxyAsyncConfiguration就兩點:
1.就是繼承了AbstractAsyncConfiguration類
2.定義了一個bean:AsyncAnnotationBeanPostProcessor
2.AbstractAsyncConfiguration源碼:
1 /**2 * Abstract base {@code Configuration} class providing common structure for enabling3 * Spring's asynchronous method execution capability.4 * 抽象異步配置類,封裝了通用結構,用以支持spring的異步方法執行能力5 * @author Chris Beams6 * @author Stephane Nicoll7 * @since 3.18 * @see EnableAsync9 */ 10 @Configuration 11 public abstract class AbstractAsyncConfiguration implements ImportAware { 12 13 protected AnnotationAttributes enableAsync;//enableAsync的注解屬性 14 15 protected Executor executor;//Doug Lea老李頭設計的線程任務執行器 16 17 protected AsyncUncaughtExceptionHandler exceptionHandler;//異常處理器 18 19 20 @Override 21 public void setImportMetadata(AnnotationMetadata importMetadata) { 22 this.enableAsync = AnnotationAttributes.fromMap( 23 importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false)); 24 if (this.enableAsync == null) { 25 throw new IllegalArgumentException( 26 "@EnableAsync is not present on importing class " + importMetadata.getClassName()); 27 } 28 } 29 30 /** 31 * Collect any {@link AsyncConfigurer} beans through autowiring. 32 */ 33 @Autowired(required = false) 34 void setConfigurers(Collection<AsyncConfigurer> configurers) { 35 if (CollectionUtils.isEmpty(configurers)) { 36 return; 37 } 38 if (configurers.size() > 1) { 39 throw new IllegalStateException("Only one AsyncConfigurer may exist"); 40 } 41 AsyncConfigurer configurer = configurers.iterator().next(); 42 this.executor = configurer.getAsyncExecutor(); 43 this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler(); 44 } 45 46 }很清晰哈,
屬性:
1)注解屬性
2)異步任務執行器
3)異常處理器
方法:
1)setImportMetadata 設置注解屬性,即屬性1
2)setConfigurers 設置異步任務執行器和異常處理器,即屬性2,3
2.AsyncAnnotationBeanPostProcessor這個Bean,類圖如下:
后面詳細分析AOP詳細過程。
2.3.AOP-Advisor切面初始化:(AsyncAnnotationBeanPostProcessor -》setBeanFactory())
AsyncAnnotationBeanPostProcessor這個類的Bean 初始化時 : BeanFactoryAware接口setBeanFactory方法中,對AsyncAnnotationAdvisor異步注解切面進行了構造。
1 @Override2 public void setBeanFactory(BeanFactory beanFactory) {3 super.setBeanFactory(beanFactory);4 5 AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);6 if (this.asyncAnnotationType != null) {7 advisor.setAsyncAnnotationType(this.asyncAnnotationType);8 }9 advisor.setBeanFactory(beanFactory); 10 this.advisor = advisor; 11 }AsyncAnnotationAdvisor的類圖如下:
2.4.AOP-生成代理類AopProxy(AsyncAnnotationBeanPostProcessor -》postProcessAfterInitialization())
具體的后置處理:AsyncAnnotationBeanPostProcessor的后置bean處理是通過其父類AbstractAdvisingBeanPostProcessor來實現的,
該類實現了BeanPostProcessor接口,復寫postProcessAfterInitialization方法如下圖所示:
1 @Override 2 public Object postProcessAfterInitialization(Object bean, String beanName) { 3 if (bean instanceof AopInfrastructureBean) { 4 // Ignore AOP infrastructure such as scoped proxies. 5 return bean; 6 } 7 //把Advisor添加進bean ProxyFactory-》AdvisedSupport-》Advised 8 if (bean instanceof Advised) { 9 Advised advised = (Advised) bean; 10 if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) { 11 // Add our local Advisor to the existing proxy's Advisor chain... 12 if (this.beforeExistingAdvisors) { 13 advised.addAdvisor(0, this.advisor); 14 } 15 else { 16 advised.addAdvisor(this.advisor); 17 } 18 return bean; 19 } 20 } 21 //構造ProxyFactory代理工廠,添加代理的接口,設置切面,最后返回代理類:AopProxy 22 if (isEligible(bean, beanName)) { 23 ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); 24 if (!proxyFactory.isProxyTargetClass()) { 25 evaluateProxyInterfaces(bean.getClass(), proxyFactory); 26 } 27 proxyFactory.addAdvisor(this.advisor); 28 customizeProxyFactory(proxyFactory); 29 return proxyFactory.getProxy(getProxyClassLoader()); 30 } 31 32 // No async proxy needed. 33 return bean; 34 }isEligible用于判斷這個類或者這個類中的某個方法是否含有注解,AsyncAnnotationAdvisor?實現了PointcutAdvisor接口,滿足條件2如下圖:
19 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { 20 if (advisor instanceof IntroductionAdvisor) { 21 return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); 22 }//滿足第二分支PointcutAdvisor 23 else if (advisor instanceof PointcutAdvisor) { 24 PointcutAdvisor pca = (PointcutAdvisor) advisor; 25 return canApply(pca.getPointcut(), targetClass, hasIntroductions); 26 } 27 else { 28 // It doesn't have a pointcut so we assume it applies. 29 return true; 30 } 31 }isEligible校驗通過后,構造ProxyFactory代理工廠,添加代理的接口,設置切面,最后返回代理類:AopProxy接口實現類
2.5.AOP-切點執行(InvocationHandler.invoke)
上一步生成的代理AopProxy接口,我們這里最終實際生成的是JdkDynamicAopProxy,即JDK動態代理類,類圖如下:
最終執行的是InvocationHandler接口的invoke方法,下面是截取出來的核心代碼:
1 // 得到方法的攔截器鏈 2 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 3 // Check whether we have any advice. If we don't, we can fallback on direct 4 // reflective invocation of the target, and avoid creating a MethodInvocation. 5 if (chain.isEmpty()) { 6 // We can skip creating a MethodInvocation: just invoke the target directly 7 // Note that the final invoker must be an InvokerInterceptor so we know it does 8 // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. 9 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); 10 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); 11 } 12 else { 13 // 構造 14 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); 15 // Proceed to the joinpoint through the interceptor chain. 16 retVal = invocation.proceed(); 17 }@Async注解的攔截器是AsyncExecutionInterceptor,它繼承了MethodInterceptor接口。而MethodInterceptor就是AOP規范中的Advice(切點的處理器)。
chain不為空,執行第二個分支,構造ReflectiveMethodInvocation,然后執行proceed方法。
1 @Override 2 public Object proceed() throws Throwable { 3 // 如果沒有攔截器,直接執行被代理的方法 4 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { 5 return invokeJoinpoint(); 6 } 7 8 Object interceptorOrInterceptionAdvice = 9 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); 10 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { 11 // Evaluate dynamic method matcher here: static part will already have 12 // been evaluated and found to match. 13 InterceptorAndDynamicMethodMatcher dm = 14 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; 15 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { 16 return dm.interceptor.invoke(this); 17 } 18 else { 19 // Dynamic matching failed. 20 // Skip this interceptor and invoke the next in the chain. 21 return proceed(); 22 } 23 } 24 else { 25 // It's an interceptor, so we just invoke it: The pointcut will have 26 // been evaluated statically before this object was constructed. 27 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); 28 } 29 }如上圖,核心方法是InterceptorAndDynamicMethodMatcher.interceptor.invoke(this),實際就是執行了AsyncExecutionInterceptor.invoke,繼續追!
1 public Object invoke(final MethodInvocation invocation) throws Throwable { 2 Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); 3 Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); 4 final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); 5 6 AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod); 7 if (executor == null) { 8 throw new IllegalStateException(//如果沒有自定義異步任務執行器,報下面這行錯,不用管,可以默認執行 9 "No executor specified and no default executor set on AsyncExecutionInterceptor either"); 10 } 11 12 Callable<Object> task = new Callable<Object>() { 13 @Override 14 public Object call() throws Exception { 15 try { 16 Object result = invocation.proceed(); 17 if (result instanceof Future) { 18 return ((Future<?>) result).get();//阻塞等待執行完畢得到結果 19 } 20 } 21 catch (ExecutionException ex) { 22 handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments()); 23 } 24 catch (Throwable ex) { 25 handleError(ex, userDeclaredMethod, invocation.getArguments()); 26 } 27 return null; 28 } 29 }; 30 //提交有任務給執行器 31 return doSubmit(task, executor, invocation.getMethod().getReturnType()); 32 }終極執行核心方法doSubmit()
1 protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) { 2 if (completableFuturePresent) {//先判斷是否存在CompletableFuture這個類,優先使用CompletableFuture執行任務 3 Future<Object> result = CompletableFutureDelegate.processCompletableFuture(returnType, task, executor); 4 if (result != null) { 5 return result; 6 } 7 }//返回值是可監聽Future,定義過回調函數:addCallback 8 if (ListenableFuture.class.isAssignableFrom(returnType)) {9 return ((AsyncListenableTaskExecutor) executor).submitListenable(task); 10 }//返回值是Future 11 else if (Future.class.isAssignableFrom(returnType)) { 12 return executor.submit(task); 13 } 14 else {//沒有返回值 15 executor.submit(task); 16 return null; 17 } 18 }最終執行:就是開啟一個線程啟動...
1 protected void doExecute(Runnable task) { 2 Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task)); 3 thread.start(); 4 } 回到頂部3.總結
整體流程大體可梳理為兩條線:
1.從注解開始:@EnableAsync--》ProxyAsyncConfiguration類構造一個bean(類型:AsyncAnnotationBeanPostProcessor)
2.從AsyncAnnotationBeanPostProcessor這個類的bean的生命周期走:AOP-Advisor切面初始化(setBeanFactory())--》AOP-生成代理類AopProxy(postProcessAfterInitialization())--》AOP-切點執行(InvocationHandler.invoke)
轉載于:https://www.cnblogs.com/wang-meng/p/9478029.html
總結
以上是生活随笔為你收集整理的【转载】Spring @Async 源码解读。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux swap交换分区说明/管理
- 下一篇: spring 事务-使用@Transac