An annotation, in the Java computer programming language, is a form of syntactic metadata that can be added to Java source code.[1] Classes, methods, variables, parameters and packages may be annotated. Unlike Javadoc tags, Java annotations can be reflective in that they can be embedded in class files generated by the compiler and may be retained by the Java VM to be made retrievable atrun-time.[2] It is possible to create meta-annotations out of the existing ones in Java.[3]

History[edit]

The Java platform has various ad-hoc annotation mechanisms—for example, the transient modifier, or the @deprecated javadoc tag. JSR-175 introduced the general-purpose annotation (also known asmetadata) facility to the Java Community Process in 2002; it gained approval in September 2004.[4] Annotations became available in the language itself beginning with version 1.5 of the JDK. The apttool provided a provisional interface for compile-time annotation processing in JDK version 1.5; JSR-269 formalized this, and it became integrated into the javac compiler in version 1.6.

Built-in annotations[edit]

Java defines a set of annotations that are built into the language.[5]

Annotations applied to Java code:

  • @Override - Checks that the method is an override. Causes a compile error if the method is not found in one of the parent classes or implemented interfaces.
  • @Deprecated - Marks the method as obsolete. Causes a compile warning if the method is used.
  • @SuppressWarnings - Instructs the compiler to suppress the compile time warnings specified in the annotation parameters.
  • @SafeVarargs - Suppress warnings for all callers of a method or constructor with a generics varargs parameter, since Java 7.
  • @FunctionalInterface - Specifies that the type declaration is intended to be a functional interface, since Java 8.

Annotations applied to other annotations: "or can be called Meta Annotations"

  • @Retention - Specifies how the marked annotation is stored—Whether in code only, compiled into the class, or available at runtime through reflection.
  • @Documented - Marks another annotation for inclusion in the documentation.
  • @Target - Marks another annotation to restrict what kind of Java elements the annotation may be applied to.
  • @Inherited - Marks another annotation to be inherited to subclasses of annotated class (by default annotations are not inherited to subclasses).
  • @Repeatable - Specifies that the annotation can be applied more than once to the same declaration, since Java 8.

Example[edit]

Built-in annotations[edit]

This example shows the use of the @Override annotation. It instructs the compiler to check parent classes for matching methods. In this case, an error is generated as the gettype() method of class Cat does not in fact override getType() of class Animal as desired. If the @Override annotation was absent, a new method of name gettype() would be created in class Cat.

public class Animal {
    public void speak() {
    }

    public String getType() {
        return "Generic animal";
    }
}

public class Cat extends Animal {
    @Override
    public void speak() { // This is a good override.
        System.out.println("Meow.");
    }

    @Override
    public String gettype() { // Compile-time error due to mistyped name.
        return "Cat";
    }
}

Custom annotations[edit]

Annotation type declarations are similar to normal interface declarations. An at-sign (@) precedes the interface keyword. Each method declaration defines an element of the annotation type. Method declarations must not have any parameters or a throws clause. Return types are restricted to primitives, String, Class, enums, annotations, and arrays of the preceding types. Methods can have default values.

  // @Twizzle is an annotation to method toggle().
  @Twizzle
  public void toggle() {
  }

  // Declares the annotation Twizzle.
  public @interface Twizzle {
  }

Annotations may include an optional list of key-value pairs:

  // Same as: @Edible(value = true)
  @Edible(true)
  Item item = new Carrot();

  public @interface Edible {
      boolean value() default false;
  }

  @Author(first = "Oompah", last = "Loompah")
  Book book = new Book();

  public @interface Author {
      String first();
      String last();
  }

Annotations themselves may be annotated to indicate where and when they can be used:

  @Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection.
  @Target({ElementType.METHOD})       // This annotation can only be applied to class methods.
  public @interface Tweezable {
  }

The compiler reserves a set of special annotations (including @Deprecated@Override and @SuppressWarnings) for syntactic purposes.

Annotations are often used by frameworks as a way of conveniently applying behaviours to user-defined classes and methods that must otherwise be declared in an external source (such as an XML configuration file) or programmatically (with API calls). The following, for example, is an annotated JPA data class:

@Entity                                             // Declares this an entity bean
@Table(name = "people")                             // Maps the bean to SQL table "people"
public class Person implements Serializable {
    @Id                                             // Map this to the primary key column.
    @GeneratedValue(strategy = GenerationType.AUTO) // Database will generate new primary keys, not us.
    private Integer id;

    @Column(length = 32)                            // Truncate column values to 32 characters.
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The annotations are not method calls and will not, by themselves, do anything. Rather, the class object is passed to the JPA implementation at run-time, which then extracts the annotations to generate an object-relational mapping.

A complete example is given below:

package com.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,
         ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE,
         ElementType.PACKAGE,ElementType.FIELD,ElementType.LOCAL_VARIABLE})
@Inherited

public @interface Unfinished {
    public enum Priority { LOW, MEDIUM, HIGH }
    String value();
    String[] changedBy() default "";
    String[] lastChangedBy() default "";
    Priority priority() default Priority.MEDIUM;
    String createdBy() default "James Gosling";
    String lastChanged() default "2011-07-08";
}
package com.annotation;

public @interface UnderConstruction {
    String owner() default "Patrick Naughton";
    String value() default "Object is Under Construction.";
    String createdBy() default "Mike Sheridan";
    String lastChanged() default "2011-07-08";
}
package com.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import com.annotation.UnderConstruction;
import com.annotation.Unfinished;
import com.annotation.Unfinished.Priority;
import com.util.Util;

@UnderConstruction(owner="Navin Gujarish")
public class DateValidator implements Validator {
	
    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        String date = (String) value;
        String errorLabel = "Please enter a valid date.";
        if (!component.getAttributes().isEmpty())
        {
            errorLabel = (String) component.getAttributes().get("errordisplayval");
        }

        if (!Util.validateAGivenDate(date))
        {
            @Unfinished(changedBy = "Steve",
                value = "whether to add message to context or not, confirm",
                priority = Priority.HIGH
            )
            FacesMessage message = new FacesMessage();
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            message.setSummary(errorLabel);
            message.setDetail(errorLabel);
            throw new ValidatorException(message);
        }
    }
}

Processing[edit]

When Java source code is compiled, annotations can be processed by compiler plug-ins called annotation processors. Processors can produce informational messages or create additional Java source files or resources, which in turn may be compiled and processed, and also modify the annotated code itself. The Java compiler conditionally stores annotation metadata in the class files, if the annotation has a RetentionPolicy of CLASS or RUNTIME. Later, the JVM or other programs can look for the metadata to determine how to interact with the program elements or change their behavior.

In addition to processing an annotation using an annotation processor, a Java programmer can write their own code that uses reflections to process the annotation. Java SE 5 supports a new interface that is defined in the java.lang.reflect package. This package contains the interface called AnnotatedElement that is implemented by the Java reflection classes including ClassConstructorField,Method, and Package. The implementations of this interface are used to represent an annotated element of the program currently running in the Java Virtual Machine. This interface allows annotations to be read reflectively.

The AnnotatedElement interface provides access to annotations having RUNTIME retention. This access is provided by the getAnnotationgetAnnotations, and isAnnotationPresent methods. Because annotation types are compiled and stored in byte code files just like classes, the annotations returned by these methods can be queried just like any regular Java object. A complete example of processing an annotation is provided below:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// This is the annotation to be processed
// Default for Target is all Java Elements
// Change retention policy to RUNTIME (default is CLASS)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeHeader {
    // Default value specified for developer attribute
    String developer() default "Unknown";
    String lastModified();
    String [] teamMembers();
    int meaningOfLife();
}

// This is the annotation being applied to a class
@TypeHeader(developer = "Bob Bee",
    lastModified = "2013-02-12",
    teamMembers = { "Ann", "Dan", "Fran" },
    meaningOfLife = 42)

public class SetCustomAnnotation {
    // Class contents go here
}

// This is the example code that processes the annotation
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

public class UseCustomAnnotation {
    public static void main(String [] args) {
        Class<SetCustomAnnotation> classObject = SetCustomAnnotation.class;
        readAnnotation(classObject);
    }

