본문 바로가기
CS/Web

Annotation (어노테이션)과 스프링 부트 어노테이션

by 장장 2020. 9. 5.

※ Annotation이란?

소스코드에 메타 코드를 주는 것.
컴파일 또는 런타임 시에 해석

자바 1.5버전부터 지원되는 기능

클래스, 메소드, 변수 등에 붙여서 사용

 


 

▶ Annotation 선언

interface에 @를 붙여서 선언.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation {}

 

1. @Target 

 

어노테이션이 적용되는 대상(위치)을 결정.

import java.lang.annotation.Target;

 

ElementType 요소 중에서 선택해서 지정

import java.lang.annotation.ElementType;

 

 

[어노테이션 요소 목록]

TYPE  클래스 및 인터페이스
FIELD  클래스의 멤버변수
METHOD  메서드
PARAMETER  파라미터
CONSTRUCTOR  생성자
LOCAL_VARIABLE  지역변수
ANNOTATION_TYPE  어노테이션 타입
PACKAGE  패키지
TYPE_PARAMETER  타입 파라미터
TYPE_USE 타입 사용

 

 

2. @Retention

 

어노테이션이 적용될 범위를 결정 (어떤 시점까지 영향을 미치는지)

import java.lang.annotation.Retention;

 

  • Class : 어노테이션 작성 시 기본값으로 클래스 파일에 포함되지만 JVM이 로드하지 않는다
  • Runtime : 클래스 파일에 포함되고, JVM에 의해서 참조 가능
  • Source : 컴파일때만 사용되고 사라진다. 클래스 파일에 포함 X (소스 상에서만 확인 가능 -> 주석)

 

≫ Annoation 생성 예

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String strValue();
    int intValue();
}

 

public class MyService {

    @MyAnnotation(strValue = "hello", intValue = 1111)
    public void printSomething() {
    	System.out.println("test my annotation");
    }
}

 

//MyAnnotation 값 확인

public class AnnotationApp {
    public static void main(String ar[]) throws Exception {
        //MyService 클래스에서 메소드 가져옴
        Method[] methods = Class.forName(MyService.class.getName()).getMethods();

        for(int i = 0; o < methods.length; i++) {
            //MyAnnotation이 존재하는 지 확인
            if(methods[i].isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation an = methods[i].getAnnotation(MyAnnotation.class);
                System.out.println("str:" + an.strValue());
                System.out.println("int:" + an.intValue());
            }
        }
    }
}

// 출력

str : hello

int : 1111

 


 

 

▶ 스프링 부트 어노테이션

부트에서 내부적으로 사용되는 어노테이션

 

1. ImportSelector

 

어노테이션의 값에 따라서 설정 클래스의 로딩 여부가 결정.

 

 

msg필드를 가지고 있는 MyBean 클래스를 사용하는 UseMyBean 클래스

public class UseMyBean {
    @Autowired
    private MyBean myBean;
    public void printMsg() {
    	System.out.println(myBean.getMsg());
    }
}

 

myBean을 Autowired로 의존성 주입 해주기 위해 myBean을 bean으로 등록해주어야 함.

 

MyBean 클래스를 빈으로 등록해주는 설정 클래스 2가지

@Configuration
public class AConfig {
    @Bean
    MyBean myBean() {
    	return new MyBean("from Aconfig");
    }
}
@Configuration
public class BConfig {
    @Bean
    MyBean myBean() {
    	return new MyBean("from Bconfig");
    }
}

 

어노테이션 값에 따라서 AConfig/BConfig 선택하는 MyImportSelector

 

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    	AnnotationAttributes attr = AnnotationAttributes.fromMap(
        	importingClassMetadata.getAnnotationAttributes(EnableAutoMyModule.class.getName(), false));
            
       String value = attr.getString("value");
       
       //EnableAutoMyModule 어노테이션 값이 a면 AConfig / 아니면 BConfig
       if("a".equals(value))
       		return new String[]{AConfig.class.getName()};
       else
       		return new String[]{BConfig.class.getName()};
    }
 }

 

ImportSelect 인터페이스를 상속받아 selectImports 메서드를 구현한 클래스

