1. 復(fù)現(xiàn)錯(cuò)誤
今天在執(zhí)行quartz
定時(shí)任務(wù)時(shí),報(bào)出如下錯(cuò)誤:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
at com.xxx.SpringApplicationContext.getBean(SpringApplicationContext.java:19)
at com.xxx.quartz.CollectionTaskJob.execute(CollectionTaskJob.java:27)
at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
... 1 common frames omitted
發(fā)現(xiàn)這個(gè)錯(cuò)誤:No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available
。
2. 分析錯(cuò)誤
我們繼續(xù)看錯(cuò)誤,錯(cuò)誤發(fā)生在SpringApplicationContext.getBean
的方法中。
結(jié)合No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available
錯(cuò)誤可知,SpringApplicationContext
拿不到CollectionTaskServiceImpl
這個(gè)類。
如是SpringApplicationContext
的源碼:
@Component
public class SpringApplicationContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringApplicationContext.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> requiredType){
return applicationContext.getBean(requiredType);
}
}
SpringApplicationContext
實(shí)現(xiàn)了 ApplicationContextAware
接口,并由@Component
注解。
我們?cè)偃ネ驴矗e(cuò)誤在CollectionTaskJob
類的execute
方法中,如下代碼:
@Slf4j
@DisallowConcurrentExecution
public class CollectionTaskJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
CollectionTaskServiceImpl collectionTaskServiceImpl = SpringApplicationContext.getBean(CollectionTaskServiceImpl.class);
//此處省略邏輯代碼
}
}
我們?cè)偃タ?code>CollectionTaskServiceImpl類,如下代碼所示:
@Service
public class CollectionTaskServiceImpl implements CollectionTaskService {
//此處省略邏輯代碼
}
CollectionTaskServiceImpl
實(shí)現(xiàn)了CollectionTaskService
接口,并由@Service
注解。
按道理說,CollectionTaskServiceImpl
類注入到spring
容器中,通過SpringApplicationContext
能夠拿得到,但結(jié)果是拿不到的。
但為什么拿不到呢?我們需要寫個(gè)測(cè)試類,如下代碼所示:
@Component
public class Test implements CommandLineRunner, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void run(String... args) throws Exception {
Map<String, CollectionTaskServiceImpl> beansOfType =
applicationContext.getBeansOfType(CollectionTaskServiceImpl.class);
System.out.println();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
測(cè)試類Test
實(shí)現(xiàn)了CommandLineRunner
和ApplicationContextAware
接口,此時(shí),我們運(yùn)行代碼:
你會(huì)清楚的看到,beansOfType
的容器為0
,確實(shí)沒有拿到。
我們將CollectionTaskServiceImpl
修改為CollectionTaskService
:
@Override
public void run(String... args) throws Exception {
Map<String, CollectionTaskService> beansOfType =
applicationContext.getBeansOfType(CollectionTaskService.class);
System.out.println();
}
重新運(yùn)行:
此時(shí),拿到了CollectionTaskServiceImpl
的對(duì)象,但注意紅框處,它采用的是jdk aop
的動(dòng)態(tài)代理。
然后,我修改CollectionTaskServiceImpl
類,不實(shí)現(xiàn)CollectionTaskService
接口,如下代碼所示:
@Service
public class CollectionTaskServiceImpl {
//此處省略邏輯代碼
}
而run
方法依然是CollectionTaskServiceImpl
,如下代碼所示:
@Override
public void run(String... args) throws Exception {
Map<String, CollectionTaskServiceImpl> beansOfType =
applicationContext.getBeansOfType(CollectionTaskServiceImpl.class);
System.out.println();
}
重新運(yùn)行代碼:
如此,也能拿到了CollectionTaskServiceImpl
的對(duì)象,但注意紅框處,它采用的是spring cglib
的動(dòng)態(tài)代理。
分析到這里大體就明白了,可以有如下兩種解決方法。
3. 解決問題
3.1 解決方法一
修改CollectionTaskJob
類的execute
方法,在SpringApplicationContext.getBean
方法中傳入CollectionTaskService.class
接口,如下代碼所示:
@Slf4j
@DisallowConcurrentExecution
public class CollectionTaskJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
CollectionTaskServiceImpl collectionTaskServiceImpl = (CollectionTaskServiceImpl) SpringApplicationContext.getBean(CollectionTaskService.class);
//此處省略邏輯代碼
}
}
3.2 解決方法二
修改CollectionTaskServiceImpl
類,不實(shí)現(xiàn)CollectionTaskService
即可。
4. 分析spring中的jdk和cglib的動(dòng)態(tài)代理
4.1 動(dòng)態(tài)代理對(duì)比
JDK
動(dòng)態(tài)代理是實(shí)現(xiàn)了被代理對(duì)象所實(shí)現(xiàn)的接口,CGLib
是繼承了被代理對(duì)象。
JDK
和CGLib
都是在運(yùn)行期生成字節(jié)碼,JDK
是直接寫Class
字節(jié)碼。CGLib
使用ASM
框架Class
字節(jié)碼,Cglib
代理實(shí)現(xiàn)更復(fù)雜,生成代理類的效率比JDK
代理低。
JDK
調(diào)用代理方法,是通過反射機(jī)制調(diào)用,CGLib
是通過FastClass
機(jī)制直接調(diào)用方法,CGLib
執(zhí)行效率更高。
4.2 原理區(qū)別
java
動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler
來處理。核心是實(shí)現(xiàn)InvocationHandler
接口,使用invoke()
方法進(jìn)行面向切面的處理,調(diào)用相應(yīng)的通知。
而cglib
動(dòng)態(tài)代理是利用asm
開源包,對(duì)代理對(duì)象類的class
文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。
核心是實(shí)現(xiàn)MethodInterceptor
接口,使用intercept()
方法進(jìn)行面向切面的處理,調(diào)用相應(yīng)的通知。
- 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用
JDK
的動(dòng)態(tài)代理實(shí)現(xiàn)AOP
- 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以強(qiáng)制使用
CGLIB
實(shí)現(xiàn)AOP
- 如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口,必須采用
CGLIB
庫(kù),spring
會(huì)自動(dòng)在JDK
動(dòng)態(tài)代理和CGLIB
之間轉(zhuǎn)換
4.3 性能區(qū)別
-
CGLib
底層采用ASM
字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,在jdk6
之前比使用Java
反射效率要高。唯一需要注意的是,CGLib
不能對(duì)聲明為final
的方法進(jìn)行代理,因?yàn)?code>CGLib原理是動(dòng)態(tài)生成被代理類的子類。 -
在
jdk6、jdk7、jdk8
逐步對(duì)JDK
動(dòng)態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下,JDK
代理效率高于CGLIB
代理效率,只有當(dāng)進(jìn)行大量調(diào)用的時(shí)候,jdk6
和jdk7
比CGLIB
代理效率低一點(diǎn),但是到jdk8
的時(shí)候,jdk
代理效率高于CGLIB
代理。
4.4 各自局限
-
JDK
的動(dòng)態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,而不能實(shí)現(xiàn)接口的類就不能實(shí)現(xiàn)JDK
的動(dòng)態(tài)代理。 -
cglib
是針對(duì)類來實(shí)現(xiàn)代理的,他的原理是對(duì)指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對(duì)final
修飾的類進(jìn)行代理。
類型 | 機(jī)制 | 回調(diào)方式 | 適用場(chǎng)景 | 效率 |
JDK動(dòng)態(tài)代理 | 委托機(jī)制,代理類和目標(biāo)類都實(shí)現(xiàn)了同樣的接口,InvocationHandler持有目標(biāo)類,代理類委托InvocationHandler去調(diào)用目標(biāo)類的原始方法 | 反射 | 目標(biāo)類是接口類 | 效率瓶頸在反射調(diào)用稍慢 |
CGLIB動(dòng)態(tài)代理 | 繼承機(jī)制,代理類繼承了目標(biāo)類并重寫了目標(biāo)方法,通過回調(diào)函數(shù)MethodInterceptor調(diào)用父類方法執(zhí)行原始邏輯 | 通過FastClass方法索引調(diào)用 | 非接口類、非final類,非final方法 | 第一次調(diào)用因?yàn)橐啥鄠€(gè)Class對(duì)象,比JDK方式慢。多次調(diào)用因?yàn)橛蟹椒ㄋ饕确瓷淇?,如果方法過多,switch case過多其效率還需測(cè)試 |
4.5 靜態(tài)代理和動(dòng)態(tài)的本質(zhì)區(qū)別
-
靜態(tài)代理只能通過手動(dòng)完成代理操作,如果被代理類增加新的方法,代理類需要同步新增,違背開閉原則。
-
動(dòng)態(tài)代理采用在運(yùn)行時(shí)動(dòng)態(tài)生成代碼的方式,取消了對(duì)被代理類的擴(kuò)展限制,遵循開閉原則。文章來源:http://www.zghlxwxcb.cn/news/detail-765908.html
-
若動(dòng)態(tài)代理要對(duì)目標(biāo)類的增強(qiáng)邏輯擴(kuò)展,結(jié)合策略模式,只需要新增策略類便可完成,無需修改代理類的代碼。文章來源地址http://www.zghlxwxcb.cn/news/detail-765908.html
到了這里,關(guān)于springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type ‘x‘ available的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!