Java 메모 프레임워크 깊이 분석

9162 단어 java주해프레임
우리는 자주 자바 코드에서 "“@Override", "@Target"등의 모습을 볼 수 있는데, 이것들은 무엇입니까?
자바에서 그것들은'주해'다.
다음은 바이두 백과사전의 설명:java.lang.annotation.Retention은 Annotation 형식을 정의할 때 컴파일러가 사용자 정의 Annotation에 어떻게 대처하는지 지시할 수 있습니다. 미리 설정된 컴파일러는 Annotation 정보를class 파일에 남기지만 가상 기기에서 읽히지 않고 컴파일러나 도구가 실행될 때만 정보를 제공합니다.
즉, 주해는class 파일을 바탕으로 한 것으로 C 언어의 매크로와 이곡동공의 효과가 있다.
class 파일에는 주해의 흔적이 전혀 보이지 않습니다.
주해의 기초는 반사다.그래서 주해는 자바 특유의 개념으로 이해할 수 있다.
1. 메타 메모
자바에 있어요.lang.annotation 패키지 안에 4가지 annotation의'원어'가 정의되어 있습니다.
1).@Target, 명확하게 수식되는 유형에 사용: (방법, 필드, 클래스, 인터페이스 등)
2).@Retention, anntation이 존재할 때까지 설명:
RetentionPolicy.RUNTIME 메모는class 바이트 파일에 존재하며 실행할 때 반사를 통해 얻을 수 있습니다
RetentionPolicy.CLASS 기본 보존 정책, 주석은class 바이트 파일에 존재하지만 실행할 때 얻을 수 없습니다
RetentionPolicy.SOURCE 메모는 원본 코드에만 존재하고class 바이트 파일에는 포함되지 않습니다.
3).@Documented, 기본적으로 주해는javadoc에 기록되지 않지만, 이 주해를 통해 이 주해가 기록되어야 한다는 것을 나타낼 수 있습니다.
4).@Inherited 메타데이터는 태그 메모이며 @Inherited는 태그된 유형이 상속되는 것을 설명합니다.
@Inherited 수식을 사용한 annotation 형식이 클래스에 사용되면, 이 annotation은 클래스의 하위 클래스에 사용됩니다.
2. 메모 사용자 정의

package com.joyfulmath.jvmexample.annnotaion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author deman.lu
* @version on 2016-05-23 13:36
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName {
String value() default "";
} 
먼저 하나의 메모에는 일반적으로 2개의 메타메모 코스메틱이 필요합니다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
구체적인 작용은 위에서 이미 해석되었다.
모든 주석에는 "func"와 유사한 부분이 있습니다.이것은 주해의 매개 변수로 이해할 수 있다.

package com.joyfulmath.jvmexample.annnotaion;
import com.joyfulmath.jvmexample.TraceLog;
/**
* @author deman.lu
* @version on 2016-05-23 13:37
*/
public class Apple {
@FruitName("Apple")
String appleName;
public void displayAppleName()
{
TraceLog.i(appleName);
}
}
이 코드의 log:
05-23 13:39:38.780 26792-26792/com.joyfulmath.jvmexample I/Apple: displayAppleName: null [at (Apple.java:16)]
부치가 성공하지 못했는데, 왜?주해를 위한 "Apple"은 도대체 이 filed의 값을 어떻게 부여해야 하는지, 현재 컴파일러는 어떻게 해야 할지 아직 모른다.
3. 메모 프로세서
우리는 주석이 도대체 어떻게 작동하는지 설명하기 위해 프로세서가 하나 더 필요하다. 그렇지 않으면 주석과 차이가 많지 않을 것이다.
반사 방식으로 메모의 내용을 가져올 수 있습니다.