    static void readAnnotation(AnnotatedElement element) {
        try {
            System.out.println("Annotation element values: \n");
            if (element.isAnnotationPresent(TypeHeader.class)) {
                // getAnnotation returns Annotation type
                Annotation singleAnnotation = 
                        element.getAnnotation(TypeHeader.class);
                TypeHeader header = (TypeHeader) singleAnnotation;

                System.out.println("Developer: " + header.developer());
                System.out.println("Last Modified: " + header.lastModified());

                // teamMembers returned as String []
                System.out.print("Team members: ");
                for (String member : header.teamMembers())
                    System.out.print(member + ", ");
                System.out.print("\n");

                System.out.println("Meaning of Life: "+ header.meaningOfLife());
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

See also[edit]

References[edit]

External links[edit]







source - https://en.wikipedia.org/wiki/Java_annotation












Annotations in Tiger, Part 1: 메타데이터를 자바 코드에 추가하기


Java 5의 빌트인 어노테이션을 사용하는 방법

요약:  J2SE 5.0 (Tiger)의 새로운 기능인 Annotation은 필요가 많은 메타데이터 기능을 핵심 자바 언어로 가져왔다. 메타테이터가 유용하게 쓰이는 이유를 설명하고 자바의 Annotation 소개한다.

프로그래밍, 특히 자바 프로그래밍의 최신 경향 중 하나는 metadata를 사용한다는 것이다. 메타데이터는 간단히 말해서 데이터에 대한 데이터이다. 메타데이터는 문서화에 사용될 수 있고 코드 의존성을 트래킹하는데 사용되며 심지어 초기 컴파일 시간 체크를 수행 할 때도 사용될 수 있다. XDoclet 같은 (참고자료)메타데이터용 툴들은 이러한 기능을 핵심 자바 언어로 가져왔고 얼마 동안 자바 프로그래밍 관습의 일부가 되었다.

J2SE 5.0 (일명 Tiger)이 나오기 전 까지, 핵심 자바 언어는 javadoc 방법론과 함께 메타데이터 장치에 근접했다. 특별한 태그 세트를 사용하여 코드를 마크업(mark-up)하고 그런 다음 javadoc 명령어를 실행하여 태그를 포맷된 HTML로 변환하여 태그들이 어태치 될 클래스들을 문서화한다. 하지만 Javadoc은 부적당한 메타데이터 툴이다. 문서들을 모으는 것 이상의 다른 목적의 데이터들을 얻을 수 있는 견고하고 표준화된 방법이 없기 때문이다. HTML 코드가 종종 Javadoc 아웃풋과 섞인다는 사실이 이를 더욱 증명하고 있다.

Tiger는 어노테이션이라는 새로운 기능을 통해 보다 다양한 메타데이터 장치를 핵심 자바 언어에 추가했다. 어노테이션은 코드에 추가할 수 있고, 패키지 선언, 유형 선언, 생성자, 메소드, 필드, 매개변수, 변수에 적용할 수 있는 변경자(modifier)이다. Tiger에는 빌트인 어노테이션이 추가되었고 직접 작성할 수 있는 커스텀 어노테이션도 지원한다. 이 글에서는 메타테이터의 효용을 설명하고 Tiger의 빌트인 어노테이션을 소개하겠다. Part 2에서는 커스텀 어노테이션에 대해 자세히 알아 볼 것이다. O'Reilly Media, Inc.에 특별히 감사한다. 이들의 도움으로 내 저서의 어노테이션 챕터에서 코드 샘플을 인용할 수 있었다.(참고자료)

메타데이터의 가치

일반적으로 메타데이터의 효용은 세 가지로 나눌 수 있다. 문서화, 컴파일러 체크, 코드 분석. 코드 레벨의 문서화는 가장 자주 인용되는 사용법이다. 메타데이터는 메소드가 다른 메소드에 의존하고 있다는 것을 가르키는 유용한 방법을 제공한다. 또한 그들이 불완전한지, 특정 클래스가 또 다른 클래스를 레퍼런싱 하는지 등을 가르킨다. 이는 정말로 유용하지만 문서화는 메타데이터를 자바에 추가하는 것과 가장 관련이 적은 항목이다. 코드의 문서화에 있어서는 Javadoc이 사용이 쉽고 강력한 방식을 제공하고 있기 때문이다. 이미 사용하고 있는 것이 있고 작동도 잘 되고 있는데 문서를 굳이 만들 필요는 없지 않는가?

Part 2를 놓치지 마세요!

커스텀 어노테이션을 설명하는 "Part 2"도 잊지 말고 읽어주시기 바랍니다.

컴파일러 체크

보다 중요한 메타데이터의 장점은 컴파일러가 메타데이터를 사용하여 몇 가지 기본적인 컴파일 시간 체크를 수행할 수 있는 기능이라고 할 수 있다. 예를 들어, 이 글의 The Override 어노테이션섹션에서 Tiger 는 메소드가 또 다른 메소드를 수퍼클래스에서 겹쳐 쓰게끔 지정하는 어노테이션을 도입한다. 자바 컴파일러는 메타데이터에서 가르키는 작동이 실제로 코드 레벨에서 발생한다는 것을 확인할 수 있다. 이러한 유형의 버그를 추적해 본 적이 없다면 어리석은 일 같지만 대부분의 자바 프로그래밍 베테랑들은 코드가 왜 작동하지 않는지를 밝혀내기 위해서 밤을 지새우기도 하는 법이다. 메소드가 잘못된 매개변수를 갖고 있고 사실 수퍼클래스에서 메소드를 겹쳐 쓰지 않는다는 것을 발견했을 때의 씁쓸함이란... 메타데이터를 소비하는 툴을 사용하면 이러한 유형의 에러를 쉽게 발견할 수 있다. 많은 밤을 뜬눈으로 지새우지 않아도 된다.

JSR 175

JSR 175(자바 프로그래밍 언어를 위한 메타테이터 장치)메타데이터를 핵심 자바 언어로 결합하는 공식 스팩을 제공한다.(참고자료) JSR에 따르면 어노테이션은 "프로그램의 문법에 직접적인 영향을 주지 않는다. 하지만 개발과 전개 툴은 이러한 어노테이션을 읽고 특정 방식으로 처리하면서 부가적인 자바 프로그래밍 언어 소스 파일, XML 문서 등을 만들어내서 어노테이션을 포함하고 있는 프로그램과 결합하여 사용될 수 있게 한다."

코드 분석

아마도 좋은 어노테이션 또는 메타데이터 툴의 최고의 기능은 여분의 데이터를 사용하여 코드를 분석하는 것이다. 간단한 경우, 코드 목록을 구현하고 필요한 인풋 유형을 제공하고 리턴 유형을 지시한다. 하지만 자바 리플렉션도 같은 기능을 제공한다고 생각할 수도 있다; 결국 이 모든 정보에 대한 코드를 검사할 수 있다. 표면적으로 볼 때 그럴 듯 하지만 실제로 그렇지 않다. 많은 경우 메소드는 인풋으로서 받아들이거나 아웃풋으로 리턴한다. 이는 메소드가 원하는 것이 아니다. 예를 들어, 매개변수 유형이 Object이지만 메소드는 Integer를 사용해서만 작동한다. 이는 메소드가 겹쳐쓰기된 곳에서 쉽게 발생할 수 있다. 그리고 수퍼클래스가 메소드를 일반 매개변수로 선언하던가 많은 직렬화가 진행되는 시스템에서도 쉽게 발생한다. 두 경우 모두 메타데이터는 코드 분석 툴을 지정할 수 있다. 매개변수 유형이 Object이더라도 정말로 원하는 것은 Integer라는 것을 나타낼 수 있다. 이러한 종류의 분석은 상당히 유용하며 그 가치는 상당하다.

보다 복잡한 경우 코드 분석 툴은 모든 종류의 추가 태스크들을 수행할 수 있다. 그 예 중 하나가 Enterprise JavaBean (EJB) 컴포넌트이다. 심지어 간단한 EJB 시스템으로의 의존성과 복잡함은 상당하다. 로컬 인터페이스와 로컬 홈 인터페이스의 가능성과 함께 홈 인터페이스와 원격 인터페이스를 얻는다. 이 모든 클래스들을 연동시키는 것은 진정한 고통이다. 하지만 메타데이터는 이 문제에 대한 솔루션을 제공한다. 좋은 툴은(예를 들어, XDoclet)은 이 모든 의존성을 관리하면서 "코드-레벨" 연결이 없지만 "로컬-레벨" 관계를 가진 클래스들이 연동될 수 있도록 한다. 이것이 바로 메타테이터의 진정한 가치이다.

어노테이션의 기초

메타데이터가 어디에 좋은 지를 이해했으니 Tiger 의 어노테이션을 소개하겠다. Tiger는 "at" 표시(@)를 취한다. 그 뒤에는 어노테이션 이름이 붙는다. 그런 다음 데이터가 필요할 때 어노테이션에 데이터를 제공한다. (name=value)종류의 표기법을 사용할 때 마다 어노테이션을 만든다. 코드 한 조각은 10, 50 또는 그 이상의 어노테이션을 가진다. 하지만 많은 어노테이션은 같은 어노테이션 유형을 사용한다. 이 유형은 실제 구조체이고 어노테이션 자체는 이 유형의 특정 사용법이다. (사이드 바 참조 어노테이션 또는 어노테이션 유형?)

어노테이션 또는 어노테이션 유형?

어노테이션과 어노테이션 유형이 무엇인지 혼돈스러운가? 가장 쉽게 이해하려면 이미 익숙한 자바 언어 개념의 관점에서 생각해야 한다. 단일 클래스 (예를 들어, Person)를 정의할 수 있고 단 한 버전의 클래스를 JVM에 가질 수 있다. (단, 중첩 클래스 경로를 수행하지 않는 한) 하지만 주어진 시간동안 사용할 때, 이 클래스의 10개 또는 20개의 인스턴스를 갖게 된다. 여전히 단 하나의 Person 클래스만 존재하지만 다양한 방식으로 여러 번 사용된다. 어노테이션 유형과 어노테이션의 경우도 이와 같다. 어노테이션 유형은 클래스와 유사하고 어노테이션은 이 클래스의 인스턴스와 비슷하다.

어노테이션은 세 가지 기본 범주로 나뉜다:

  • Marker 어노테이션은 변수가 없다. 이 어노테이션은 이름으로 구분되며 추가 데이터 없이 나타난다. 예를 들어,@MarkerAnnotation은 marker 어노테이션이다. 데이터가 없으며 단지 어노테이션 이름만 있을 뿐이다. 

  • Single-value 어노테이션은 marker와 비슷하지만 데이터를 제공한다. 싱글 비트 데이트를 제공하기 때문에 간단한 신택스를 사용할 수 있다. (단, 어노테이션 유형이 이 문법을 수용해야 함):@SingleValueAnnotation("my data")이는 @표시만 제외하고는 일반적인 자바 메소드 호출과 비슷하다. 

  • Full 어노테이션은 다중 데이터 멤버를 갖고 있다. 결과적으로 전체 신택스를 사용해야 한다. (그리고 어노테이션은 일반 자바 메소드와 더 이상 비슷하지 않다): @FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")

디폴트 신택스를 통해 어노테이션에 값을 제공하는 것 외에도 한 개 이상의 값을 전달해야 할 때 name-value쌍을 사용할 수 있다. 또한 어노테이션 변수에 값 어레이를 제공할 수 있다. 이때 중괄호({})를 사용한다. Listing 1은 어노테이션에서의 값의 어레이 예제이다.


Listing 1. 어노테이션에서 어레이 값 사용하기
@TODOItems({    // Curly braces indicate an array of values is being supplied
  @TODO(
    severity=TODO.CRITICAL,
    item="Add functionality to calculate the mean of the student's grades",
    assignedTo="Brett McLaughlin"
  ),
  @TODO(
    severity=TODO.IMPOTANT,
    item="Print usage message to screen if no command-line flags specified",
    assignedTo="Brett McLaughlin"
  ),
  @TODO(
    severity=TODO.LOW,
    item="Roll a new website page with this class's new features",
    assignedTo="Jason Hunter"
  )
})

Listing 1의 예제는 보기보다 간단하다. TODOItems 어노테이션 유형은 값을 취하는 하나의 변수를 갖고 있다. 여기에서 제공되는 값은 매우 복잡하다. 하지만 TODOItems를 사용하면 single-value 어노테이션 스타일과 실제로 맞다. Single value가 어레이라는 것을 제외하면 말이다. 이 어레이는 세 개의 TODO 어노테이션을 포함하고 있는데 각각 값이 증폭된다. 콤마는 각 어노테이션에서 값을 분리하고 하나의 어레이에서의 값도 콤마로 분리된다.

TODOItems TODO는 커스텀 어노테이션이다. 커스텀 어노테이션은 Part 2의 주제이다. 여러분에게 복잡한 어노테이션을 보여주고 싶었다. Listing 1은 어떤 어노테이션 보다 복잡하지만 그렇게 심하지는 않다. 자바의 표준 어노테이션 유형을 살펴본다면 그렇게 복잡한 것도 드물다. 다음 섹션에서는 Tiger의 기본 어노테이션 유형이 사용하기 쉽다는 것을 알게될 것이다.

Override 어노테이션

Tiger의 첫 번째 빌트인 어노테이션 유형은 Override이다. Override는 메소드에 대해서만 사용되어야 한다. (클래스, 패키지 선언, 기타 구조체는 안된다.) 주석이 첨가된 메소드는 수퍼클래스에서 메소드를 오버라이드한다는 것을 나타낸다. Listing 2는 예제이다.


Listing 2. The Override 어노테이션
package com.oreilly.tiger.ch06;

public class OverrideTester {

  public OverrideTester() { }

  @Override
  public String toString() {
    return super.toString() + " [Override Tester Implementation]";
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }
}

Listing 2는 따라가기 쉽다. @Override어노테이션은 두 개의 메소드, toString() hashCode() OverrideTester 클래스의 수퍼클래스 (java.lang.Object)에서 메소드의 버전을 오버라이드 한다는 것을 나타내고 있다. 언뜻 보기에는 사소한 것 같지만 매우 좋은 기능이다. 이들 메소드를 오버라이딩 하지 않고는 클래스를 컴파일 할 수 없다. 어노테이션은 toString()과 썩일 때 적어도 hashCode()와 맞는다는 것을 확인해야 하는 것을 나타낸다.

이 어노테이션 유형은 코딩하기엔 너무 늦었거나 무언가를 잘못 타이핑했을 때 빛을 발한다. (Listing 3)


Listing 3. Override 어노테이션의 오타 찾아내기
package com.oreilly.tiger.ch06;

public class OverrideTester {

  public OverrideTester() { }

  @Override
  public String toString() {
    return super.toString() + " [Override Tester Implementation]";
  }

  @Override
public int hasCode() {
    return toString().hashCode();
  }
}

Listing 3에서, hashCode() hasCode()로 잘못 표기되었다. 어노테이션은 hasCode()가 메소드를 오버라이드해야 한다는 것을 지시한다. 하지만 컴파일 시, javac는 수퍼클래스(java.lang.Object)가 오버라이드 할 hasCode()라는 메소드가 없다는 것을 알게 된다. 결과적으로 컴파일러는 에러를 표시한다. (그림 1)


그림 1. Override 어노테이션에서의 컴파일러 경고
 

빠진 기능

Deprecated로 에러 유형 메시지를 single-value 어노테이션 유형으로 포함시킬 수 있도록 한다면 좋을 것이다. 컴파일러는 사용자가 deprecated 메소드를 사용할 때 메시지를 프린트할 수 있다. 메시지는 이 메소드를 사용할 때의 결과가 얼마나 심각한지를 나타낼 수 있다. 이 메소드가 정지할 때 대안을 권할 수도 있다. 아마 다음 J2SE 버전에 포함될 것 같다. (Mustang이 임시 이름이다.)

이 간편한 기능으로 오타를 매우 빠르게 잡을 수 있다.

Deprecated 어노테이션

이제 Deprecated표준 어노테이션 유형을 살펴보자. Override와 마찬가지로 Deprecated는 marker 어노테이션이다. Deprecated를 사용하여 더 이상 사용되지 말아야 하는 메소드에 주석을 단다. Override와 다른 점은, Deprecated는 더 이상 사용되지 말아야 하는(depreciated) 메소드와 같은 라인상에 놓여져야 한다. (이유는 나도 모르겠다.)


Listing 4. Deprecated 어노테이션 사용하기
package com.oreilly.tiger.ch06;

public class DeprecatedClass {

  @Deprecated public void doSomething() {
    // some code
  }

  public void doSomethingElse() {
    // This method presumably does what doSomething() does, but better
  }
}

이 클래스를 컴파일 할 때 비정상적인 그 어떤 것도 기대해서는 안된다. 오버라이드 또는 호출이든 Depreciated 메소드를 사용하면 컴파일러는 어노테이션을 처리하고 메소드가 사용되어서는 안된다는 것을 알게 되고 에러 메시지를 만든다. (그림 2)


그림 2. Deprecated 어노테이션의 컴파일러 경고
 

컴파일러 경고를 켜고 정상적인 depreciation 경고를 원한다는 것을 자바 컴파일러에게 명령한다. 두 플래그 -deprecated또는 -Xlint:deprecated중 하나와 javac명령어를 사용할 수 있다.

SuppressWarnings 어노테이션

마지막 어노테이션 유형은 SuppressWarnings이다. 이것이 어떤 일을 수행하는지 알아내는 것은 쉽다. 하지만 왜 이 어노테이션이 중요한지는 분명하지 않다. 이는 실제로 Tiger의 새로운 기능의 부작용이다. 예를 들어, generics를 생각해보자. generics는 모든 유형의 새로운 type-safe 작동을 만든다. 특히 자바 컬렉션의 경우 더욱 그렇다. 하지만 generics 때문에 컴파일러는 컬렉션이 type-safety 없이 사용될 때 경고를 던진다. Tiger를 겨냥한 코드에는 유용하지만 Java 1.4.x의 경우엔 코드 작성이 고통 그 자체이다. 전혀 신경 쓰지 않은 것에 대해 경고를 받아야 한다. 컴파일러를 어떻게 하면 없앨 수 있을까?

SupressWarnings는 구원자다. Override Deprecated와는 다르게 SupressWarnings는 변수를 갖고 있다. 따라서 이를 작동하게 하려면 싱글-어노테이션 유형을 사용한다. 값 어레이로서 변수를 제공할 수 있다. 각각 삭제할(Suppress) 특정 유형의 경고를 나타낸다. Listing 5의 예제를 보자. Tiger에서 에러를 만드는 코드이다.


Listing 5. type-safe가 아닌 Tiger 코드
public void nonGenericsMethod() {
  List wordList = new ArrayList();    // no typing information on the List

  wordList.add("foo");                // causes error on list addition
}

그림 3은 Listing 5에서 코드 컴파일을 한 결과이다.


그림 3. non-typed 코드에서 컴파일러 경고
 

Listing 6은 SuppressWarnings 어노테이션을 사용하여 번거로운 경고를 제거한다.


Listing 6. 경고 제거하기
@SuppressWarnings(value={"unchecked"})
public void nonGenericsMethod() {
  List wordList = new ArrayList();    // no typing information on the List

  wordList.add("foo");                // causes error on list addition
}

간단하지 않은가? 경고 유형을 배치하고(그림 3의 "unchecked") SuppressWarnings에 전달하면 된다.

SuppressWarnings의 변수 값이 어레이를 취한다는 사실은 같은 어노테이션으로 다중의 경고를 삭제할 수 있음을 의미한다. 예를 들어,@SuppressWarnings(value={"unchecked", "fallthrough"})는 두 개의 값 어레이를 취한다. 이 장치는 매우 유연한 방식을 제공하여 장황하지 않게 에러를 핸들 할 수 있다.

결론

이 글에서 본 문법이 다소 생소하더라도 어노테이션은 이해하기도 쉽고 사용도 쉽다는 것을 알아야 한다. Tiger 에서 제공하는 표준 어노테이션 유형이 어설프고 개선의 여지가 많다. 메타데이터는 점점 유용해지고 있고 자신의 애플리케이션에 맞는 어노테이션 유형을 사용할 수 있을 것이다. Part 2에서는 커스텀 어노테이션 유형을 작성하는 방법을 자세히 다루겠다. 자바 클래스를 만들고 이를 어노테이션 유형으로서 정의하는 방법, 컴파일러가 어노테이션 유형을 인식하게 하는 방법, 코드에 주석을 달 때 이를 사용하는 방법 등을 설명하겠다.


참고자료








Annotations in Tiger, Part 2: 커스텀 어노테이션

커스텀 어노테이션 작성하기

요약:  Part 1에서 J2SE 5.0의 새로운 메타데이터 장치인 어노테이션을 소개했고 Tiger의 기본적인 빌트인 어노테이션에 초점을 맞추었다. 커스텀 어노테이션을 작성을 지원한다는 점이 특징적이였다. 이 글에서 커스텀 어노테이션을 만드는 방법과 어노테이션에 주석을 달아 코드의 문서화와 커스터마이징을 강화하는 방법을 설명하겠다

이전글에서 메타데이터가 무엇이고, 이것이 가치 있는 이유와 J2SE 5.0 (Tiger)에 도입된 기본적인 빌트인 어노테이션을 사용하는 방법을 설명했다. 이제 이러한 개념들에 익숙해졌다면 Java 5가 제공하는 세 개의 표준 어노테이션이 특별히 강력한 것은 아니라는 것도 깨달았을 것이다. Deprecated, SuppressWarnings, Override를 사용할 수 있을 뿐이다. 다행히도, Tiger를 사용하여 자신만의 어노테이션 유형을 정의할 수 있다. 이 글에서 몇 가지 예제를 들어 비교적 간단한 프로세스를 설명하겠다. 어노테이션에 주석을 다는 방법과 이를 통한 효용을 설명한다. O'Reilly Media, Inc.에 특별히 감사한다. 이들의 도움으로 내 저서의 어노테이션 챕터에서 코드 샘플을 인용할 수 있었다. (참고자료)

자신의 어노테이션 유형 정의하기

약간의 문법의 추가로(Tiger는 많은 문법상의 구조체를 추가해왔다.) 자바는 새로운 유형인 어노테이션 유형을 지원한다. 어노테이션 유형은 일반 클래스와 비슷해보이지만 독특한 속성이 있다. 가장 주목할만한 것은 클래스에서 @(at)기호와 함께 사용하여 다른 자바 코드에 주석을 달 수 있다는 점이다.

@interface 선언

새로운 어노테이션 유형을 정의하는 것은 하나의 인터페이스를 만드는 것과 많은 부분 같다. 단 interface 키워드 앞에 @(at)기호를 붙인다는 점만 다르다. Listing 1은 가장 간단한 어노테이션 유형 예제이다:


Listing 1. 어노테이션 유형
package com.oreilly.tiger.ch06;

/**
 * Marker annotation to indicate that a method or class
 *   is still in progress.
 */
public @interface InProgress { }

Listing 1은 설명적이다. 이 어노테이션 유형을 컴파일하고 이것이 자신의 클래스경로에 있다는 것을 확인하면 자신의 소스 코드 메소드에서 이를 사용하여 메소드 또는 클래스가 여전히 실행 중이라는 것을 나타낼 수 있다. (Listing 2):


Listing 2. 커스텀 어노테이션 유형 사용하기
@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

Listing 1의 어노테이션 유형을 빌트인 어노테이션 유형을 사용했던 것과 정확히 같은 방식으로 Listing 1에서 어노테이션 유형을 사용한다. 이름과 패키지로 커스텀 어노테이션이라는 것을 표시하면 된다. 물론 일반적인 자바 규칙을 적용하여 어노테이션 유형을 반입하고 이를 @InProgress 언급하면 된다.

Part 1을 읽으셨나요?

Java 5.0의 어노테이션을 소개한 "Part 1"도 반드시 참조하기 바랍니다.

멤버 추가하기

내가 설명한 기본적인 사용법은 강력함과는 거리가 멀다. 어노테이션 유형은 멤버 변수를 가질 수 있다. (참고자료) 단순한 미가공(raw) 문서가 아닌 복잡한 메타데이터로서 어노테이션을 사용하기 시작할 때 특히 유용하다. 코드 분석 툴은 많은 정보를 가질 필요가 있고 커스텀 어노테이션은 이 정보를 제공할 수 있다.

어노테이션 유형의 데이터 멤버들은 제한된 정보를 사용하여 작동하도록 설정된다. 멤버 변수를 정의하지 않고 accessor와 mutator 메소드를 제공한다. 대신, 멤버의 이름을 딴 하나의 메소드를 정의한다. 데이터 유형은 이 메소드의 리턴 값이어야 한다. Listing 3의 구체적인 예제를 보면 보다 명확해진다:


Listing 3. 어노테이션 유형에 멤버 추가하기
package com.oreilly.tiger.ch06;

/**
 * Annotation type to indicate a task still needs to be
 *   completed.
 */
public @interface TODO {
  String value();
}

Listing 3은 이상하게 보이지만 이는 어노테이션 유형에서 필요한 것이다. Listing 3은 어노테이션 유형이 받아들일 수 있는 value 라는 스트링을 정의한다. 이제 Listing 4 처럼 어노테이션 유형을 사용한다:


Listing 4. 멤버 값과 어노테이션 유형 사용하기
@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

여기에서 트릭은 많이 사용하지 않았다. Listing 4는 com.oreilly.tiger.ch06.TODO 가 반입되었고 따라서 소스에서는 패키지 이름으로 어노테이션에 접두사를 달지 않는다. Listing 4는 속기법을 사용한다: 멤버 변수 이름을 지정하지 않고 값 ("Figure out the amount of interest per month") 을 어노테이션에 준다. Listing 4는 Listing 5와 같다. 속기법을 사용하지 않는다:


Listing 5. Listing 4의 "보통표기(Longhand)" 버전
@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
  // Need to finish this method later
}

물론 우리는 모두 코더(coder)들이기 때문에 보통표기법으로 혼란을 가중시키고 싶지 않다. 속기는 어노테이션 유형이 value라는 싱글멤버 변수를 갖고 있을 경우에만 통한다.

디폴트 값 설정하기

지금까지 시작은 좋았다. 하지만 이것을 요리할 많은 방법이 있다. 이제 다음 순서는 이 어노테이션에 디폴트 값을 설정하는 것이라고 생각할지도 모르겠다. 이 같은 경우는 사용자들이 값을 지정하도록 할 때는 좋지만 디폴트와 다를 경우에만 다른 값을 지정해야 한다. Listing 6은 이 개념과 구현을 또 다른 커스텀 어노테이션을 사용하여 설명한다. Listing 4 TODO 어노테이션 유형의 더욱 완숙한 버전이라고 할 수 있다:


Listing 6. 디폴트 값을 가진 어노테이션 유형
package com.oreilly.tiger.ch06;

public @interface GroupTODO {

  public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };

  Severity severity() default Severity.IMPORTANT;
  String item();
  String assignedTo();
  String dateAssigned();
}

Listing 6의 GroupTODO 어노테이션 유형은 새로운 여러 변수들을 추가한다. 이 어노테이션 유형은 싱글-멤버 변수가 없기 때문에 이 변수 중 하나에 value라는 이름을 붙여도 어떤 것도 얻지 못한다는 것을 기억하라. 한 개 이상의 멤버 변수를 갖게 되면 가능한 정확하게 이름을 정해야 한다. Listing 5의 속기 문법으로는 효과를 얻지 못한다. 조금 더 장황해지더라도 어노테이션 유형에 대한 보다 나은 문서를 만들도록 한다.

Listing 6의 또 다른 새로운 기능은 어노테이션 유형이 고유의 열거방식을 정의한다는 것이다. (열거(Enumeration): 일반적으로enums라고 불린다. Java 5의 새로운 기능이다. 어노테이션 유형에 있어 주목할 만한 것은 아니다.) Listing 6은 멤버 변수용 유형으로서 새로운 열거를 사용한다.

마지막으로 다시 주제로 돌아가서 디폴트 값을 보겠다. 디폴트 값을 설정하는 것은 매우 간단하다. 멤버 선언의 끝에 라는 키워드를 추가하고 디폴트 값을 주면 된다. 멤버 변수를 위해 선언했던 것과 같은 유형이다. 거듭 말하지만 이것은 로케트 과학은 아니다. 그저 어휘 트릭일 뿐이다. Listing 7은 GroupTODO 어노테이션의 실행 모습이다. severity가 표시되지 않는 경우이다:


Listing 7. 디폴트 값 사용하기
  @com.oreilly.tiger.ch06.InProgress
  @GroupTODO(
    item="Figure out the amount of interest per month",
    assignedTo="Brett McLaughlin",
    dateAssigned="08/04/2004"
  )
  public  void calculateInterest(float amount, float rate) {
    // Need to finish this method later
  }

Listing 8은 같은 어노테이션을 사용하고 있다. severity에 제공된 값을 함께 사용한다:


Listing 8. 디폴트 값 오버라이드
  @com.oreilly.tiger.ch06.InProgress
  @GroupTODO(
    severity=GroupTODO.Severity.DOCUMENTATION,
    item="Need to explain how this rather unusual method works",
    assignedTo="Jon Stevens",
    dateAssigned="07/30/2004"
  )
  public  void reallyConfusingMethod(int codePoint) {
    // Really weird code implementation
  }

어노테이션에 주석달기

어노테이션에 대한 글을 마감하기 전에 어노테이션에 주석을 다는 것을 간단히 설명하고자 한다. Part 1에서 배웠던 사전 정의된 어노테이션 유형은 사전에 정해진 목적을 갖고 있다. 하지만 자신의 어노테이션 유형을 작성하는 만큼 어노테이션 유형의 목적은 언제나 스스로 명백해지는 것은 아니다. 기본 문서에 더하여 특정 멤버 유형에 해당하는 유형을 작성할 수도 있고 또는 멤버 유형의 특정 세트에 해당하는 유형을 작성할 수도 있다. 이럴 때에는 어노테이션 유형에 일종의 메타데이터를 제공해야 한다. 이렇게 되면 컴파일러는 어노테이션이 의도한 기능을 실행할 수 있다.

물론 어노테이션이 해결책이라는 생각을 바로 해야 한다. 메타-어노테이션이라고 하는 네 개의 사전 정의된 어노테이션 유형을 사용하여 어노테이션에 주석을 단다.

목표 지정하기

가장 분명한 메타-어노테이션은 어떤 프로그램 엘리먼트가 정의된 유형의 어노테이션을 가질 것인가를 가르킬 수 있도록 하는 것이다. 이 메타-어노테이션을 Target이라고 한다. Target을 사용하는 방법을 보기 전에 또 다른 새로운 클래스인 ElementType을(실제로 enum) 알아야 한다. 이 enum은 어노테이션 유형이 목표로 하는 다양한 프로그램 엘리먼트를 정의한다. Listing 9는 ElementType을 보여준다:


Listing 9. ElementType enum
package java.lang.annotation;

public enum ElementType {
  TYPE,			// Class, interface, or enum (but not annotation)
  FIELD,		// Field (including enumerated values)
  METHOD,		// Method (does not include constructors)
  PARAMETER,		// Method parameter
  CONSTRUCTOR,		// Constructor
  LOCAL_VARIABLE,	// Local variable or catch clause
  ANNOTATION_TYPE,	// Annotation Types (meta-annotations)
  PACKAGE		// Java package
}

Listing 9의 열거된 값은 아주 분명하고 각각 어떻게 적용해야 하는지 이해할 수 있다. Target 메타-어노테이션을 사용할 때 열거된 값들 중 최소한 하나를 주고 주석이 달린 어노테이션이 목표로 할 수 있는 프로그램 엘리먼트가 무엇인지 나타낸다. Listing 10은Target의 실행 모습이다:


Listing 10. Target 메타-어노테이션 사용하기
package com.oreilly.tiger.ch06;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
 * Annotation type to indicate a task still needs to be completed
 */
@Target({ElementType.TYPE,
         ElementType.METHOD,
         ElementType.CONSTRUCTOR,
         ElementType.ANNOTATION_TYPE})
public @interface TODO {
  String value();
}

이제 자바 컴파일러는 유형, 메소드, 컨스트럭터, 기타 어노테이션 유형에 TODO를 적용한다. 이로서 어떤 누구도 어노테이션 유형을 취하거나 이를 그릇되게 적용하지 않는다는 것을 보장한다.

retention 설정하기

이제 설명할 메타-어노테이션은 Retention이다. 이 메타-어노테이션은 자바 컴파일러가 주석이 달린 어노테이션 유형을 다루는 방법과 관련되어 있다. 컴파일러는 여러 옵션들이 있다:

  • 주석이 달린 클래스의 컴파일 된 클래스 파일에 있는 어노테이션을 유지하다가 클래스가 첫 번째로 로딩될 때 이를 읽는다.
  • 컴파일 된 클래스 파일에서 어노테이션을 유지하지만 런타임 시 이를 무시한다.
  • 지시된 대로 어노테이션을 사용하지만 컴파일 된 클래스 파일에서 이를 버린다.

이 세 가지 옵션은 java.lang.annotation.RetentionPolicy에서 나타난다. (Listing 11)::


Listing 11. The RetentionPolicy enum
package java.lang.annotation;

public enum RetentionPolicy {
  SOURCE,		// Annotation is discarded by the compiler
  CLASS,		// Annotation is stored in the class file, but ignored by the VM
  RUNTIME		// Annotation is stored in the class file and read by the VM
}

Retention 메타-어노테이션 유형은 하나의 인자로서 열거된 값들(Listing 11) 중 하나를 취한다. 이 메타-어노테이션을 자신의 어노테이션으로 향하게 한다. (Listing 12):


Listing 12. Retention 메타-어노테이션 사용하기
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
  // annotation type body
}

Retention이 싱글-멤버 변수를 갖고 있기 때문에 Listing 12에서 속기를 사용한다. Retention이 RetentionPolicy.CLASS가 되도록 하려면 어떤 일을 수행하지 않아도 된다. 디폴트 작동이 있기 때문이다.

퍼블릭 문서 추가하기

다음 메타-어노테이션은 Documented이다. Documented는 마커(marker) 어노테이션이기 때문에 이해하기 쉽다. Part 1에서도 언급했지만, 마커 어노테이션은 멤버 변수가 없다. Documented는 어노테이션이 클래스용 Javadoc에 나타나야 한다는 것을 나타내고 있다. 기본적으로 어노테이션들은 Javadoc에 포함되지 않는다. 클래스에 주석을 달기위해 많은 시간을 보내야 할 때, 할 일이 나아있음을 상세히 적으로 때, 정확히 무엇을 수행하는지 기록할 때 이를 기억해두면 좋다.

Listing 13은 Documented 메타-어노테이션의 사용법이다:


Listing 13. Documented 메타-어노테이션
package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Marker annotation to indicate that a method or class
 *   is still in progress.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }

