主頁 >  其他 > Dubbo學習記錄(八) -- Spring整合Dubbo中@Reference注解決議原理

Dubbo學習記錄(八) -- Spring整合Dubbo中@Reference注解決議原理

2022-03-08 08:21:02 其他

Spring整合Dubbo中@Reference注解決議原理

@Reference: 可以用在屬性或者方法, 意味著需要參考某個Dubbo服務, 那么Dubbo整合Spring后, 我很好奇怎么把這個程序完成的,

package org.apache.dubbo.demo.provider;
public class Application {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();

        System.in.read();
    }
    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static class ProviderConfiguration {

    }
}
//@EnabeDubbo注解
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {

	//該屬性的值也是DubboComponentScan屬性basePackages的值;
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
	
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
    boolean multipleConfig() default true;
}


  • @DubboComponentScan: 用來掃描@Service 和@Reference注解修飾的類;

DubboComponentScanRegistrar

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("執行DubboComponentScanRegistrar");

        // 拿到DubboComponentScan注解所定義的包路徑,掃描該package下的類,識別這些類上
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);

        // 注冊ServiceAnnotationBeanPostProcessor一個Bean
        // 實作了BeanDefinitionRegistryPostProcessor介面,所以在Spring啟動時會呼叫postProcessBeanDefinitionRegistry方法
        // 該方法會進行掃描,掃描@Service注解了的類,然后生成BeanDefinition(會生成兩個,一個普通的bean,一個ServiceBean),后續的Spring周期中會生成Bean
        // 在ServiceBean中會監聽ContextRefreshedEvent事件,一旦Spring啟動完后,就會進行服務匯出
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);

        // 注冊ReferenceAnnotationBeanPostProcessor
        // 實作了AnnotationInjectedBeanPostProcessor介面,繼而實作了InstantiationAwareBeanPostProcessorAdapter介面
        // 所以Spring在啟動時,在對屬性進行注入時會呼叫AnnotationInjectedBeanPostProcessor介面中的postProcessPropertyValues方法
        // 在這個程序中會按照@Refrence注解的資訊去生成一個RefrenceBean物件
        registerReferenceAnnotationBeanPostProcessor(registry);

    }
}

  • registerServiceAnnotationBeanPostProcessor方法用來決議@Service注解修飾的類, 最后會獲得一個服務實作類, 一個ServiceBean類注入容器;
  • registerReferenceAnnotationBeanPostProcessor: 是用來決議@Reference注解的方法;

registerReferenceAnnotationBeanPostProcessor(registry)

    /**
     * Registers {@link ReferenceAnnotationBeanPostProcessor} into {@link BeanFactory}
     * 
     * @param registry {@link BeanDefinitionRegistry}
     */
    private void registerReferenceAnnotationBeanPostProcessor(BeanDefinitionRegistry registry) {

        // Register @Reference Annotation Bean Processor
        // 注冊一個ReferenceAnnotationBeanPostProcessor做為bean,ReferenceAnnotationBeanPostProcessor是一個BeanPostProcessor
        BeanRegistrar.registerInfrastructureBean(registry,
                ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class);

    }

作業:

  1. 判斷容器中是否包含了referenceAnnotationBeanPostProcessor名稱的Bean,如果有,結束;
  2. 根據Bean的型別ReferenceAnnotationBeanPostProcessor.class,創建一個RootBeanDefinition實體;
  3. 設定Role屬性;
  4. 注入容器中;
public class BeanRegistrar {

    /**
     * Register Infrastructure Bean
     *
     * @param beanDefinitionRegistry {@link BeanDefinitionRegistry}
     * @param beanType               the type of bean
     * @param beanName               the name of bean
     */
    public static void registerInfrastructureBean(BeanDefinitionRegistry beanDefinitionRegistry,
                                                  String beanName,
                                                  Class<?> beanType) {

        if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
        }

    }

}

