@Async注解失效的九種場景
前言
最近有粉絲問了我一個問題:他在項目某個方法使用@Async注解,但是該方法還是同步執(zhí)行了,異步不起作用,到底是什么原因呢?
偽代碼如下:
@Slf4j
@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:" + value);
}
}
這個問題還是比較有意思的,今天這篇文章總結了@Async注解失效的9種場景,希望對你會有所幫助。
圖片
1 未使用@EnableAsync注解
在Spring中要開啟@Async注解異步的功能,需要在項目的啟動類,或者配置類上,使用@EnableAsync注解。
例如:
@EnableAsync
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableAsync注解相當于一個開關,控制是否開啟@Async注解異步的功能,默認是關閉的。
如果在項目的啟動類上沒使用@EnableAsync注解,則@Async注解異步的功能不生效。
2 內部方法調用
我們在日常開發(fā)中,經(jīng)常需要在一個方法中調用另外一個方法,例如:
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
這個示例中,在UserService類中的test()方法中調用了async()方法。
如果在controller中@Autowired了UserService類的對象,調用了它的test()方法,則async()異步的功能會失效。
我們知道Spring通過@Async注解實現(xiàn)異步的功能,底層其實是通過Spring的AOP實現(xiàn)的,也就是說它需要通過JDK動態(tài)代理或者cglib,生成代理對象。
異步的功能,是在代理對象中增加的,我們必須調用代理對象的test()方法才行。
而在類中直接進行方法的內部調用,在test()方法中調用async()方法,調用的是該類原對象的async方法,相當于調用了this.async()方法,而并非UserService代理類的async()方法。
因此,像這種內部方法調用,@Async注解的異步功能會失效。
3 方法非public
在Java中有4種權限修飾符
- public:所有類都可以訪問。
- private:只能同一個類訪問。
- protected:同一個類,同一個包下的其他類,不同包下的子類可以訪問。
- 默認修飾符:同一個類,同一個包下的其他類可以訪問。
在實際工作中,我們使用頻率最高的可能是public和private了。
如果我在定義Service類中的某個方法時,有時把權限修飾符定義錯了,例如:
@Slf4j
@Service
public class UserService {
@Async
private void async(String value) {
log.info("async:{}", value);
}
}
這個例子中將UserService類的async()方法的權限修飾符定義成了private的,這樣@Async注解也會失效。
因為private修飾的方法,只能在UserService類的對象中使用。
而@Async注解的異步功能,需要使用Spring的AOP生成UserService類的代理對象,該代理對象沒法訪問UserService類的private方法,因此會出現(xiàn)@Async注解失效的問題。
4 方法返回值錯誤
我們在寫一個新的方法時,經(jīng)常需要定義方法的返回值。
返回值可以是void、int、String、User等等,但如果返回值定義錯誤,也可能會導致@Async注解的異步功能失效。
例如:
@Service
public class UserService {
@Async
public String async(String value) {
log.info("async:{}", value);
return value;
}
}
UserService類的async方法的返回值是String,這種情況竟然會導致@Async注解的異步功能失效。
在AsyncExecutionInterceptor類的invoke()方法,會調用它的父類AsyncExecutionAspectSupport中的doSubmit方法,該方法時異步功能的核心代碼,如下:
圖片
從圖中看出,@Async注解的異步方法的返回值,要么是Future,要么是null。
因此,在實際項目中,如果想要使用@Async注解的異步功能,相關方法的返回值必須是void或者Future。
5 方法用static修飾了
有時候,我們的方法會使用static修飾,這樣在調用的地方,可以直接使用類名.方法名,訪問該方法了。
但如果在@Async方法上加了static修飾符,例如:
@Slf4j
@Service
public class UserService {
@Async
public static void async(String value) {
log.info("async:{}", value);
}
}
這時@Async的異步功能會失效,因為這種情況idea會直接報錯:Methods annotated with '@Async' must be overridable 。
使用@Async注解聲明的方法,必須是能被重寫的,很顯然static修飾的方法,是類的靜態(tài)方法,是不允許被重寫的。
因此這種情況下,@Async注解的異步功能會失效。
6 方法用final修飾
在Java種final關鍵字,是一個非常特別的存在。
用final修飾的類,沒法被繼承。
用final修飾的方法,沒法被重寫。
用final修飾的變量,沒法被修改。
如果final使用不當,也會導致@Async注解的異步功能失效,例如:
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public final void async(String value) {
log.info("async:{}", value);
}
}
這種情況下idea也會直接報錯:Methods annotated with '@Async' must be overridable 。
因為使用final關鍵字修飾的方法,是沒法被子類重寫的。
因此這種情況下,@Async注解的異步功能會失效。
7 業(yè)務類沒加@Service注解
有時候,我們在新加Service類時,會忘了加@Service注解,例如:
@Slf4j
//@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
@Service
public class TestService {
@Autowired
private UserService userService;
public void test() {
userService.async("test");
}
}
這種情況下,@Async注解異步的功能也不會生效。因為UserService類沒有使用@Service、@Component或者@Controller等注解聲明,該類不會被Spring管理,因此也就無法使用Spring的異步功能。
8 自己new的對象
在項目中,我們經(jīng)常需要new一個對象,然后對他賦值,或者調用它的方法。
但如果new了一個Service類的對象,可能會出現(xiàn)一些意想不到的問題,例如:
@Slf4j
@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
@Service
public class TestService {
public void test() {
UserService userService = new UserService();
userService.async("test");
}
}
在TestService類的test()方法中,new了一個UserService類的對象,然后調用該對象的async()方法。
很顯然這種情況下,async()方法只能同步執(zhí)行,沒法異步執(zhí)行。
因為在項目中,我們自己new的對象,不會被Spring管理,因此也就無法使用Spring的異步功能。
不過我們可以通過BeanPostProcessor類,將創(chuàng)建的對象手動注入到Spring容器中。
9 Spring無法掃描異步類
我們在Spring項目中可以使用@ComponentScan注解指定項目中掃描的包路徑,例如:
@ComponentScan({"com.susan.demo.service1"})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
項目中com.susan.demo.service1這個路徑是不存在的,會導致@Async注解異步的功能失效。
同時如果@ComponentScan注解定義的路徑,沒有包含你新加的Servcie類的路徑,@Async注解異步的功能也會失效。
好了,今天的文章內容先到這里。