Documented의 한 가지 문제는 retention 정책이다. Listing 13은 어노테이션의 retention을 RUNTIME으로 지정한다. 이는 Documented 어노테이션 유형을 사용할 때 필요한 부분이다. Javadoc은 가상 머신을 사용하여 클래스 파일(소스 파일 아님)에서 정보를 로딩한다. VM이 클래스 파일들에서 Javadoc을 만들어내기 위한 정보를 얻는다는 것을 확인하는 유일한 방법은 RetentionPolicy.RUNTIME의 retention을 지정하는 것이다. 결과적으로 어노테이션은 컴파일 된 클래스 파일에 저장되고 VM에 의해 로딩된다; Javadoc은 이를 가져다가 클래스의 HTML 문서에 추가한다.

상속 설정하기

마지막 메타-어노테이션인 Inherited는 설명하기도 가장 복잡하고, 자주 사용되지도 않으며, 많은 혼란을 일으킨다.

먼저, 사용 케이스를 보도록 하자. 클래스가 진행중임을 표시한다고 가정한다. 물론 자신의 커스텀 InProgress 어노테이션을 사용한다. Documented 메타-어노테이션을 정확히 적용했다면 Javadoc에서 보일 것이다. 새로운 클래스를 작성하여 진행중인 클래스로 확장한다고 가정해본다. 쉬울 것 같다. 하지만 수퍼클래스도 진행 중이라는 것을 기억하라. 서브클래스를 사용하고 문서를 본다면 어떤 것도 불완전하다는 것을 인식하지 못한다. 상속 받은 서브클래스를 통해 InProgress 어노테이션이 전달되기를 기대했겠지만 그렇지 않다. 원하는 작동을 지정하기 위해 Inherited 메타 어노테이션을 사용해야 한다. (Listing 14):