ReferenceAnnotationBeanPostProcessor

  • 繼承了AnnotationInjectedBeanPostProcessor類,該類又繼承了MergedBeanDefinitionPostProcessor,InstantiationAwareBeanPostProcessorAdapter 類;
  • 應用啟動的時候, 對于我們引入服務的類, Spring會進行對這些類進行依賴注入(類似@Autowired)
  • 依賴注入的時候會呼叫AnnotationInjectedBeanPostProcessor#postProcessPropertyValues()方法,這個方法里面使用了子類的處理邏輯,按照子類的處理邏輯進行依賴注入;
  • 在依賴注入前,會先執行MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法, 子類AnnotationInjectedBeanPostProcessor實作了這個介面, 目的是掃描所有被@Reference注解修飾的方法和屬性的準備作業;
public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor implements
        ApplicationContextAware, ApplicationListener {
	//...
}
public abstract class AnnotationInjectedBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
        //...
}
public abstract class InstantiationAwareBeanPostProcessorAdapter implements SmartInstantiationAwareBeanPostProcessor {
	//....
	@Override
	public PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

		return pvs;
	}

}

MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法

由子類AnnotationInjectedBeanPostProcessor 實作了這個方法;

	public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor 
	void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
	}
	public abstract class AnnotationInjectedBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
        //...
	 @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
            InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            metadata.checkConfigMembers(beanDefinition);
        }
    }
  	} 

一、findInjectionMetadata(beanName, beanType, null)

    private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }
                    try {
                        metadata = buildAnnotatedMetadata(clazz);
                        this.injectionMetadataCache.put(cacheKey, metadata);
                    } catch (NoClassDefFoundError err) {
                 //...
                }
            }
        }
        return metadata;
    }
	
  • 這些元資料是通過反射獲取的,由于反射本身性能不高,所以拿到一些元資料后,需要放入本地快取injectionMetadataCache中,下次直接從快取中獲取;
  • 掃描 @Reference修飾的屬性或者方法,對應型別為AnnotatedFieldElement, AnnotatedMethodElement
  • injectionMetadataCache快取 的 key為 bean的名稱,或者類的名稱, 值的型別AnnotatedInjectionMetadata;
  • AnnotatedInjectionMetadata里面包裝兩個集合, 被@reference修飾的屬性集合, 被@Reference修飾的方法集合

buildAnnotatedMetadata(clazz)

作業:

  • 獲取哪些Filed上有@Reference注解
  • 獲取哪些方法上有@Reference注解
  • 創建一個AnnotatedInjectionMetadata實體, 引數是屬性和方法集合, 屬性注入會按照這個類去進行的;回傳實體;
    private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {

        // 掃描Field
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
        // 掃描Method
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
        // 回傳的是Dubbo定義的AnnotatedInjectionMetadata,接下來就會使用這個類去進行屬性注入
        return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);

    }
  • 掃描Field
    作業:

  • 創建結果集合List;

  • 遍歷beanClass的所有的Field;

  • 獲取Feild上所有的注解;

  • getMergedAttributes獲取@Reference注解, 如果不存在,回傳空;

  • 存在@Reference注解,就會獲取@reference注解的所有屬性,AnnotationAttributes 存盤就是注解的屬性資訊;

  • 存在Reference注解,就加入集合List;

   private List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {

        final List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>();
        ReflectionUtils.doWithFields(beanClass, field -> {
            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {
                AnnotationAttributes attributes = getMergedAttributes(field, annotationType, getEnvironment(), true);

                if (attributes != null) {
				//...
                    elements.add(new AnnotatedFieldElement(field, attributes));
                }
            }
        });

        return elements;

    }
  • 掃描Method
    作業:
  1. 創建結果集合List
  2. 獲取beanClass所有的Method屬性
  3. 獲取所有修飾Method屬性的注解,遍歷
  4. getMergedAttributes判斷Method是否被@Reference注解修飾,有則獲取注解的所有資訊;
  5. 找到set方法所對應的屬性;
  6. 加入結果集合List
    private List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {

        final List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>();

        ReflectionUtils.doWithMethods(beanClass, method -> {

            Method bridgedMethod = findBridgedMethod(method);
            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {
                AnnotationAttributes attributes = getMergedAttributes(bridgedMethod, annotationType, getEnvironment(), true);

                if (attributes != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
					//....
                    // 找到set方法所對應的屬性
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                    elements.add(new AnnotatedMethodElement(method, pd, attributes));
                }
            }
        });

        return elements;

    }

