Dubbo 확장 SPI 소스 분석
Dubbo 확장 SPI 소스 분석
SPI 는 이전에 사용 한 적 이 있 는데 그 중에서 가장 중요 한 종 류 는 Extension Loader 입 니 다. 모든 Dubbo 에서 SPI 의 입구 입 니 다.org. apache. dubbo. comon. extension. Extension Loader. getExtension Loader 와 org. apache. dubbo. comon. extension. Extension Loader. getExtension 방법 을 구체 적 으로 소개 합 니 다.getExtensionLoader 는 확장 점 로 더 를 가 져 오고 해당 하 는 모든 확장 점 을 불 러 옵 니 다. getExtension 은 name 에 따라 확장 점 을 가 져 옵 니 다.
1. getExtensionLoader 로 딩 과정
(1) ExtensionLoader 를 어떻게 예화 하 는 지
private static <T> boolean withExtensionAnnotation(Class<T> type) {
// `@SPI`
return type.isAnnotationPresent(SPI.class);
}
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//
if (type == null) {
throw new IllegalArgumentException("Extension type null");
}
//
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// SPI
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// ( )
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// , ,
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
(2) ExtensionLoader 의 구조 기 함 수 를 구체 적 으로 살 펴 보면 그의 실현 은 비교적 간단 하고 많은 조작 을 하지 않 았 다.주로 type 에 값 을 부여 하고 ExtensionFactory 대상 을 가 져 옵 니 다.
private ExtensionLoader(Class<?> type) {
this.type = type;
// ,
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
(3) Extension Factory 가 무엇 에 사용 되 는 지 구체 적 으로 살 펴 보 자. 여기 서 우 리 는 그 가 확장 점 유형 과 진정한 이름 을 입력 하여 확장 을 얻 는 것 을 대충 알 수 있다.여 기 는 우리 SPI 의 구체 적 인 명칭 실현 과 연 결 됩 니 다.
@SPI
public interface ExtensionFactory {
/**
* Get extension.
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);
}
(4) dubbo - common / src / main / resources / META - INF / dubbo / internal / org. apache. dubbo. comon. extension. Extension Factory 에서 볼 수 있 습 니 다. 그 는 기본적으로 세 가지 실현 가능 한 제공 이 있 습 니 다.
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
(5) Adaptive Extension Factory 에서 @ Adaptive 로 표 시 된 것 을 볼 수 있 습 니 다.여기 서 유형 명 을 통 해 알 수 있 듯 이 그의 가장 중요 한 역할 은 다른 Extension Factory 를 대리 하 는 것 이다.그 중에서 가장 중요 한 방법 은 getSupported Extensions 방법 으로 모든 지원 하 는 확장 정 보 를 얻 는 것 입 니 다.
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
// ExtensionFactory
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//
for (String name : loader.getSupportedExtensions()) {
// ExtensionFactory
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
// ExtensionFactory
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
(6) 지원 하 는 모든 확장 정 보 를 가 져 옵 니 다. ExtensionLoader. getSupported Extensions. 여기 서 볼 수 있 습 니 다. 사실 가장 중요 한 방법 은 getExtensionClasses 방법 입 니 다.
public Set<String> getSupportedExtensions() {
//
Map<String, Class<?>> clazzes = getExtensionClasses();
//
return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
}
(7) getExtensionClasses 의 실현 을 관찰 하면 여기 서 알 수 있 듯 이 주로 하 는 일 은 중복 로드 를 방지 하 는 것 이기 때문에 진정한 실현 은 loadExtensionClasses 방법 을 전문 적 으로 살 펴 봐 야 한다.확장 클래스 를 이름 으로 가 져 오기 전에 확장 클래스 이름 에서 확장 클래스 까지 의 맵 관계 표 classes 를 설정 파일 에 따라 분석 한 다음 확장 항목 이름 에 따라 맵 관계 표 에서 해당 하 는 확장 클래스 를 가 져 오 면 됩 니 다.관련 프로 세 스 코드 분석 은 다음 과 같다.
private Map<String, Class<?>> getExtensionClasses() {
//
Map<String, Class<?>> classes = cachedClasses.get();
//
if (classes == null) {
// , ,
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
(8) loadExtensionClasses 방법 을 관찰 하여 실현 한다.이곳 에 서 는 주로 두 가지 일 을 했다.1: 현재 SPI 의 기본 값 을 불 러 옵 니 다.2: 이 종류의 모든 확장 점 을 불 러 오고 name 과 Class 대상 의 형식 으로 저장 합 니 다.다음은 cache Default Extension Name 과 loadDirectory 방법 에 대한 설명 입 니 다.
private Map<String, Class<?>> loadExtensionClasses() {
//
cacheDefaultExtensionName();
// classes
//
Map<String, Class<?>> extensionClasses = new HashMap<>();
// internal extension load from ExtensionLoader's ClassLoader first
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
cache Default Extension Name 방법 을 관찰 합 니 다.이 안 은 실행 이 비교적 간단 합 니 다. 주로 주석 에서 value 값 을 읽 어서 기본 이름 을 가 져 오 는 데 사 용 됩 니 다.
private void cacheDefaultExtensionName() {
// SPI ,
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
// value , SPI
// LoadBalance random。
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
}
if (names.length 1) {
cachedDefaultName = names[0];
}
}
}
loadDirectory 방법 을 관찰 하여 실현 합 니 다.이 폴 더 에서 실제 파일 목록 을 찾 고 그 중의 파일 내용 을 분석 하여 extensionClasses Map 에 넣 는 것 이 주요 기능 입 니 다. 파일 의 내용 을 구체 적 으로 분석 하고 loadResource 를 참고 하여 실현 해 야 합 니 다.
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {
// : / .
String fileName = dir + type;
try {
// classloader url
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
// , ClassLoader
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
// URL , ClassLoader
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
//
if (urls != null) {
while (urls.hasMoreElements()) {
// , extensionClasses,
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t);
}
}
(9) loadResource 의 실현 을 관찰 합 니 다. 주로 파일 작업 을 읽 고 loadClass 에 방법 을 맡 겨 클래스 정 보 를 불 러 옵 니 다.클래스 정 보 를 불 러 오 는 것 도 가장 중요 한 방법 이다.
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
//
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// #
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
//
if (line.length() > 0) {
try {
// key=value
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
(10) loadclass 류 의 실현 을 관찰 하면 최종 적 으로 클래스 맵 을 완성 하 는 곳 임 을 알 수 있다.Adaptive 의 클래스 실현 원리 에 대해 우 리 는 이 장 에서 의 편 뒤에 놓 고 자세하게 이야기 합 니 다.
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// ,
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface.");
}
// Adaptive , , , cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
// wapper ,
// wrapper ,
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
// , , org.apache.dubbo.common.Extension , ,
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// , class
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// Activate , cachedActivates ,
cacheActivateClass(clazz, names[0]);
//
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
이 몇 가지 방법 을 실행 한 후에 몇 개의 필드 를 업데이트 합 니 다: cached Adaptive Class: 현재 Extension 형식 에 대응 하 는 Adaptive Extension 형식 (하나만 가능) cached Wrapper Classes: 현재 Extension 형식 에 대응 하 는 모든 Wrapper 구현 유형 (순서 없 음) cached Activates: 현재 Extension 은 자동 으로 활성화 되 어 캐 시 (map, 무질서) 를 실현 합 니 다.cachedNames: 확장 점 구현 클래스 에 대응 하 는 이름 (여러 이름 을 설정 하면 첫 번 째 값)
2. name 에 따라 확장 점 을 가 져 오 는 방법 getExtension
(1) getExtension 방법 실현.이 안 역시 name 에 따라 확장 점 을 처리 하고 자 물 쇠 를 추가 하여 실제 인용 을 만 드 는 데 주요 역할 을 합 니 다. 그 중에서 도 캐 시 를 사용 하여 처리 합 니 다.
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name null");
}
// SPi
if ("true".equals(name)) {
return getDefaultExtension();
}
// holder, cachedClasses ,
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
(2) getOrCreate Holder 가 캐 시 를 어떻게 보장 하 는 지 살 펴 보 자.
private Holder<Object> getOrCreateHolder(String name) {
// Holder
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
// , putIfAbsent , ,
cachedInstances.putIfAbsent(name, new Holder<>());
// holder
holder = cachedInstances.get(name);
}
return holder;
}
(3) 그리고 createExtension 의 실현 을 살 펴 보 겠 습 니 다. 그 는 확 장 된 class 이름 에 따라 인 스 턴 스 를 만 드 는 클래스 입 니 다.여기 도 확장 점 류 를 만 드 는 주요 실현 입 니 다.다음은 우리 도 다른 확장 점 등록 방법 에 대해 설명 한다.
private T createExtension(String name) {
//
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// , putIfAbsent
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// ,
injectExtension(instance);
// , ,
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
(4) injectExtension 방법 관찰
private T injectExtension(T instance) {
if (objectFactory null) {
return instance;
}
try {
//
for (Method method : instance.getClass().getMethods()) {
// set
// 1. "set"
// 2. 1
// 3.
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
// ,
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// , (String, Integer )
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// set
String property = getSetterProperty(method);
// ExtensionLoader
// setRandom(LoadBalance loadBalance), random
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
3. Adaptive 기능 실현 원리
Adaptive 의 주요 기능 은 모든 확장 점 을 하나의 클래스 로 밀봉 하고 URL 을 통 해 매개 변 수 를 입력 할 때 사용 할 확장 점 을 동적 으로 선택 하 는 것 입 니 다.그 밑바닥 의 실현 원 리 는 바로 동적 대리 이다. 여기 서 우 리 는 소스 코드 의 형식 을 통 해 그 가 어떻게 동적 대 리 를 통 해 로드 하 는 지 알려 줄 것 이다.(1) 여기 서 우리 getAdaptive Extension 방법 에 대해 말하자면 이 안 은 바로 이 종 류 를 진정 으로 얻 는 것 이다.ExtensionLoader 에 서 는 Holder 와 자 물 쇠 를 추가 하 는 방식 으로 유일 하 게 만 들 었 음 을 알 수 있다.
public T getAdaptiveExtension() {
// , Holder
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
// , ,
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
(2) 여기 서 우 리 는 createAdaptive Extension 에서 계속 실현 을 살 펴 본다.이곳 은 주로 몇 가지 방법 으로 포장 되 었 다.
private T createAdaptiveExtension() {
try {
// `getAdaptiveExtensionClass`
// class , injectExtension
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
//
getExtensionClasses();
// ,
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
(3) createAdaptive ExtensionClass 방법 을 구체 적 으로 살 펴 본다.Adaptive 코드 를 만 들 고 컴 파일 하여 class 를 생 성 합 니 다.
private Class<?> createAdaptiveExtensionClass() {
// Adaptive ,
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//
ClassLoader classLoader = findClassLoader();
// , , Java Javassist ,
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.cla ss).getAdaptiveExtension();
// class
return compiler.compile(code, classLoader);
}
(4) 구체 적 으로 Adaptive ClassLoader CodeGenerator. generate 방법 을 통 해 진정한 코드 생 성 을 실현 한다.
public String generate() {
// Adaptive,
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
//
StringBuilder code = new StringBuilder();
//
code.append(generatePackageInfo());
//
code.append(generateImports());
//
code.append(generateClassDeclaration());
//
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
// "}"
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
(5) 여 기 는 주로 그 중의 모든 방법 에 대해 처리한다.구체 적 으로 generateMethod 를 보 는 방법 입 니 다.이곳 의 많은 방법 은 주로 반사 메커니즘 에 의존 하여 방법 을 봉인 하고 최종 문자열 로 연결 하 는 것 이다.그 중에서 가장 관건 적 인 방법 은 generateMethodContent 방법 으로 대리 기능 을 생 성 하 는 것 이다.
private String generateMethod(Method method) {
//
String methodReturnType = method.getReturnType().getCanonicalName();
//
String methodName = method.getName();
//
String methodContent = generateMethodContent(method);
//
String methodArgs = generateMethodArguments(method);
//
String methodThrows = generateMethodThrows(method);
//
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
(6) generateMethodContent 방법 해석.이 부분 은 debug 형식 으로 들 어 오 는 것 을 추천 합 니 다. 코드 를 보 는 것 도 더욱 직접적 입 니 다.이 부분 도 전체 Adaptive 에서 가장 핵심 적 인 코드 로 확장 점 이름 을 가 져 오고 실행 하 는 것 을 포함한다.
private String generateMethodContent(Method method) {
// Adaptive , Adaptive
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// ,
// throw new UnsupportedOperationException
return generateUnsupported(method);
} else {
// URL
int urlTypeIndex = getUrlTypeIndex(method);
if (urlTypeIndex != -1) {
// url
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
//
// "get" , URL
// ,
code.append(generateUrlAssignmentIndirectly(method));
}
//
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// Invocation
// , Invocation ,
// Invocation , getMethodParameter, getParameter
// getMethodParameter dubboURL , "test.a" "testA"
boolean hasInvocation = hasInvocationArgument(method);
// Invocation
code.append(generateInvocationArgumentNullCheck(method));
//
code.append(generateExtNameAssignment(value, hasInvocation));
//
code.append(generateExtNameNullCheck(value));
//
code.append(generateExtensionAssignment());
//
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
이만
마지막.
이 지식 이 비용 을 지불 하 는 시대 에 기술 공 유 를 사랑 하고 직필 하 는 모든 사람들 은 우리 가 존경 할 만하 다!그 러 니 손 에 쥐 고 있 는 마 우 스 를 아 끼 지 말고 왼쪽 단 추 를 누 르 고 작은 편집 에 좋아요 를 눌 러 주세요.더 많은 내용 은 위 챗 공식 번호: 구조 적 시각 에 주목 하 세 요.
특별히 사 의 를 표 하 다
성도 선생님 의 재 미 있 고 유머 러 스 한 설명 에 감 사 드 립 니 다. 저 는 배 운 지식 에 대해 기억 에 남 습 니 다!무궁화 스승 님 의 진지 함 과 책임감 에 감 사 드 립 니 다. 매번 숙제 평 가 는 제 가 나 아 가 는 원동력 입 니 다!담임 필 선생님 의 책임 과 인내심 에 감 사 드 립 니 다. 매번 지루 하지 않 은 수업 통 지 는 제 가 초심 을 잊 지 않 고 좋 은 학습 상 태 를 유지 하 는 정신 적 지주 입 니 다!체크 교육 플랫폼 에 감 사 드 립 니 다. 저 에 게 이번에 적은 돈 을 쓰 면 1 기 체크 훈련 캠프 에 지원 할 수 있 고 심층 적 인 기술 정 수 를 배 울 수 있 는 기 회 를 많이 줄 수 있 습 니 다.그리고 공부 하 는 과정 에서 많은 기술 자 를 알 게 되 었 고 그들 에 게 몇 가지 문 제 를 물 어 볼 수 있 습 니 다. 예 를 들 어 장 씨 어른, 노 씨 어른, 우 생 큰 사람 등 입 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Dubbo 소스 해석(6) Dubbo Protocol은 Dubbo Protocol을 예로 들면Protocol은 두 가지 기능을 포함하는데, 발표 서비스와 인용 서비스이기 때문에, 서비스 발표와 Reference 인용을 할 때 이 인터페이스를 호출해야 한다는 것을 알 수 있다.즉 ServiceBean, Ref...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.