Listing 14. Inherited 메타-어노테이션
package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Marker annotation to indicate that a method or class
 *   is still in progress.
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }

@Inherited의 추가로 InProgress 어노테이션이 주석이 달린 클래스의 서브클래스상에 나타난다. 물론 여러분의 모든 어노테이션 유형에 이 작동이 필요한 것은 아니다. (디폴트는 상속되지 않기 때문이다); 예를 들어 TODO 어노테이션은 광고 되지 않는다.(되어서도 안된다.) 오직 이 경우에만 Inherited가 유용할 뿐이다.

결론

이쯤 되면 모든 것을 문서화하고 주석을 달 준비가 되어있을 것이다. 모든 사람이 Javadoc을 이해할 때 어떤 일이 일어날지를 생각나게 한다. Javadoc이 혼란스러운 클래스나 메소드를 명확히 하는데 최상의 방법이라는 것을 깨닫기 전에 모든 것을 과도하게 문서화했다. 어떤 누구도 이러한 이해하기 쉬운 메소드인 getXXX() setXXX()를 보려고 하지 않을 것이다.

어노테이션의 경우도 마찬가지다. 가끔 표준 어노테이션 유형을 사용하는 것은 좋은 생각이다. 모든 Java 5 컴파일러가 이를 지원하고 작동도 이해하기 쉽다. 하지만 커스텀 어노테이션과 메타-어노테이션의 경우 힘들게 작업한 유형들이 개발 정황의 밖에서 어떤 의미를 갖고 있는지를 파악하기는 더 힘들다. 합리적으로 어노테이션을 사용하되 비웃음거리가 되지 않도록 하라. 어노테이션 장치는 가치가 있고 개발 프로세스에 도움이 된다.