獲取完之后, 退出, 回傳AnnotatedInjectionMetadata到(一)步驟中的呼叫處,然后會將這些Field和Method資訊放入快取;

postProcessPropertyValues

InstantiationAwareBeanPostProcessorAdapter抽象類已經定義好了該方法,子類AnnotationInjectedBeanPostProcessor 重寫該方法,進行@Reference的依賴注入程序;

public abstract class InstantiationAwareBeanPostProcessorAdapter implements SmartInstantiationAwareBeanPostProcessor {

	@Override
	public PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
		return pvs;
	}
	}
}
public abstract class AnnotationInjectedBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
	@Override
    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

        // 尋找需要注入的屬性(被@Reference標注的Field)
        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        }catch(Exception e){
        	//..例外處理
        }
        return pvs;
    }
  }  

作業:

  1. 先獲取代表@Reference修飾的Field和Method資訊類;在(一)步驟中,已經掃描完成,放入快取,因此只需要先從快取中獲取;
  2. 執行屬性注入;

InjectionMetadata#inject(Object target, String beanName, PropertyValues pvs)

作用: 執行屬性注入
作業:

  1. 先獲取被@Reference修飾的屬性集合injectedElements;
  2. 遍歷集合injectedElements
  3. 執行屬性注入;
	public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> elementsToIterate =
				(this.checkedElements != null ? this.checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			boolean debug = logger.isDebugEnabled();
			for (InjectedElement element : elementsToIterate) {
				if (debug) {
					logger.debug("Processing injected element of bean '" + beanName + "': " + element);
				}
				element.inject(target, beanName, pvs);
			}
		}
	}

AnnotatedFieldElement#inject(target, beanName, pvs)

  • InjectedElement 是 InjectionMetadata內部類,定義了inject方法, 但是代表被@Reference修飾的屬性的類是AnnotatedFieldElement
  • 該類繼承了InjectedElement ,重寫了inject方法, 因此呼叫InjectedElement 其實就是呼叫AnnotatedFieldElement的inject方法
    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {}

inject作業:

  1. 獲取Field的型別;
  2. 獲取物件getInjectedObject
  3. 設定為可訪問
  4. 反射給Field設值;
        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            // 給bean物件進行屬性賦值

            Class<?> injectedType = field.getType();

            // 獲取物件,然后進行注入
            Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);

            ReflectionUtils.makeAccessible(field);

            // 欄位賦值,injectedObject就是值
            field.set(bean, injectedObject);

        }

AnnotationInjectedBeanPostProcessor#getInjectedObject方法

獲取注入屬性實體;
作業:

  1. buildInjectedObjectCacheKey()生成注入物件的快取key;
  2. 從快取中獲取 屬性注入的實體;
  3. 如果存在, 直接回傳;
  4. 如果不能存在, 生成屬性注入的實體;
  5. 放入快取中;
    protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
        // ServiceBean:org.apache.dubbo.demo.DemoService#source=private org.apache.dubbo.demo.DemoService org.apache.dubbo.demo.consumer.comp.DemoServiceComponent.demoService#attributes={parameters=[Ljava.lang.String;@42e25b0b}
        // 哪個Service應用了哪個型別的服務,通過什么方式引入的
        String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);
        // cacheKey很雞肋,屬性名不一樣的時候,cacheKey不一樣,導致不能快取, 在一個Service中@Reference兩次同一個服務快取不到

        Object injectedObject = injectedObjectsCache.get(cacheKey);

        if (injectedObject == null) {
            // 生成Bean
            injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);

            // Customized inject-object if necessary
            injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }

        return injectedObject;

    }

