@Transaction,@Async在同一個類中注解失效的原因分析及解決
@Transaction @Async在同一個類中注解失效
在同一個類中,一個方法調用另外一個有注解(比如@Async,@Transational)的方法,注解是不會生效的。
比如,下面代碼例子中,有兩方法,一個有@Async注解,一個沒有。第一次如果調用了有注解的test()方法,會啟動@Async注解作用;第一次如果調用testAsync(),因為它內部調用了有注解的test(),如果你以為系統(tǒng)也會為它啟動Async作用,那就錯了,實際上是沒有的。
@Service
public class TestAsyncService {
public void testAsync() throws Exception {
test();
}
@Async
public void test() throws InterruptedException{
Thread.sleep(10000);//讓線程休眠,根據(jù)輸出結果判斷主線程和從線程是同步還是異步
System.out.println("異步threadId:"+Thread.currentThread().getId());
}
}
運行結果:testAsync()主線程和從線程()test()從線程同步執(zhí)行。
原因:spring 在掃描bean的時候會掃描方法上是否包含@Async注解,如果包含,spring會為這個bean動態(tài)地生成一個子類(即代理類,proxy),代理類是繼承原來那個bean的。
此時,當這個有注解的方法被調用的時候,實際上是由代理類來調用的,代理類在調用時增加異步作用。
然而,如果這個有注解的方法是被同一個類中的其他方法調用的,那么該方法的調用并沒有通過代理類,而是直接通過原來的那個bean,所以就沒有增加異步作用,我們看到的現(xiàn)象就是@Async注解無效。
下面用偽代碼闡述一下原因
@Service
class A{
@Async
method b(){...}
method a(){ //標記1
b();
}
}
//Spring掃描注解后,創(chuàng)建了另外一個代理類,并為有注解的方法加上異步效果
class proxy$A{
A objectA = new A();
method b(){ //標記2
//異步執(zhí)行Async
objectA.b();
}
method a(){ //標記3
objectA.a(); //由于a()沒有注解,所以不會異步執(zhí)行,而是直接調用A的實例的a()方法
}
}
當我們調用A的bean的a()方法的時候,也是被proxyA攔截,執(zhí)行proxyA攔截,執(zhí)行proxyA.a()(標記3),然而,由以上代碼可知,這時候它調用的是objectA.a(),也就是由原來的bean來調用a()方法了,所以代碼跑到了“標記1”。由此可見,“標記2”并沒有被執(zhí)行到,所以異步執(zhí)行的效果也沒有運行。
說說解決
了解了失效的原因,解決的方法就簡單了(兩種):
- 把這兩個方法分開到不同的類中
- 把注解加到類名上面
@Async的實現(xiàn)類方式
詳解:用于開啟異步處理的接口, @Async,使用異步必須再啟動類加上@EnableAsync
方法1:實現(xiàn)接口AsyncConfigurer
@Configuration
public class ThreadConfiguration implements AsyncConfigurer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
@Bean
public Executor getAsyncExecutor() {
executor.setThreadNamePrefix("Zy-TaskExecutor-");
executor.setCorePoolSize(50);// 核心線程池大小
executor.setMaxPoolSize(200);// 最大可創(chuàng)建的線程數(shù)
executor.setQueueCapacity(1000);// 隊列最大長度
executor.setKeepAliveSeconds(300);// 線程池維護線程所允許的空閑時間
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
logger.info("任務線程池初始化...");
return executor;
}
/**
* 只有在方法上添加@Async的出現(xiàn)異常才會跳到此方法中
* */
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
logger.error("線程執(zhí)行出現(xiàn)異常....");
return (e, method, arguments) -> logger.error("exception method : " + method.getName() + " message:" + e.getMessage(), e);
}
/**
* 線程監(jiān)控類,訪問/monitor
* */
@Bean
public ServletRegistrationBean threadPoolMonitorServlet() {
ServletRegistrationBean registration = new ServletRegistrationBean(new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = resp.getWriter();
writer.print("corePoolSize : " + executor.getCorePoolSize());
writer.flush();
writer.close();
}
});
registration.addUrlMappings("/monitor/*");
logger.info("springboot線程監(jiān)控start!");
return registration;
}
}
方法2:直接注入bean
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(200);
executor.setQueueCapacity(1000);
executor.setCorePoolSize(50);
executor.setKeepAliveSeconds(300);
executor.setThreadNamePrefix("Zy-TaskExecutor-");
// 線程池對拒絕任務(無線程可用)的處理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
log.info("任務線程池初始化...");
return executor;
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
使用ShardingJDBC進行數(shù)據(jù)分片以及讀寫分離
ShardingJDBC是一個輕量級的Java框架,提供了數(shù)據(jù)分片、讀寫分離、分布式主鍵生成等數(shù)據(jù)訪問功能,本文將給大家介紹如何使用ShardingJDBC進行數(shù)據(jù)分片以及讀寫分離,需要的朋友可以參考下2024-01-01
詳談Servlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應用
下面小編就為大家?guī)硪黄斦凷ervlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應用。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08

