TIL - 0615

@Annotation

어노테이션

  • 자바 1.5부터 등장한 API
  • 코드에 메타 데이터를 설정 : 자신에 대한 부가 정보를 담고 있음
@Transactional(rollbackFor = CannotDeleteException.class)
public void deleteQuestion(User loginUser, Long id) throws CannotDeleteException {
    deleteHistoryService.saveAll(findQuestionById(id).delete(loginUser));
}

이렇게 사용된다

  1. 컴파일러에 정보 제공 : @FunctionalInterface, @SuppressWarnings

    이렇게 사용된다

  2. 코드 생성 : 롬복 @Setter, @Getter 등
  3. 런타임 프로세싱(조작) : Spring의 @Autowired(DI), JUnit의 @Test(테스트 할 메소드), Jackson의 @JsonIgnore(json 생성 시 참고) 등

종류

  1. 빌트인 어노테이션 : 제공되는 어노테이션
    • @Override : 상위 클래스의 메소드를 오버라이드 했음을 컴파일러에게 알려줌, 제대로 오버라이드 하지 않았을 때 컴파일러가 컴파일 fail
    • @Deprecated : 해당 코드는 오래된 방법으로 곧 삭제될 코드 임을 표시하는 메타데이터, 컴파일러가 알리도록 설정함
    • @SuppressWarnings : 개발자 의도에 의해 실행되는 흐름이지만 컴파일러가 경고를 띄울 때가 있는데, 띄우지 않도록 알려줌
  2. 메타 어노테이션 : 어노테이션을 만들 때 필요한 메타데이터를 설정하는 어노테이션
    • @Target : 어노테이션이 적용될 코드
    • @Retention : 어노테이션의 스코프(메타데이터의 생명주기)
    • @Inherited : 해당 어노테이션이 적용된 클래스의 하위 클래스일 경우 똑같이 어노테이션이 적용됨
  3. 커스텀 어노테이션 : 만든 어노테이션, 보통 어노테이션을 만들 일은 거의 없고 프레임워크 개발자들이 만들면 우리는 쓰면 됨
    • @Transactional
    • @Autowired
    • @JsonIgnore
    • @Column

정의하면서 기본 구성보기

/* 어노테이션 정의 */
@Target(ElementType.TYPE)  // @Target({METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
    String company = "codesquad";

    String name() default "colin";
}

/* 어노테이션 사용 */
@Author(name = "jinbro")
public class Book {
    
}

/* 어노테이션 설정된 값 가져오기(리플렉션 API) */
public class BookTest {
    @Test
    public void read() throws Exception {
        /* Annotation[] annotations = Book.class.getAnnotations(); */
        
        Class<?> clazz = Book.class;
        assertTrue(clazz.isAnnotationPresent(Author.class));

        Annotation annotation = clazz.getAnnotation(Author.class);
        System.out.println(annotation);

        System.out.println(Author.company);
    }
}
  • @interface : 어노테이션 정의임을 알리는 선언, 특별한 인터페이스(메소드 선언만 해두고 구현은 하지않음)
    • 선언한 메소드의 리턴값 : 기본값을 설정해두거나 기본값을 설정하지않을 경우 어노테이션 사용하는 쪽에서 어노테이션 옆 (name = 값) 형태로 지정해줘야함
  • @Target : 어노테이션이 적용될 수 있는 대상, 타겟은 복수개 설정 가능(중괄호 내에 콤마로 구분해서 타입을 여러개 설정할 수 있음 {TYPE1, TYPE2} )
    • RetentionPolicy.TYPE : 타입 선언 시
    • RetentionPolicy.METHOD : 메소드
    • RetentionPolicy.CONSTRUCTOR : 생성자 외 몇가지 더 있음
  • @Retention : 어노테이션이 언제까지 유효한지 설정(하나만 설정가능)
    • ElementType.SOURCE : 컴파일 시에 사용되고, 바이트 코드로 변환되지않고 코드 상에서 삭제됨
    • ElementType.CLASS : 바이트 코드 변환은 되지만 런타임에는 사용할 수 없음(있는지만 표시)
    • ElementType.RUNTIME : 실행 시 어노테이션 정보가 jvm에 의해 사용 - 리플렉션 API
  • String company : 메타데이터의 고정 설정값, 사용하는 쪽 모두가 공통적으로 가지는 값
  • String name() default "colin" : 메타데이터 설정값의 변수 역할, 사용하는 쪽에서 유동적으로 설정할 수 있음, 기본값 설정 가능

