您好,登錄后才能下訂單哦!
這篇文章主要介紹“dubbo之@Reference注解有什么作用”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“dubbo之@Reference注解有什么作用”文章能幫助大家解決問題。
看看dubbo是怎么給加了@Reference注解的屬性注入invoker實例,為什么有時候加了@Reference注解的屬性會是null。
看到這個名字,就很容易知道,是專門針對@Reference注解的后置處理。
ReferenceAnnotationBeanPostProcessor的代碼比較多,下面列一下比較重要的內容。
public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, BeanClassLoaderAware, DisposableBean { // 緩存加了@Referece注解的元數據信息,key是bean的名稱或者類名,value是加了@Reference的屬性和方法 private final ConcurrentMap<String, ReferenceInjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<String, ReferenceInjectionMetadata>(256); // 緩存new過的ReferenceBean,相同的key,只會產生一個ReferenceBean private final ConcurrentMap<String, ReferenceBean<?>> referenceBeansCache = new ConcurrentHashMap<String, ReferenceBean<?>>(); // spring設置屬性到bean之前調用該方法 @Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { // 根據bean的類型,獲取需要注入的元數據信息 InjectionMetadata metadata = findReferenceMetadata(beanName, bean.getClass(), pvs); try { // 注入對象 metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of @Reference dependencies failed", ex); } return pvs; } private InjectionMetadata findReferenceMetadata(String beanName, Class<?> clazz, PropertyValues pvs) { // 如果是自定義的消費者,沒有beanName,退化成使用類名作為緩存key String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // 雙重檢查,判斷是否需要刷新注入信息 ReferenceInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); // 判斷是否需要刷新 if (InjectionMetadata.needsRefresh(metadata, clazz)) { // 第一次判斷為需要刷新,則鎖住injectionMetadataCache對象 synchronized (this.injectionMetadataCache) { // 再次判斷是否需要刷新 metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { // 需要刷新,而且原來緩存的信息不為空,清除緩存信息 if (metadata != null) { metadata.clear(pvs); } try { // 生成新的元數據信息 metadata = buildReferenceMetadata(clazz); // 放入緩存 this.injectionMetadataCache.put(cacheKey, metadata); } catch (NoClassDefFoundError err) { throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() + "] for reference metadata: could not find class that it depends on", err); } } } } return metadata; } private ReferenceInjectionMetadata buildReferenceMetadata(final Class<?> beanClass) { // 查找加了@Reference注解的屬性 Collection<ReferenceFieldElement> fieldElements = findFieldReferenceMetadata(beanClass); // 查找加了@Reference注解的屬性 // !!!!@Reference還能加到方法上!!!還真沒試過 // 不過這個不關心,只關注屬性的 Collection<ReferenceMethodElement> methodElements = findMethodReferenceMetadata(beanClass); return new ReferenceInjectionMetadata(beanClass, fieldElements, methodElements); } private List<ReferenceFieldElement> findFieldReferenceMetadata(final Class<?> beanClass) { // 保存加了@Reference注解的屬性列表 final List<ReferenceFieldElement> elements = new LinkedList<ReferenceFieldElement>(); ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() { @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { // 獲取屬性上的@Reference注解 Reference reference = getAnnotation(field, Reference.class); // 如果存在@Reference注解 if (reference != null) { // 不支持靜態屬性的注入 if (Modifier.isStatic(field.getModifiers())) { if (logger.isWarnEnabled()) { logger.warn("@Reference annotation is not supported on static fields: " + field); } return; } // 添加到隊列里 elements.add(new ReferenceFieldElement(field, reference)); } } }); return elements; }
dubbo在ReferenceAnnotationBeanPostProcessor里定義了一個私有的子類
ReferenceInjectionMetadata繼承spring定義的InjectionMetadata類。
之所以需要自定義ReferenceInjectionMetadata類,是因為dubbo的@Reference注解可以使用在屬性和方法上,需要區分開。但是spring定義的InjectionMetadata類,只支持一個injectedElements集合,代碼如下:
public class InjectionMetadata { private static final Log logger = LogFactory.getLog(InjectionMetadata.class); private final Class<?> targetClass; private final Collection<InjectedElement> injectedElements; @Nullable private volatile Set<InjectedElement> checkedElements; public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) { // 構造函數接收兩個參數,類型,注入的元素 this.targetClass = targetClass; this.injectedElements = elements; } ...... ...... ...... public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; // 優先使用checkedElements來注入,如果checkedElements為空,則直接使用injectedElements(沒有調用checkConfigMembers方法,checkedElements會空) Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { // 循環遍歷所有待注入的元素 for (InjectedElement element : elementsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Processing injected element of bean '" + beanName + "': " + element); } // 調用注入方法 element.inject(target, beanName, pvs); } } }
基于這個原因,dubbo定義了ReferenceInjectionMetadata類,代碼如下:
private static class ReferenceInjectionMetadata extends InjectionMetadata { private final Collection<ReferenceFieldElement> fieldElements; private final Collection<ReferenceMethodElement> methodElements; public ReferenceInjectionMetadata(Class<?> targetClass, Collection<ReferenceFieldElement> fieldElements, Collection<ReferenceMethodElement> methodElements) { // 構造函數接收3個參數,類型,待注入的屬性元素,待注入的方法元素 // 把fieldElements和methodElements的內容合并,作為InjectionMetadata的injectedElements super(targetClass, combine(fieldElements, methodElements)); this.fieldElements = fieldElements; this.methodElements = methodElements; } private static <T> Collection<T> combine(Collection<? extends T>... elements) { List<T> allElements = new ArrayList<T>(); for (Collection<? extends T> e : elements) { allElements.addAll(e); } return allElements; } public Collection<ReferenceFieldElement> getFieldElements() { return fieldElements; } public Collection<ReferenceMethodElement> getMethodElements() { return methodElements; } }
代碼很簡單,入參變為3個,屬性和方法列表區分開,然后把兩者合并起來,調用父類的構造函數,用fieldElements保存屬性列表,用methodElements保存方法列表。
屬性注入實際發生在ReferenceFieldElement類,代碼如下:
private class ReferenceFieldElement extends InjectionMetadata.InjectedElement { private final Field field; private final Reference reference; private volatile ReferenceBean<?> referenceBean; // 構造函數會傳入要設置的Field對象,Reference注解對象 protected ReferenceFieldElement(Field field, Reference reference) { super(field, null); this.field = field; this.reference = reference; } @Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class<?> referenceClass = field.getType(); // 構建ReferenceBean對象 referenceBean = buildReferenceBean(reference, referenceClass); // 將屬性設置為可訪問的 ReflectionUtils.makeAccessible(field); // 給Field對象設置屬性 field.set(bean, referenceBean.getObject()); } }
方法注入實際發生在ReferenceMethodElement類,代碼如下:
private class ReferenceMethodElement extends InjectionMetadata.InjectedElement { private final Method method; private final Reference reference; private volatile ReferenceBean<?> referenceBean; protected ReferenceMethodElement(Method method, PropertyDescriptor pd, Reference reference) { super(method, pd); this.method = method; this.reference = reference; } @Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { // 獲取類型 Class<?> referenceClass = pd.getPropertyType(); // 構建ReferenceBean對象 referenceBean = buildReferenceBean(reference, referenceClass); // 將方法設置為可訪問 ReflectionUtils.makeAccessible(method); // 把referenceBean生成的對象作為入參,調用bean對象的method方法 // 看到這里,就能明白,@Reference也可以加在那些setXXX方法上 // 例如有個userService屬性,有個setUserService方法,可以在setUserService方法上加@Reference注解 method.invoke(bean, referenceBean.getObject()); } }
從上面的代碼分析,可以知道屬性的注入,是靠ReferenceAnnotationBeanPostProcessor后置處理來觸發,往filed設置值。
如果這一過程中,發生異常,導致沒有成功為field設置值,則加了@Referencce的屬性就會一直是null。
2020-07-30 17:00:00.013 WARN 13092 --- [ main] com.alibaba.dubbo.config.AbstractConfig : [] [DUBBO] null, dubbo version: 2.6.2, current host: 10.0.45.150
java.lang.reflect.InvocationTargetException: null
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.alibaba.dubbo.config.AbstractConfig.toString(AbstractConfig.java:474)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.alibaba.dubbo.config.spring.beans.factory.annotation.AbstractAnnotationConfigBeanBuilder.build(AbstractAnnotationConfigBeanBuilder.java:79)
at com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.buildReferenceBean(ReferenceAnnotationBeanPostProcessor.java:385)
at com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.access$100(ReferenceAnnotationBeanPostProcessor.java:65)
at com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor$ReferenceFieldElement.inject(ReferenceAnnotationBeanPostProcessor.java:363)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.postProcessPropertyValues(ReferenceAnnotationBeanPostProcessor.java:92)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1400)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1325)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1171)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at com.xdchen.bp.award.api.server.Application.main(Application.java:20)
Caused by: java.lang.IllegalStateException: Failed to check the status of the service com.xdchen.searchplatform.searcher.protocol.SearcherService. No provider available for the service com.xdchen.searchplatform.searcher.protocol.SearcherService:1.0.0111 from the url zookeeper://zk1.esf.fdd:2181/com.alibaba.dubbo.registry.RegistryService?application=award.ddxf.bp.fdd&default.reference.filter=traceIdConsumer,default,consumerCatFilter&default.timeout=6000&dubbo=2.6.2&interface=com.xdchen.searchplatform.searcher.protocol.SearcherService&methods=search,searchByBytes,multiSearch,scrollIndex,searchByHttp,searchByIds,multiSearchByBytes&organization=fangdd&owner=chenxudong&pid=13092®ister.ip=10.0.45.150&revision=3.8.0&side=consumer&timeout=5000×tamp=1596099599500&version=1.0.0111 to the consumer 10.0.45.150 use dubbo version 2.6.2
at com.alibaba.dubbo.config.ReferenceConfig.createProxy(ReferenceConfig.java:422)
at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:333)
at com.alibaba.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:163)
at com.alibaba.dubbo.config.spring.ReferenceBean.getObject(ReferenceBean.java:66)
... 42 common frames omitted2020-07-30 17:00:00.014 INFO 13092 --- [ main] c.a.d.c.s.b.f.a.ReferenceBeanBuilder : [] <dubbo:reference singleton="true" interface="com.xdchen.searchplatform.searcher.protocol.SearcherService" uniqueServiceName="com.xdchen.searchplatform.searcher.protocol.SearcherService:1.0.0111" generic="false" version="1.0.0111" timeout="5000" id="com.xdchen.searchplatform.searcher.protocol.SearcherService" /> has been built.
看這一段錯誤日志,當SearcherService沒有任何provider啟動的時候調用ReferenceBean.getObject方法,就會拋IllegalStateException異常,設置屬性失敗。
網上很多說,遇到加@Reference注解的屬性為null的,應該就是這個情況。
private ReferenceBean<?> buildReferenceBean(Reference reference, Class<?> referenceClass) throws Exception { String referenceBeanCacheKey = generateReferenceBeanCacheKey(reference, referenceClass); ReferenceBean<?> referenceBean = referenceBeansCache.get(referenceBeanCacheKey); if (referenceBean == null) { ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder .create(reference, classLoader, applicationContext) .interfaceClass(referenceClass); // 這里報錯 referenceBean = beanBuilder.build(); referenceBeansCache.putIfAbsent(referenceBeanCacheKey, referenceBean); } return referenceBean; }
buildReferenceBean方法調用ReferenceBeanBuilder.build報錯
ReferenceBeanBuilder.build方法是它的父類AbstractAnnotationConfigBeanBuilder的
public final B build() throws Exception { checkDependencies(); B bean = doBuild(); configureBean(bean); if (logger.isInfoEnabled()) { // 這里報錯 logger.info(bean + " has been built."); } return bean; }
ReferenceBeanBuilder重寫了doBuild方法,返回ReferenceBean對象
@Override protected ReferenceBean doBuild() { return new ReferenceBean<Object>(); }
所以,問題是出在了ReferenceBean.toString方法上
ReferenceBean并沒有重寫toString方法,但他的根父類是AbstractConfig,看錯誤日志,可以看到這個:
at com.alibaba.dubbo.config.AbstractConfig.toString(AbstractConfig.java:474)
AbstractConfig.toString代碼如下:
@Override public String toString() { try { StringBuilder buf = new StringBuilder(); buf.append("<dubbo:"); buf.append(getTagName(getClass())); Method[] methods = getClass().getMethods(); // 拿到當前類的所有方法 for (Method method : methods) { try { String name = method.getName(); // 過濾剩下get和is開頭的方法,但不包括getClass、get和is if ((name.startsWith("get") || name.startsWith("is")) && !"getClass".equals(name) && !"get".equals(name) && !"is".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { int i = name.startsWith("get") ? 3 : 2; String key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1); // 反射獲取方法返回值,拼接字符串 // 就是這里報空指針 Object value = method.invoke(this, new Object[0]); if (value != null) { buf.append(" "); buf.append(key); buf.append("=\""); buf.append(value); buf.append("\""); } } } catch (Exception e) { logger.warn(e.getMessage(), e); } } buf.append(" />"); return buf.toString(); } catch (Throwable t) { logger.warn(t.getMessage(), t); return super.toString(); } }
ReferenceBean類實現類FactoryBean接口,實現了getObject方法,getObject方法滿足get開頭的條件,會被AbstractConfig.toString方法調用到
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean { @Override public Object getObject() throws Exception { return get(); } public synchronized T get() { if (destroyed) { throw new IllegalStateException("Already destroyed!"); } if (ref == null) { init(); } return ref; } private void init() { if (initialized) { return; } initialized = true; ...... ...... ref = createProxy(map); ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods()); ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); } private T createProxy(Map<String, String> map) { ...... ...... Boolean c = check; if (c == null && consumer != null) { c = consumer.isCheck(); } if (c == null) { c = true; // default true } if (c && !invoker.isAvailable()) { throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion()); } if (logger.isInfoEnabled()) { logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); } // create service proxy return (T) proxyFactory.getProxy(invoker); }
省略了大部分代碼,只保留了比較重要的,調用getObject方法,會判斷是否初始化過,如果初始化過,直接返回ref;如果沒有初始化,則會進行初始化,然后調用createProxy方法來創建代理,如果我們沒有配置consumer的check或者check=true,則會檢查invoker對象的可用性“invoker.isAvailable()”,如果不可用,就會拋IllegalStateException異常。
配置消費者的檢查為false,即@Reference(check=false)
看的ReferenceFieldElement.inject方法,很容易以為IllegalStateException是在 “field.set(bean, referenceBean.getObject());”這一行報錯的,但實際上是在 “referenceBean = buildReferenceBean(reference, referenceClass);”
@Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class<?> referenceClass = field.getType(); // 構建ReferenceBean對象 referenceBean = buildReferenceBean(reference, referenceClass); // 將屬性設置為可訪問的 ReflectionUtils.makeAccessible(field); // 給Field對象設置屬性 field.set(bean, referenceBean.getObject()); }
為什么要在AbstractConfig.toString就調用了getObject方法,觸發報錯呢?
如果AbstractConfig.toString過濾掉getObject方法,會發生什么事情呢?
InjectionMetadata.inject方法是遍歷checkedElements列表,挨個調用element.inject方法。
如果AbstractConfig.toString過濾掉getObject方法,則首次調用ReferenceBean.getObject方法是在“field.set(bean, referenceBean.getObject());”。異常沒有被catch住,checkedElements列表的遍歷會被打斷。
會出現這樣的情況,有一個bean需要注入5個代理對象,但是調用第一個ReferenceBean.getObject的時候拋異常,則注入行為被中斷,另外4個屬性也沒有被注入。
關于“dubbo之@Reference注解有什么作用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。