참고자료











어노테이션은 자바 SE5가 제공하는 핵심적인 변화 중 하나이며, 
자바언어로 표현할 수 없지만 프로그램에서 전체적으로 표현해야 할 데이터를 기술하는 방법을 제공한다.

다시말해 어노테이션을 사용하면, 프로그램에서 컴파일러가 테스트하고, 검증해야 하는 부가정보를
정해진 형식으로 설명하는 것이 가능하다.

어노테이션의 문법은 매우 단순하게 구성되어 있으며, 대체로 언어에 @ 심볼을 추가하는 것으로 시작한다.

자바 SE5의 java.lang 은 크게 세 가지 목적의 어노테이션을 제공한다.

@Override : 기반클래스의 메소드를 오버라이드 한 것을 표시힌다. 메소드 이름을 잘못 표기하거나 시그니처를 잘못 지정할 경우 컴파일 에러가 발생한다.

@Deprecated : 해당 요소가 사용될 경우 컴파일러가 경고를 발생시킨다.

@SuppressWarning : 부적절한 컴파일러의 경고를 제거하기 위해 사용한다. 자바 SE5의 초기버전에서는 이 어노테이션을 사용할 수는 있었찌만 실제로는 지원되지 않고 무시되었다.

* 기본문법
아래 예제에서 testExecute() 메소드에 @Test를 사용했다.
이 자체로는 아무런 작동을 하지 않지만, 컴파일러는 사용자가 빌드 경로 어딘가에 @Test 어노테이션에 대한 정의를 해 놓았을 것으로 판단한다.
  1. package annotation.begin;  
  2.   
  3. public class Testable {  
  4.     public void execute(){  
  5.         System.out.println("Executing.....");  
  6.     }  
  7.     @Test void testExecute(){ execute(); }  
  8. }  