ReferenceAnnotationBeanPostProcessor#doGetInjectedBean

引數:

  • attributes: 代表@Reference注解的所有配置的屬性資訊;
  • bean : 代表執行屬性注入的類Bean實體;
  • beanName: 代表執行屬性注入的類Bean實體的名稱;
  • injectedType : 待注入的屬性型別;

作業:

  1. 獲取該屬性注入的服務Bean的名稱referencedBeanName ;
  2. 獲取referencedBeanName屬性;
  3. buildReferenceBeanIfAbsent生成一個ReferenceBean 物件;
  4. registerReferenceBean 注入到Spring容器中, 這樣就可以在其他地方就可以使用@Autowired注解自動注入;
  5. cacheInjectedReferenceBean-快取bean實體;
  6. getOrCreateProxy - 創建一個代理物件回傳;也就是意味著,最后注入的屬性是一個代理物件;
@Override
    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
		//該屬性注入的服務Bean的名稱referencedBeanName
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);

        /**
         * The name of bean that is declared by {@link Reference @Reference} annotation injection
         */
        // @Reference(methods=[Lorg.apache.dubbo.config.annotation.Method;@39b43d60) org.apache.dubbo.demo.DemoService
        // 我要生成一個RefrenceBean,對應的beanName, 根據@Reference注解來標識不同
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);

        // 生成一個ReferenceBean物件
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);

        // 把referenceBean添加到Spring容器中去
        registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
		//快取
        cacheInjectedReferenceBean(referenceBean, injectedElement);

        // 創建一個代理物件,Service中的屬性被注入的就是這個代理物件
        // 內部會呼叫referenceBean.get();
        return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
    }
ReferenceAnnotationBeanPostProcessor#buildReferencedBeanName

創建一個ReferencedBeanName字串;
作業:

  1. 創建一個ServiceBeanNameBuilder構建者物件, 傳入@Reference注解資訊, 注入的服務型別,環境資訊;
  2. 生成ReferencedBeanName字串;
  • 該名稱是以 注入服務介面類名 + 版本號 + 分組名稱 格式生成的;
  • 如: ServiceBean:org.apache.dubbo.demo.DemoService 表示得到該服務Bean的beanName
    private String buildReferencedBeanName(AnnotationAttributes attributes, Class<?> serviceInterfaceType) {
        ServiceBeanNameBuilder serviceBeanNameBuilder = create(attributes, serviceInterfaceType, getEnvironment());
        return serviceBeanNameBuilder.build();
    }
// ServiceBeanNameBuilder # builder()
    public String build() {
        StringBuilder beanNameBuilder = new StringBuilder("ServiceBean");
        // Required
        append(beanNameBuilder, interfaceClassName);
        // Optional
        append(beanNameBuilder, version);
        append(beanNameBuilder, group);
        // Build and remove last ":"
        String rawBeanName = beanNameBuilder.toString();
        // Resolve placeholders
        return environment.resolvePlaceholders(rawBeanName);
    }
ReferenceAnnotationBeanPostProcessor#getReferenceBeanName

獲取ReferenceBeanName名稱字串;
作業:

  1. 獲取注解資訊中的id屬性;
  2. 判斷id是否為空,為空 ;generateReferenceBeanName生成一個ReferenceBeanName字串;
  3. 回傳
    private String getReferenceBeanName(AnnotationAttributes attributes, Class<?> interfaceClass) {
        // id attribute appears since 2.7.3
        String beanName = getAttribute(attributes, "id");

        // beanName為null時會進入if判斷
        if (!hasText(beanName)) {
            beanName = generateReferenceBeanName(attributes, interfaceClass);
        }
        return beanName;
    }
    

generateReferenceBeanName生成一個ReferenceBeanName字串;
作業:

  1. 獲取@Reference注解資訊所有屬性項;
  2. 遍歷屬性項, 每個配置項以 “key = value ”的形式插入構造器字串;
  3. 插入屬性介面型別名稱;
  4. 回傳生成的ReferenceBeanName名稱;