어노테이션 in 스프링(스프링빈 등록), 어노테이션 이외 방법

어노테이션만으로 스프링빈 등록, 관리하기

@Controller
public class UserController { 
     
     @Autowired
     private UserService userService;
}
  • 대략적인 흐름

  • 스프링 실행 시 빈 스캔을 하고 시작함 : 스프링빈으로 메타데이터(stereotype 어노테이션) 설정된 클래스의 인스턴스 생성 후 컨테이너에서 관리
  • 단점 : 스캔 범위가 넓어지면 오래걸림(기본적으로 메인 클래스의 하위 패키지 전체 스캔 - 메인클래스 @SpringBootApplication 설정값으로 스캔 패키지 커스텀)

.xml 사용

<bean id="userController" class="com.example.web.UserController">
    <property name="userService" ref="userService" />
</bean>

<bean id="userService" class="com.example.serivce.UserService">
   <property name="name" value="xmlBean" />
</bean>
  • 어노테이션은 스캐닝 해서 찾아야하지만, xml은 직접적으로 어디에 메타정보를 주입해줘야할지 설정이 되어있기 때문에 바로 찾기 가능
  • 단점이라고 생각되는 부분 : xml의 크기가 커짐(유지보수가 어려울 것이라 생각됨 - 자바 코드 변경되면 xml도 변경), 빈의 특성 구분없이 bean 하나로 퉁침

java config 파일 + 어노테이션 사용

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Bean
  public MessageSource messageSource() {
      ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
      messageSource.setBasename("classpath:messages");
      messageSource.setDefaultEncoding("UTF-8");
      messageSource.setCacheSeconds(30);
      return messageSource;
  }
}
  • 직접 인스턴스를 생성해서 컨테이너에 등록해야만 할 때 사용 : 주로 외부 라이브러리 수동으로 함(스프링빈 설정이 되어있지않음)
  • 단점이라고 생각되는 부분 : bean 태그를 @Bean로 만든 것 - 인스턴스 생성을 수동으로 해줘야함

어노테이션 in Lombok

어노테이션을 활용한 코드 생성 자동화

  • 컴파일 타임에 어노테이션에 따라 코드를 생성함 : 컴파일러가 해당 자바 파일을 .class로 변환할 때 어노테이션 프로세서가 처리함

사용 설정하기

  • 인텔리j 사용할 경우 : Lombok 플러그인 설치
  • 컴파일러가 해당 어노테이션을 만났을 때 어노테이션 프로세서을 동작시킬 수 있도록 허용해줘야함 : Compiler > Annotation Processors
  • 롬복 디펜던시 설정 : 컴파일 타임에만 필요하므로 디펜던시 스코프를 complieOnly로 설정(gradle 기준)
dependencies {
    compileOnly('org.projectlombok:lombok')
}

소스 파일(.java)과 컴파일 파일(.class) 비교해보기

/* .java */
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class User {

    String name;
    Question question;
}

/* .class */ 
public class User {
    Question question;
    String name;

    public User() {
    }

    public User(Question question, String name) {
        this.question = question;
        this.name = name;
    }

    public void setQuestion(Question question) {
        this.question = question;
    }

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

    public Question getQuestion() {
        return this.question;
    }

    public String getName() {
        return this.name;
    }

    public String toString() {
        return "User(question=" + this.getQuestion() + ", name=" + this.getName() + ")";
    }
}

사용할 때 생각해야할 점

  1. 사용 환경이 갖춰져야 사용할 수 있음 : 같은 코드지만, 사용 환경이 갖춰지지 않은 곳에서는 컴파일 실행 시킬 수 없음(에러 발생)
  2. @toString 사용할 때 : 해당 클래스의 모든 속성을 가지고 작성 - 서로 연관된 객체라면(속성에 서로가 서로를 가진다면)?
    • 객체 호출하면 객체의 toString() 호출 -> 연관 객체의 toString() 실행 시 또다시 연관된 toString() 호출 : 스택오버플로우 발생