* 어노테이션의 정의
위에서 언급한 어노테이션의 정의이다. 일반적인 인터페이스의 정의와 매우 유사하다.
컴파일러는 다른 자바 인터페이스와 마찬가지로 어노테이션을 클래스로 컴파일 한다.
  1. package annotation.begin;  
  2.   
  3. import java.lang.annotation.ElementType;  
  4. import java.lang.annotation.Retention;  
  5. import java.lang.annotation.RetentionPolicy;  
  6. import java.lang.annotation.Target;  
  7.   
  8. @Target(ElementType.METHOD)  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. public @interface Test { }  

어노테이션 정의시 @Target, @Retention 이라는 메타-어노테이션이 필요함.
@Target 은 어노테이션을 적용할 대상(예를들면 메소드 또는 필드)
@Retention 은 해당 어노테이션이 소스코드(SOURCE), 클래스파일(CLASS), 또는 런타임(RUNTIME) 중 어디에서 적용되는지 정의.

위의 @Test와 같이 요소를 가지지 않은 어노테이션은 표식 어노테이션(marker annotation)이라고 불린다.

다음 글에서는 위 내용에 이어서,
프로젝트의 유스케이스를 추적하는 어노테이션 작성과정을 정리해 보겠습니다.  

1번 글에 이어서 annotation의 적용 사례로
프로젝트의 유스케이스를 추적하는 어노테이션을 작성하는 것을 정리해 보도록 하겠습니다.

 프로젝트 관리자는 구현된 유스케이스의 수를 이용하여 프로젝트의 진척도를 알 수 있으며 개발자는 시스템 내의
비즈니스 규칙을 변경하거나 디버깅할 때 유스케이스를 쉽게 찾을 수 있으므로 프로젝트의 유지보수를 쉽게 할 수 있을것입니다.

- @UseCase 선언
UseCase.java

  1. //: annotation.usecase/UseCase.java  
  2. package annotation.usecase;  
  3.   
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Retention;  
  6. import java.lang.annotation.RetentionPolicy;  
  7. import java.lang.annotation.Target;  
  8.   
  9. @Target(ElementType.METHOD)  
  10. @Retention(RetentionPolicy.RUNTIME)  
  11. public @interface UseCase {  
  12.     public int id();  
  13.     public String description() default "no description";  
  14. }  


- 다음은 클래스의 메소드에 유스케이스 정보를 기록한 예제입니다.
PasswordUtils.java

  1. //: annotation.usecase/PasswordUtils.java  
  2. package annotation.usecase;  
  3.   
  4. import java.util.List;  
  5.   
  6. public class PasswordUtils {  
  7.       
  8.     @UseCase(id = 47, description = "password must contain at least one numeric")  
  9.     public boolean validatePassword(String password){  
  10.         return (password.matches("\\w*\\d\\w*"));  
  11.     }  
  12.       
  13.     @UseCase(id = 48)  
  14.     public String encryptPassword(String password){  
  15.         return new StringBuilder(password).reverse().toString();  
  16.     }  
  17.       
  18.     @UseCase(id = 49, description = "New password can't equal previously used ones")  
  19.     public boolean checkForNewPassword(List<String> prevPasswords, String password){  
  20.         return !prevPasswords.contains(password);  
  21.     }  
  22. }  


*어노테이션 프로세스 작성하기!
어노테이션을 읽고 처리할 수 있는 프로세서가 없다면 어노테이션은 단지 주석에 불과할 뿐입니다.
그러므로 어노테이션을 사용하는 과정에서 해당 프로세서를 정의하고 적용하는 것이 중요합니다.
자바 SE5는 이러한 툴을 만들기 위한 리플렉션 API의 확정성을 제공합니다.

- 다음은 어노테이션을 적용한 PasswordUtils 클래스를 읽고 리플렉션을 사용하여 @UseCase를 검색하는 어노테이션 프로세스 입니다.
UseCaseTracker.java

  1. package annotation.usecase;  
  2.   
  3. import java.lang.reflect.Method;  
  4. import java.util.ArrayList;  
  5. import java.util.Collections;  
  6. import java.util.List;  
  7.   
  8. public class UseCaseTracker {  
  9.       
  10.     public static void trackUseCases(List<Integer> useCases, Class<?> cl){  
  11.         for(Method m : cl.getDeclaredMethods()){  
  12.             UseCase uc = m.getAnnotation(UseCase.class);  
  13.             if(uc != null){  
  14.                 System.out.println("Found Use Case :" + uc.id() + " " + uc.description());  
  15.                 useCases.remove(new Integer(uc.id()));  
  16.             }  
  17.         }  
  18.         for(int i : useCases){  
  19.             System.out.println("Warning : Missing use case-" + i);  
  20.         }  
  21.     }  
  22.     /** 
  23.      * @param args 
  24.      */  
  25.     public static void main(String[] args) {  
  26.         List<Integer> useCases = new ArrayList<Integer>();  
  27.         Collections.addAll(useCases, 47484950);  
  28.         trackUseCases(useCases, PasswordUtils.class);  
  29.     }  
  30.   
  31. }  


-실행결과

 






*어노테이션 요소

UseCase.java에 정의된 @UseCase 태그는 int 형의 id요소와 String 형의 description 요소를 가지고 있습니다.
다음은 어노테이션 요소에 허용되는 타입의 목록입니다.
- 모든 기본형 타입(int, float, boolean 등)
- String
- enum
- Annotation
- 상기 타입의 배열

->포장클래스 (Wrapper Class) 는 사용할수 없다.

자 이정도면 어느정도 annotation 에 대한 감이 잡히시는지요?
시간이 된다면 이어서 어노테이션을 이용하여 데이터베이스 생성 스크립트를 만들어주는 예제에 대해서
정리해 보도록 하겠습니다. 

출처 -  http://choija.com/204





[Java] 어노테이션(Annotation)


프로그래밍을 하면서 @Override 와 @Test와 같은 어노테이션들을 많이 사용했지만,  그 중요성이나 의미를 
깊이 있게 생각해보지는 못했었다.  단순히 컴파일러에게 알려주기 위한 표식정도로 생각했었다.  그런데  Spring Roo 와 
Spring3.0 과 같은 최신 프레임웍들의 변화 경향을 보면,  어노테이션을 적극활용하는 쪽으로 변화되고 있다.  어노테이션을
사용하여 코드의 작성량도 한결 줄어들었다고 한다.  어노테이션들의 어떤 특성을 활용한 것일까?  어노테이션이란 뭘까?
최신 프레임웍들에 변화경향을 보기에 앞서, 어노테이션에 대해서 먼저 알아보았다.



1. Annotation 이란?
Annotation은 JEE5 부터 새롭게 추가된 문법요소이다.  사전적으로는 "주석"이라는  의미를 가지고 있으며, 의미대로
자바 코드에 주석처럼 달아 특수한 의미를 부여해준다.   이 특별한 의미는 컴파일 타임 또는 런타임에 해석될 수 있다.
 

 

2.  기존 웹애플리케이션들의 문제점
기존의 자바 웹애플리케이션들은 선언적인 프로그래밍 방식을 사용한다.  선언적이란  프로그램의 전체 및 각 레이어별 구성과 설정값들을 외부의 XML 설정파일에 명시하는 방식을 의미한다.  
변경이 될 수 있는 데이터들을 최대한 코드가 아닌 외부설정파일에 분리하기 때문에 변경 요구사항이 들어왔을 때, 재컴파일 없이도 쉽게 변경사항을 적용할 수 가 있다. 
유연성이란 장점을 얻었지만,  단점 또한 존재한다. 프로그램 작성을 위해 매번 많은 설정파일을 작성해야 한다는 것이다. 
그 규모가 커질수록 설정의 양도 많아지게 되며 이를 잘 구조화 할 수 있는 방법도 필요하게 된다. 또 하나의 단점은 이것보다 좀 더 크다.  도메인 데이터 처리정보가 Model 클래스, 서비스 클래스,  XML 설정파일에 분산되어 있어서,   이를 확인하기 위해서는 Model , Service 클래스와 XML 설정파일을 모두 뒤져야 한다는 것이다. 