private String generateReferenceBeanName(AnnotationAttributes attributes, Class<?> interfaceClass) {
        StringBuilder beanNameBuilder = new StringBuilder("@Reference");

        if (!attributes.isEmpty()) {
            beanNameBuilder.append('(');
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                beanNameBuilder.append(entry.getKey())
                        .append('=')
                        .append(entry.getValue())
                        .append(',');
            }
            // replace the latest "," to be ")"
            beanNameBuilder.setCharAt(beanNameBuilder.lastIndexOf(","), ')');
        }

        beanNameBuilder.append(" ").append(interfaceClass.getName());

        return beanNameBuilder.toString();
    }
referenceBeanName與referencedBeanName的區別

前者以 介面類路徑名 + 版本號 + 組名 創建的;
后者以 注解所有資訊 + 屬性介面型別 創建的;

buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType)

目的: 創建一個ReferenceBean物件實體;
作業 :

  1. 根據生成的referenceBeanName從快取referenceBeanCache中獲取ReferenceBean實體;
  2. 存在, 直接回傳
  3. 不存在,呼叫ReferenceBeanBuilder#build()方法就創建一個ReferenceBean物件;
  4. 不存在, 創建完Reference實體后, 放入快取中;
  5. 回傳;
    private ReferenceBean buildReferenceBeanIfAbsent(String referenceBeanName, AnnotationAttributes attributes, Class<?> referencedType)  throws Exception {
        ReferenceBean<?> referenceBean = referenceBeanCache.get(referenceBeanName);
        if (referenceBean == null) {

            // 生成了一個ReferenceBean物件,attributes是@Reference注解的引數值
            ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
                    .create(attributes, applicationContext)
                    .interfaceClass(referencedType);
            referenceBean = beanBuilder.build();

            referenceBeanCache.put(referenceBeanName, referenceBean);
        } else if (!referencedType.isAssignableFrom(referenceBean.getInterfaceClass())) {
        //...
        }
        return referenceBean;
    }
ReferenceBeanBuilder#build()

創建一個Reference物件;
作業:

  1. 創建一個ReferenceBean物件
  2. 給ReferenceBean物件的屬性賦值
    public final C build() throws Exception {

        checkDependencies();
        C configBean = doBuild();
        configureBean(configBean);
		//....

        return configBean;

    }
configureBean(C configBean)