@EnableAutoMyModule 어노테이션 값에 따라 클래스 이름 리턴.

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableAutoMyModule {
    String value() default "";
}
@Configuration
@EnableAutoMyModule("a")
public class MainConfig {
    @Bean
    public UseMyBean useMyBean() {
        return new UseMyBean();
    }
}

 

@EnableAutoMyModule 어노테이션에 a를 넣어줘서 AConfig가 로드되도록!

 

//확인

public class ImportSelectApp {
    public static void main(String ar[]) {
	    ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        UseMyBean bean = context.getBean(UseMyBean.class);
        bean.printMsg();
    }
}

 

// 출력

from AConfig

 

 

2. @Conditional

 

조건에 따라 자바 설정 클래스를 선택할 수 있게 해주는 어노테이션.

개발 환경, 운영 환경에 따라서 다르게 해줘야 하는 부분에 많이 사용.

 

public interface MsgBean {
    default void printMsg() {
        System.out.println("My Bean default is running");
    }
}

자바 8부터 인터페이스에 default 키워드를 이용해 몸체가 있는 메서드 작성 가능.

 

환경 변수값(env)으로 입력된 값이 sitea / siteb 와 같은지 체크하는 클래스 2개

public class SiteAConfigCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "sitea".equals(context.getEnvironment().getProperty("env", "sitea"));
    }
}
public class SiteBConfigCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "siteb".equals(context.getEnvironment().getProperty("env", "siteb"));
    }
}

 

sitea, siteb 조건들을 @Conditional 어노테이션 값으로 사용하는 SiteABean / SiteBBean

 

//SiteAConfigCondition의 matches 메소드 반환값이 true면 동작

@Component
@Conditional(SiteAConfigCondition.class)
public class SiteABean implements MsgBean {
    @Override
    public void printMsg() {
        System.out.printf("site a is working);
    }
}
//SiteBConfigCondition의 matches 메소드 반환값이 true면 동작

@Component
@Conditional(SiteBConfigCondition.class)
public class SiteBBean implements MsgBean {
    @Override
    public void printMsg() {
        System.out.printf("site b is working);
    }
}

 

테스트

@Configuration
public class ConditionalMainConfig {
    @Autowired
    MsgBean msgBean;
}
public class ConditionApp {
    public static void main(String ar[]) {
        Package pack = ConditionApp.class.getPackage();
        AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext();
        context.scan(pack.getName());
        context.refresh();
        MsgBean bean = context.getBean(MsgBean.class);
        bean.printMsg();
    }
}

같은 타입을 가지고 있는 클래스들이 스프링이 관리해야 할 대상이 되어야 하므로 스캔을 하고 MsgBean을 context에 등록한다.

 

실행 전에 env에서 값을 sitea(혹은 siteb) 로 전달한 후 실행하면 

 

출력

site a is working  //sitea로 설정했을 경우

 

 

3. @AutoConfigurationPackage 

 

스프링 부트로 만든 애플리케이션 내에서 @AutoConfigurationPackage 어노테이션이 선언된 패키지 경로를 스프링 콘텍스트가 스캔 가능하도록!

 

@EnableAutoConfiguration 어노테이션이 @AutoConfigurationPackage를 포함한다.

 

ConfigurableApplicationContext c = app.run(args);

AutoConfigurationPackages.get(c);

 

위 소스로 스프링 부트 실행 시에 인식된 패키지 정보를 알 수 있다.

 

 

4. @EnableConfigurationProperties 

 

프로퍼티들을 그룹화해서 사용하는 것.

//application.properties

spring.main.web-environment=false
myapp.server-ip=192.168.34.56
myapp.name=My Config App
myapp.description=This is an example

 

myapp로 시작되는 프로퍼티들을 사용하려면, prefix를 사용해서 참조

 

@ConfigurationProperties(prefix="myapp")
public class MyProperties {
    private String name;
    private String description;
    private String serverIp;

    --- getter, setter ---
}

 

@SpringBootApplication
@EnableConfigurationProperties({MyProperties.class})
public class PropApp {
    public static void main(String ar[]) {
        SpringApplication.run(PropApp.class, ar);
    }

    @Autowired
    MyProperties prop;
    
    @Bean
    CommandLineRunner values() {
        return ar -> {
            System.out.println("server ip:" + prop.getServerIp());
        };
    }
}

댓글