3. Annotation의 사용했을 때의 장점은? 
어노테이션을 사용하면 위와 같은 문제를 해결 할 수 있다.  데이터에 대한 유효성 검사조건을 어노테이션을 사용하여 Model 클래스에 직접 명시함으로써 해당 데이터들에 대한 유효조건을 쉽게 파악할수 있게되며, 코드의 양도 줄어든다.
(엄밀히 말하면,  코드의 양은 줄어들지 않는다. 하지만 코드가 깔끔해지고, 어노테이션의 재사용도 가능해진다. )



4. 그렇다면 XML 설정은 사용하지 않아야 하는가?
앞서 말했다시피, XML은 유연성을 확보해준다.   어노테이션을 사용하여 한번 빌드된 코드는 수정을 위해서는 재컴파일 해야하는 단점이 있다.  애플리케이션 전체적인 설정이나 디플로이 환경에 따라 변경되는 사항들은 XML 설정을 사용하자.  각각의 장단점을 파악하고, 언제 무엇을 사용해야할지 아는 것이 중요하다. 




그럼 이제부터 어노테이션을 사용해보자. 사용을 위해 먼저 선행지식들에 대해 잠깐 알아보자. 


5. 일반적인  어노테이션의 용도 
어노테이션은 크게 ① 문서화 ② 컴파일러 체크 ③ 코드 분석을 위한 용도로 사용된다.  문법적으로는 @기호가 붙은 심볼을 사용하며 패키지, 클래스, 메소드,  프로퍼티, 변수에 명시할 수 있다. 이 어노테이션이 붙은 소스를 컴파일 시에 수집하여 API 문서화 할수도 있지만 기존에 JavaDoc 라는 좋은 문서화 도구가 있기 때문에 "문서화" 는 가장 비중이 낮은 어노테이션의 사용법이다. 
또한 컴파일 타임에 에러를 발생시켜 주어 개발자에서 위험요소를 경고해주거나 확인하는 목적으로도 사용된다. 
가장 큰 비중을 갖는 것은 코드 분석 또는 "메타-데이터" 로서의 용도이다. 메타-데이터 란 데이터를 위한 데이터, 즉 데이터에 대해 설명하는 데이터를 의미한다.  메타데이터로서 어노테이션의 효용을 가장 잘 느낄 수 있는 부분이 JEE 설정과 유효성 검사 부분이다. 



5.  어노테이션의 분류
어노테이션은 메타데이터 저장을 위해 클래스처럼 멤버를 갖을 수 있다. 이 멤버의 개수에 따라 Marker 어노테이션, Single-value, Full 어노테이션으로 분류할 수 있다. 


①  Marker 어노테이션
     - 멤버 변수가 없으며, 단순히 표식으로서 사용되는 어노테이션이다. 컴파일러에게 어떤 의미를 전달한다. 

②  Single-value  어노테이션
    - 멤버로 단일변수만을 갖는 어노테이션이다. 단일변수 밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있다.  
 Full 어노테이션
    -멤버로 둘 이상의 변수를 갖는 어노테이션으로, 데이터를 (값=쌍)의 형태로 전달한다. 




6.  빌트인(Built-in) 어노테이션 
자바 SDK에서 지원하는 어노테이션으로 @Override, @Deprecated, @SupressWarning 등이 있다. 



@Override 
 어노테이션은 현재 메소드가 수퍼클래스의 메소드를 오버라이드한 메소드임을 컴파일러에게 명시한다. 만일 수퍼 클래스에 해당하는 메소드가 없으면 컴파일러가 인지하고 에러를 발생시켜 준다. 

@Deprecated 

 마커 어노테이션으로  차후 버전에 지원되지 않을 수 있기 때문에 더 이상 사용되지  말아야할 메소드를 나타낸다.  

 특이하게 더이상 사용되지 말아야할 메소드와 같은 라인상에 놓여져야 한다. (이유모름)

(두 플래그  -deprecated 또는 Xlint:deprecated 중 하나와 javac 명령어를 사용하여 컴파일러 경고를  켜야만, 컴파일러 에러를 발생시켜준다)


@SupressWarning
의미데로 경고를 제거하는 어노테이션이다. Object형을 엘리먼트로 하는 컬렉션을 사용하면,  컴파일러 경고가 발생하는데 이 어노테이션을 사용하여 프로그래머의 의도적인 Object 형 사용임을 알려 경고를 제거할 수 있다. 




7. 커스텀(Custom) 어노테이션 

클래스와 같이 어노테이션을 임의로 정의하여 사용할 수 있다. 어노테이션은 interface 키워드 앞에 @를 붙여 표시한다. 


1) 커스텀 어노테이션 정의하 



// 메소드와 클래스가 여전히 작업중임을 나타내기 위해 정의한 마커 어노테이션
public @interface InProgress { }


어노테이션 정의파일을 컴파일하고, 이 파일을 클래스패스에서 참조할 수 있으면 다른 소스코드상에서 어노테이션을 사용할 수 있다. 



2) 멤버 추가하기 

어노테이션 유형은 멤버변수를 가질 수 있으며, 이 변수들  컴파일시 또는 런타임에 메타-데이터로서 사용될 수 있다.  
이렇게 정의를 하면 자동으로 
accessor와 mutator를 제공해준다.


// 해당 태스크가 완료되야함 나타내는 어노테이션

public @interface TODO {

  String value();           

}


정의한 어노테이션을 다음과 같이 사용한다. 


@TODO("Figure out the amount of interest per month")

public void calculateInterest(float amount, float rate) {

  // ...

}


단일 멤버를 갖을 경우에만 위와 같이 사용할 수 있다. 



3) 디폴트 값 설정하기 
 

public @interface GroupTODO {


  public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };


  Severity severity() default Severity.IMPORTANT;

  String item();

  String assignedTo();

  String dateAssigned();

}


디폴트값을 사용한 예는 다음과 같다. 

@GroupTODO(

    item="Figure out the amount of interest per month",

    assignedTo="Brett McLaughlin",

    dateAssigned="08/04/2004"

  )

  public  void calculateInterest(float amount, float rate) {

    // ...

  }





4
) 메타-어노테이션

어노테이션에 사용되는 어노테이션으로 해당 어노테이션의 동작대상 및 보여타임을 결정한다. 


① @Target 메타-어노테이션

   어노테이션이 적용되는 동작 프로그래밍 

엘리멘트를 지정하며, 다음과 같은 값들이 있다. 

package java.lang.annotation;


public enum ElementType {

  TYPE,             // Class, Interface, enum 

  FIELD,                   // 프로퍼티 (enum 나열값들은 제외)

  METHOD,             // 메서드

  PARAMETER,       // 메서드 파라미터

  CONSTRUCTOR,          // 생성자

  LOCAL_VARIABLE,       // 로컬변수 또는 Catch문

  ANNOTATION_TYPE, // 어노테이션(메타 어노테이션)_

  PACKAGE             // 자바 패키지

}




② @Retention 메타-어노테이션 

   자바 컴파일러가 어노테이션을 다루는 방법과 관련이 있다.  소스파일, 클래스파일, 런타임 중 어느시점까지 어노테이션을 보유하고 있을 것인지를 결정한다. 


package java.lang.annotation;


public enum RetentionPolicy {

  SOURCE, // 어노테이션이 컴파일러에 의해 버려짐

  CLASS, // 어노테이션이 클래스파일에 저장되지만, JVM에게 무시됨

  RUNTIME // 어노테이션이 클래스파일에 저장되며, JVM에 의해 읽혀짐 

}

 

위 세값 중 하나를 인자로 취해 다음과 같이 사용된다. 


@Retention(RetentionPolicy.SOURCE)

public @interface SuppressWarnings {

  ...

}




Annotation을 실제로 사용하는 예제를 알아보자.  

첫번째 예제는 UseCase라는 어노테이션을 정의한다. 이 어노테이션은 id와  description이라는 값을 멤버로 갖으며 각각 기능번호와 기능에 대한 설명을 나타낸다.  Password 검사와 관련된 클래스에는 각 메소드에 UseCase 어노테이션을 사용하여 메서드들이 어떤 유스케이스를 구현하고 있는지를 표시한다. 
나중에 모든 유스케이스를 구현하는 모든 메소드들이 잘 구현되었는지 확인하기 위해  UseCaseTracker 를 사용하여 어노테이션 정보를 출력한다. (코드는 Thinking in Java 4E 에 있는 예제코드를 사용하였다. )


1. UseCase 어노테이션 정의
메서드에 사용할 어노테이션이므로 @Target을 ElementType.METHOD를 설정하였고, 
런타임 시에 사용되기 때문에 @Rention을 RetentionPolicy.RUNTIME 로 설정하여 class 파일에 어노테이션 정보가 남도록 지정하였다. 
package net.atgame.annotation.usecase;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public int id();
	public String description() default "no description";
}



2. PasswordUtil 클래스에 정의한  어노테이션을 사용한다. 
UseCase 어노테이션은 코드 작성간에는 주석의 역할을 하고,  코드 실행시에는 테스트로서의 역할도 한다.
import java.util.List;

public class PasswordUtils {
	
	@UseCase(id = 47, description = "passwords must contain at least one numeric")
	public boolean validatePassword(String password) {
		return (password.matches("\\w*\\d\\w*"));
	}
	
	@UseCase(id = 48)
	public String encryptPassword(String password) {
		return new StringBuilder(password).reverse().toString();
	}
	
	@UseCase(id = 49, description = "New passwords can't equal perviously used ones")
	public boolean checkForNewPassword(List prevPasswords, String password) {
		return !prevPasswords.contains(password);
	}
}





3.  UseCaseTracker를 사용하여 모든 유스케이스를 구현하는 메소드들이 작성되었는지 확인한다. 