作用: 設定ReferenceBean的屬性;
作業:

  1. @Reference注解中的配置項賦值給configBean
  2. 配置注冊中心;
  3. 配置監控中心;
  4. 配置Dubbo應用配置;
  5. 配置模塊資訊;
  6. 還是配置屬性資訊;
    protected void configureBean(C configBean) throws Exception {

        // 
        preConfigureBean(attributes, configBean);

        configureRegistryConfigs(configBean);

        configureMonitorConfig(configBean);

        configureApplicationConfig(configBean);

        configureModuleConfig(configBean);

        // 設定applicationContext、interfaceName、consumer、methods屬性,并呼叫ReferenceBean物件的afterPropertiesSet方法
        postConfigureBean(attributes, configBean);

    }
    //作業流程
    //1.創建資料系結器, 這個是Spring的內容資料系結技術;
    //2. 去空格;
    //3. 決議并設定parameter引數;
    //4. 使用資料系結技術, 將@Reference的基本資料設定給ReferenceBean;
   @Override
    protected void preConfigureBean(AnnotationAttributes attributes, ReferenceBean referenceBean) {
    
        DataBinder dataBinder = new DataBinder(referenceBean);
        // Register CustomEditors for special fields

        // 去掉空格
        dataBinder.registerCustomEditor(String.class, "filter", new StringTrimmerEditor(true));
        dataBinder.registerCustomEditor(String.class, "listener", new StringTrimmerEditor(true));

        // 你可以這么配@Reference(parameters = {"text=123"})
        // 也可以這么配@Reference(parameters = {"text:123"})
        // 最終都會轉變為Map設定到referenceBean中的parameters
        dataBinder.registerCustomEditor(Map.class, "parameters", new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws java.lang.IllegalArgumentException {
                // Trim all whitespace
                String content = StringUtils.trimAllWhitespace(text);
                if (!StringUtils.hasText(content)) { // No content , ignore directly
                    return;
                }
                // replace "=" to ","
                content = StringUtils.replace(content, "=", ",");
                // replace ":" to ","
                content = StringUtils.replace(content, ":", ",");
                // String[] to Map
                Map<String, String> parameters = CollectionUtils.toStringMap(commaDelimitedListToStringArray(content));
                setValue(parameters);
            }
        });
        
        dataBinder.bind(new AnnotationPropertyValuesAdapter(attributes, applicationContext.getEnvironment(), IGNORE_FIELD_NAMES));

    }
    //作業流程:
    //1. 設定背景關系ApplicationContext;
    //2. 設定介面;
   	//3. 設定ConsumerConfig配置; 對應的是 注解里面設定 的consumer 配置資訊;
   	//4. 設定Methodconfig配置
   	//4. 呼叫afterPropertiesSet(), 還是設定屬性星系;
   	
    @Override
    protected void postConfigureBean(AnnotationAttributes attributes, ReferenceBean bean) throws Exception {

        bean.setApplicationContext(applicationContext);

        configureInterface(attributes, bean);

        configureConsumerConfig(attributes, bean);

        configureMethodConfig(attributes, bean);

        bean.afterPropertiesSet();

    }
	
	 public void afterPropertiesSet() throws Exception {
        // 這個方法還是在給ReferenceBean物件的屬性賦值
        if (applicationContext != null) {
            BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConfigCenterBean.class, false, false);
        }

        // 如果@Reference注解中沒有配置consumer引數
        if (getConsumer() == null) {
            // 那么則從Spring容器中尋找ConsumerConfig型別的Bean, 比如通過@Bean定義了一個ConsumerConfig的Bean
            Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);

            if (consumerConfigMap != null && consumerConfigMap.size() > 0) {
                ConsumerConfig consumerConfig = null;

                // 可能存在多個ConsumerConfig型別的Bean,遍歷這些Bean,取第一個沒有配置default或者default為true的Bean作為consumer的值
                for (ConsumerConfig config : consumerConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault()) {
                        if (consumerConfig != null) {
                            throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);
                        }
                        consumerConfig = config;
                    }
                }
                if (consumerConfig != null) {
                    setConsumer(consumerConfig);
                }
            }
        }


        if (getApplication() == null
                && (getConsumer() == null || getConsumer().getApplication() == null)) {
            Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
            if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
                ApplicationConfig applicationConfig = null;
                for (ApplicationConfig config : applicationConfigMap.values()) {
                    if (applicationConfig != null) {
                        throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                    }
                    applicationConfig = config;
                }
                if (applicationConfig != null) {
                    setApplication(applicationConfig);
                }
            }
        }
        if (getModule() == null
                && (getConsumer() == null || getConsumer().getModule() == null)) {
            Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
            if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
                ModuleConfig moduleConfig = null;
                for (ModuleConfig config : moduleConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault()) {
                        if (moduleConfig != null) {
                            throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
                        }
                        moduleConfig = config;
                    }
                }
                if (moduleConfig != null) {
                    setModule(moduleConfig);
                }
            }
        }

        // 如果@Reference注解上沒有配置registryIds
        // 那么則看application或consumer上有沒有配置registryIds
        if (StringUtils.isEmpty(getRegistryIds())) {
            if (getApplication() != null && StringUtils.isNotEmpty(getApplication().getRegistryIds())) {
                setRegistryIds(getApplication().getRegistryIds());
            }
            if (getConsumer() != null && StringUtils.isNotEmpty(getConsumer().getRegistryIds())) {
                setRegistryIds(getConsumer().getRegistryIds());
            }

        }

        if (CollectionUtils.isEmpty(getRegistries())
                && (getConsumer() == null || CollectionUtils.isEmpty(getConsumer().getRegistries()))
                && (getApplication() == null || CollectionUtils.isEmpty(getApplication().getRegistries()))) {
            Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
            if (registryConfigMap != null && registryConfigMap.size() > 0) {
                List<RegistryConfig> registryConfigs = new ArrayList<>();
                if (StringUtils.isNotEmpty(registryIds)) {
                    Arrays.stream(COMMA_SPLIT_PATTERN.split(registryIds)).forEach(id -> {
                        if (registryConfigMap.containsKey(id)) {
                            registryConfigs.add(registryConfigMap.get(id));
                        }
                    });
                }

                if (registryConfigs.isEmpty()) {
                    for (RegistryConfig config : registryConfigMap.values()) {
                        if (StringUtils.isEmpty(registryIds)) {
                            registryConfigs.add(config);
                        }
                    }
                }
                if (!registryConfigs.isEmpty()) {
                    super.setRegistries(registryConfigs);
                }
            }
        }

        if (getMetadataReportConfig() == null) {
            Map<String, MetadataReportConfig> metadataReportConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MetadataReportConfig.class, false, false);
            if (metadataReportConfigMap != null && metadataReportConfigMap.size() == 1) {
                // first elements
                super.setMetadataReportConfig(metadataReportConfigMap.values().iterator().next());
            } else if (metadataReportConfigMap != null && metadataReportConfigMap.size() > 1) {
                throw new IllegalStateException("Multiple MetadataReport configs: " + metadataReportConfigMap);
            }
        }

        // 如果@Reference注解中沒有配置configCenter屬性
        // 那么則從Spring容器中找ConfigCenterConfig型別的bean
        if (getConfigCenter() == null) {
            Map<String, ConfigCenterConfig> configenterMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConfigCenterConfig.class, false, false);

            // 只能配一個ConfigCenterConfig
            if (configenterMap != null && configenterMap.size() == 1) {
                // 設定進去
                super.setConfigCenter(configenterMap.values().iterator().next());
            } else if (configenterMap != null && configenterMap.size() > 1) {
                throw new IllegalStateException("Multiple ConfigCenter found:" + configenterMap);
            }
        }

        if (getMonitor() == null
                && (getConsumer() == null || getConsumer().getMonitor() == null)
                && (getApplication() == null || getApplication().getMonitor() == null)) {
            Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
            if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
                MonitorConfig monitorConfig = null;
                for (MonitorConfig config : monitorConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault()) {
                        if (monitorConfig != null) {
                            throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
                        }
                        monitorConfig = config;
                    }
                }
                if (monitorConfig != null) {
                    setMonitor(monitorConfig);
                }
            }
        }

        if (getMetrics() == null) {
            Map<String, MetricsConfig> metricsConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MetricsConfig.class, false, false);
            if (metricsConfigMap != null && metricsConfigMap.size() > 0) {
                MetricsConfig metricsConfig = null;
                for (MetricsConfig config : metricsConfigMap.values()) {
                    if (metricsConfig != null) {
                        throw new IllegalStateException("Duplicate metrics configs: " + metricsConfig + " and " + config);
                    }
                    metricsConfig = config;
                }
                if (metricsConfig != null) {
                    setMetrics(metricsConfig);
                }
            }
        }

        if (shouldInit()) {
            getObject();
        }
    }

registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType)

目的:將ReferenceBean注入容器, 這個步驟中,會將服務實作類注入容器,這樣一個地方使用@Reference注解后,其他地方就可以通過@Autowired自動注入了,
作業:

  1. 獲取Bean工廠;
  2. 獲取referenceBeanName字串, 將@Reference注解內容以鍵值對拼接 + 服務介面名; (應該是重復操作了)
  3. 判斷容器是否已經存在這個Bean了,如果存在,則是本地服務(@Service注解修飾的服務在@Reference注解之前決議完成了)
  • 存在的話,則根據referenceBeanName去BeanFactory中獲取 代表@Service注解的ServiceBean的BeanDefinition;

  • 獲取ServiceBean的ref屬性;

  • 獲取ServiceBean的名稱;

  • 注冊別名,如果別名和ServiceBean的別名一樣,就不需要注冊別名, 不一樣就以ServiceBean的Bean名稱為鍵, referenceBeanName為值,放入別名緩沖中;

  • 不存在的話,就往容器中注入生成的referenceBean;

    private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       Class<?> interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref"); // ServiceBean --- ref
            String serviceBeanName = runtimeBeanReference.getBeanName();
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
    }

cacheInjectedReferenceBean(referenceBean, injectedElement)

目的: 將生成的referenceBean實體,放入快取;

  • 如果是屬性注入, 則放入injectedFieldReferenceBeanCache中;
  • 如果是方法注入, 則放入injectedMethodReferenceBeanCache中;
    private void cacheInjectedReferenceBean(ReferenceBean referenceBean,
                                            InjectionMetadata.InjectedElement injectedElement) {
        if (injectedElement.getMember() instanceof Field) {
            injectedFieldReferenceBeanCache.put(injectedElement, referenceBean);
        } else if (injectedElement.getMember() instanceof Method) {
            injectedMethodReferenceBeanCache.put(injectedElement, referenceBean);
        }
    }

getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType)

目的: 創建一個代理物件回傳;
為什么是代理物件,而不是上面容器中的服務實作類;
個人看法:
不同服務,分布在不同的機器上,如果是本地服務還好, 如果是遠程服務, 在呼叫服務還需要額外操作,
例如負載均衡, 注冊中心等功能;因此,不能直接回傳服務實作類,而是回傳代理物件, 公共的操作讓代理物件去執行;

作業:

  1. 判斷ServiceBean是否存在,存在則是本地服務, 不存在則是遠程服務;
  2. 本地服務:會生成一個代理物件,代理物件的InvocationHandler會放入快取中, 回傳代理物件;
  3. 遠程服務:呼叫get()獲取一個代理物件回傳;
    private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
        if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    wrapInvocationHandler(referenceBeanName, referenceBean));
        } else {                                    // ReferenceBean should be initialized and get immediately
            // 重點
            return referenceBean.get();
        }
    }
private InvocationHandler wrapInvocationHandler(String referenceBeanName, ReferenceBean referenceBean) {
        return localReferenceBeanInvocationHandlerCache.computeIfAbsent(referenceBeanName, name ->
                new ReferenceBeanInvocationHandler(referenceBean));
    }

總結

@Reference注解服務引入的程序:

有了這些邏輯,@Reference注解服務引入的程序是這樣的:

  1. 得到當前所引入服務對應的ServiceBean的beanName(原始碼中叫referencedBeanName)
  2. 根據@Reference注解的所有資訊+屬性介面型別得到一個referenceBeanName
  3. 根據referenceBeanName從referenceBeanCache獲取對應的ReferenceBean,如果沒有則創建一個ReferenceBean
  4. 根據referencedBeanName(ServiceBean的beanName)判斷Spring容器中是否存在該bean,如果存在則給ref屬性所對應的bean取一個別名,別名為referenceBeanName,
    a. 如果Spring容器中不存在referencedBeanName對應的bean,則判斷容器中是否存在referenceBeanName所對應的Bean,如果不存在則將創建出來的ReferenceBean注冊到Spring容器中(此處這么做就支持了可以通過@Autowired注解也可以使用服務了,ReferenceBean是一個FactoryBean)
  5. 如果referencedBeanName存在對應的Bean,則額外生成一個代理物件,代理物件的InvocationHandler會快取在localReferenceBeanInvocationHandlerCache中,這樣如果引入的是同一個服務,并且這個服務在本地,
  6. 如果referencedBeanName不存在對應的Bean,則直接呼叫ReferenceBean的get()方法得到一個代理物件

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/439242.html

標籤:其他

上一篇:“天池”淘寶用戶消費行為分析——sql

下一篇:詳解Elasticsearch的安裝

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more