package com.joyfulmath.jvmexample.annnotaion;
import com.joyfulmath.jvmexample.TraceLog;
import java.lang.reflect.Field;
/**
* @author deman.lu
* @version on 2016-05-23 14:08
*/
public class FruitInfoUtils {
public static void getFruitInfo(Class<?> clazz)
{
String fruitNameStr = "";
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields)
{
if(field.isAnnotationPresent(FruitName.class))
{
FruitName fruitName = field.getAnnotation(FruitName.class);
fruitNameStr = fruitName.value();
TraceLog.i(fruitNameStr);
}
}
}
}
이것은 주해의 일반적인 용법이다.
android 주석 프레임워크 분석
위에서 볼 수 있듯이 주해 프레임의 사용은 본질적으로 반사에 사용해야 한다.
그러나 내가 반사 기능으로 주해 프레임워크를 사용한다면 차라리 그것을 직접 사용하는 것이 오히려 간단하다.
만약 메커니즘이 있다면 대량의 중복된 비슷한 코드를 쓰는 것을 피할 수 있다. 특히android가 개발될 때 대량의findviewbyid & onClick 등 이벤트가 상응하는 것을 피할 수 있다.
코드의 모델은 일치하지만 코드는 각각 다르다. 이때 주해 프레임워크를 사용하면 개발 시간을 대량으로 절약할 수 있고 당연히 상응하는 것은 다른 비용을 증가시킬 수 있다.
다음은 butterknife를 사용하는 예입니다.
@BindString(R.string.login_error)
String loginErrorMessage;
보기에 매우 간단하다. 문자열을stringres에 대응하는 초기 값을 부여하는 것이다.이렇게 쓰면 약간의 시간을 절약할 수 있다.물론 이것은 하나의 예일 뿐,
다른 주석을 대량으로 사용하면 개발 시간을 상당 부분 절약할 수 있다.
다음은 어떻게 실현되었는지 살펴보자.

package butterknife;
import android.support.annotation.StringRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Bind a field to the specified string resource ID.
* <pre><code>
* {@literal @}BindString(R.string.username_error) String usernameErrorText;
* </code></pre>
*/
@Retention(CLASS) @Target(FIELD)
public @interface BindString {
/** String resource ID to which the field will be bound. */
@StringRes int value();
}
BindString, 매개 변수, value, 즉 @StringRes를 지정합니다.
위에서 주석을 정의하고 사용하는 곳이지만 주석을 진정으로 설명하는 곳은 다음과 같다. ButterKnifeProcessor
private Map findAndParseTargets(RoundEnvironment env)
이 함수는 일부 코드를 캡처합니다.

// Process each @BindString element.
for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceString(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindString.class, e);
}
}
BindString 메모의 모든 요소를 찾아서 분석을 시작합니다.

private void parseResourceString(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type is String.
if (!STRING_TYPE.equals(element.asType().toString())) {
error(element, "@%s field type must be 'String'. (%s.%s)",
BindString.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
// Verify common generated code restrictions.
hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element);
hasError |= isBindingInWrongPackage(BindString.class, element);
if (hasError) {
return;
}
// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindString.class).value();
BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
FieldResourceBinding binding = new FieldResourceBinding(id, name, "getString", false);
bindingClass.addResource(binding);
erasedTargetNames.add(enclosingElement);
}
우선 엘리먼트가string 형식인지 검증합니다.

// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindString.class).value(); 
필드의name,string id를 가져옵니다.
결국
Map targetClassMap
원소와 주해 설명, 맵이 저장된 방식입니다.

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
이것이 바로 주해 프레임워크가 시작되는 곳, 독립된 프로세스입니다.구체적인 세부 사항은 본고는 연구하지 않고 제거하기만 하면 된다. 여기는 프레임이 구동되는 곳이다.
위의 정보는 삭제되었습니다. 모든 메모 정보는 targetClassMap에 저장됩니다.
위에 빨간색으로 표시된 코드는 주해 프레임워크의 핵심이어야 한다.
Java SE5를 시작하면서 Java는 apt 도구를 도입하여 주석을 미리 처리할 수 있습니다. Java SE6는 확장 주석 프로세서를 지원합니다.
그리고 컴파일할 때 여러 번 처리하면 사용자 정의 주석 프로세서를 사용하여 자바 컴파일할 때 규칙에 따라 새로운 자바 코드를 생성할 수 있습니다.

JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName)
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(Modifier.FINAL);
} else {
result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
}
TypeName targetType = isFinal ? targetTypeName : TypeVariableName.get("T");
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(parentBinding.generatedClassName, targetType));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetType));
}
result.addMethod(createBindMethod(targetType));
if (isGeneratingUnbinder()) {
result.addType(createUnbinderClass(targetType));
} else if (!isFinal) {
result.addMethod(createBindToTargetMethod());
}
return JavaFile.builder(generatedClassName.packageName(), result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
이 단락의 관건은 새 파일을 만드는 것이다.
그리고 관련 내용을 쓰세요.

좋은 웹페이지 즐겨찾기