아래 코드에서는 리플렉션을 사용하여 유스케이스 아이디 47, 48, 49, 50 을 사용하는 메소드들을 검색하여 그 메서드의 어노테이션 멤버를  출력하고 있다.  이 프로그램 실행을 통해 어떤 유스케이스가 아직 구현되지 않았는지 알 수 있다. 

package net.atgame.annotation.usecase;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UseCaseTracker {
	
	public static void trackUseCases(List useCases, Class cl) {
		for (Method m : cl.getDeclaredMethods()) {
			UseCase uc = m.getAnnotation(UseCase.class);
			if (uc != null) {
				System.out.println("Found Use Case:" + uc.id() + " " + uc.description());
				useCases.remove(new Integer(uc.id()));
			}
		}
		
		for (int i : useCases) {
			System.out.println("Warning: Missing use case~" + i);
		}
	}
	
	public static void main(String[] args) {
		List useCases = new ArrayList();
		Collections.addAll(useCases, 47, 48, 49, 50);
		trackUseCases(useCases, PasswordUtils.class);
	}
}

어노테이션이 어떻게 정의되고 사용되는지 보여주기 위해 위 예제를 사용하였다. 어노테이션(UserCase)을 정의를 했다면, 그 어노테이션을 처리하기 위한 클래스(UseCaseTracker)도 반드시 작성해야함을 눈여겨보자.








 이제 좀더 실용적인 두번째 예제를 소개하겠다.  데이터베이스 접속 처리를 하기위해 데이터베이스 종류에 맞는 SQL을 작성하여 DBMS에 전송해야한다.  데이터베이스에 맞는 SQL 작성! 이 상당히 코드를 지저분하게 만들수 있기 때문에 모듈화가 잘 고려되어야 한다. 데이터베이스별 SQL 작성을 안할 수는 없지만  어노테이션을 사용하면, DB 접속에 관한 부분을 클라이언트 프로그래머에게 투명하게 처리할 수 있다. 그 의미에 대해서 알아보자 


1. 테이블명을 나타내기 위한 DBTable 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
	public String name() default "";
}


2. 테이블 컬럼의 타입을 나타내기 위한 SQLInteger 와 SQLString 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
	String name() default "";
	Constraints constraints() default @Constraints;
}



3. 제약조건을 나타내기 위한 Constraint 어노테이션을 정의한다.
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}



4. Memeber 모델 클래스에 정의한 어노테이션들을 사용한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}



5. TableCreator 클래스에서 어노테이션 정보를 사용하여, 데이베이스 생성 SQL를 만들어낸다. 
package net.atgame.annotation.database;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class TableCreator {
	
	public static void main(String[] args) throws Exception {
		if (args.length < 1) {
			System.out.println("arguments: annotated classes");
			System.exit(0);
		}
		// 모든 엔티티 클래스를 읽음 
		for (String className : args) {
			Class cl = Class.forName(className);
			// 클래스 어노테이션 가져오기 
			DBTable dbTable = cl.getAnnotation(DBTable.class);
			if (dbTable == null) {
				System.out.println("No DBTable annotations in class " + className);
				continue;
			}
			String tableName = dbTable.name();
			
			if (tableName.length() < 1) {
				tableName = cl.getName().toUpperCase();
			}
			List columnDefs = new ArrayList();
			
			/// 필드목록 조회 
			for (Field field : cl.getDeclaredFields()) {
				String columnName = null;
				Annotation[] anns = field.getDeclaredAnnotations();
				if (anns.length < 1) {
					continue;
				}
				if (anns[0] instanceof SQLInteger)  {
					SQLInteger sInt = (SQLInteger)anns[0];
					if (sInt.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sInt.name();
					}
					columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
				}
				if (anns[0] instanceof SQLString) {
					SQLString sString = (SQLString)anns[0];
					if (sString.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sString.name();
					}
					columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
				}
				
			}
			StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + " (");
			for (String columnDef : columnDefs) {
				createCommand.append("\n " + columnDef + ",");
			}
			// 마지막 쉼표제거 
			String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";
			System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);
		}
		
	}
	
	private static String getConstraints(Constraints con) {
		String constraints = "";
		if (!con.allowNull()) {
			constraints += " NOT NULL";
		}
		if (con.primaryKey()) {
			constraints += " PRIMARY KEY";
		}
		if (con.unique()) {
			constraints += " UNIQUE";
		}
		return constraints;
	}
}



어노테이션을 읽어 데이터베이스 SQL을 생성하는 부분에 주목하자. 
Model 클래스 어노테이션 정보를 읽어서 DB에 해당하는 SQL를 생성하고, 테이블을 생성하게 할 수 있다.  
더 나아가 getter 와 setter 메소드를 오버라이드하여 인스턴스에 대한 조작만으로 데이터베이스 CRUD(등록,조회,수정,삭제) 연산을 처리하게 할 수도 있을 것이다. 이렇게 하면 데이터베이스 종류에 따른 번거로운 처리를 클라이언트 프로그래머에게
좀더 투명하게 사용할 수 있다. 


이상 어노테이션을 사용하는 예제를 간략히 알아보았다. 어노테이션을 잘 활용하면 간결하고, 투명한 코드를 작성할 수 있다.
 하지만 언제나 그렇듯 "잘 활용하면" 이라는 조건이 붙는다.  어떻게 잘 활용할지에 대한 생각은 이제 개인의 몫이다.



출처 -  http://hiddenviewer.tistory.com/88 








사용자가 만들어 사용하는 Custom Annotation은 어떻게 효과적으로 응용할 수 있는지 궁금할 것이다.
인터넷에 예제도 별로 없고.. 
내가 짠 것은 아니지만, Spring 프렘웍의 AOP를 이용해서 만든 커스텀 어노테이션 사용법에 대해서 정리하겠다



* AOP(Aspect Oriented Programming)이란?

먼저, OOP(Object Oriented Programming)의 경우, 객체 단위로 기능을 묶어서 코딩하는 방법이다.
그러나 실무에서는 이런 구현방법이 단점이 될 수 있다. 
사용자 인증을 항상 해야 한다고 하면, 객체마다 반복되는 코드가 생기게 될 것이다. 
 
그래서 사용자 인증 같이 자주 사용되는 코드를 단위로 묶어서 필요할 때마다 삽입하여 사용하는 것을 AOP 라고 한다.
사용자 인증하는 코드가 A라고 하고, 비지니스 부분을 B라고 한다면 
[B가 실행되기 전 또는 후에 A를 실행한다.]  
전체적으로는 위의 코드가 한 비지니스 로직에서 실행되는 것처럼 하는 것이다.




컨트롤러 타기전에 사용자 인증 하는 예제이다.


(1) 커스텀 어노테이션 설정


1. 어노테이션 인터페이스 작성


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Method)
public @interface PreAuth {
// Auth는 enum 임
Auth hasAuth() default com.x.x.Auth.VISITOR // 어노테이션 속에 값이 없다면 디폴트로 VISITOR로 지정
}


*******************************************

@Retention : 어노테이션은 주석이기 때문에 컴파일시 사라진다. 하지만 retention을 통해, 런타임때까지 주석을 남기겠다 라고 컴파일러에게 알려줌

@Target : 어디에 어노테이션을 적용할 지 타겟을 정함


retention에도 옵션이 많고, 이 어노테이션들 말고 옵션이 더 있지만 그건 찾아보면 나온당ㅋㅋ



2. Auth enum 구현


public enum Auth {
ADMIN(1), OWNER(2), USER(3), VISITER(4);
// 생략
}


*********************************************

숫자로 처리하기 위해 enum 사용.



3. 비지니스에서의 어노테이션 사용


@Controller
public class Text {
      @PreAuth(hasAuth=Auth.OWNER   
public void view() {
// 비지니스 구현
}
}



(2) AOP  


1. AOP 사용을 위한 xml 설정 (*-context.xml)


....
<aop:aspectj-autoproxy />



**********************************************

저 설정을 등록하면 @Aspect 어노테이션을 사용할 수 있다.




2. 인증 처리 구현


@Component
public class AuthManager {
// 인증 처리 구현 
}



3. (2)2번에서 구현한 로직을 (1)3 에 삽입할 시점 구현(Aspect)


@Aspect
@Component
public class AuthAspect {
@Resource(name = "authManager")
AuthManager authManager;

@Pointcut("@annotation(com.x.x.annotation.PreAuth)" // @PreAuth가 있는 지점을 pointcut으로 지정
public void pointcut() { } 

@Before("pointcut()")   // pointcut 한 부분 이전에, 인증 수행(joinpoint)
public void beforeTargetMethod(JoinPoint joinPoint) {
hasAuth(joinPoint);
}

private void hasAuth(JoinPoint joinPoint) {

// Todo: reflection을 이용하여 어노테이션 속에 있는 값을 가져온다
authManager.hasAuth(hasRole); // 위에서 받아온 값을 넘긴다. 
}
}


**********************************************

joinPoint : 메소드 호출 전/후 등의 적용 지점

pointcut : 실제 로직에 적용되는 joinpoint



출처 - http://marobiana.egloos.com/1108974


'Development > Java' 카테고리의 다른 글

DAO, DTO, VO  (2) 2012.03.16
Thread pool  (0) 2012.03.16
java 어노테이션(Annotation)  (0) 2012.03.14
JVM 인코딩 설정(Dfile.encoding 옵션)  (0) 2012.03.13
자바빈즈(javabeans)  (0) 2012.03.11
java - StringTokenizer 클래스 및 split 메서드  (0) 2012.03.09
Posted by linuxism

댓글을 달아 주세요