Spring 07 - Spring Annotations Part 1


Spring Annotations

스프링 코드를 보다 보면, 골뱅이로 시작되는 단어들이 기존 JAVA 개발할 때 쓰던 @Override 보다 다양하고 많이 사용된다. 골뱅이로 시작되는 단어들, 이를 어노테이션(Annotation)이라 한다. 사전적인 의미로는 주석이라는 뜻이지만, 자바 스프링에서 어노테이션은 코드 사이에 특별한 기능을 수행하도록 하는 기술이다. 주로 클래스와 메서드에 추가하여 다양한 기능을 부여하는 역할을 맡는다.
그럼 자바 스프링에서 사용되는 대표적인 어노테이션들을 알아본다. Part 1에서는 DI 및 환경설정과 관련된 스프링 기본 어노테이션들을 살펴본다.

@SpringBootApplication

스프링 부트 어플리케이션의 진입점이 되는 클래스에 적용되는 어노테이션이다. 처음 스프링 부트용 어플리케이션 프로젝트를 만들 때 생성되는 ~Aplication 클래스 자동으로 생성되는 어노테이션이며, 해당 어플리케이션 설정 및 컴포넌트 스캔을 간편하게 설정하도록 하는 어노테이션이다.
= “이 클래스는 어플리케이션의 시작점이다”

@SpringBootApplication
public class SimpleBoardApplication {

	public static void main(String[] args) {
		SpringApplication.run(SimpleBoardApplication.class, args);
	}

}

@SpringBootApplication을 자세히 까보면 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan이 포함되어 있다. 즉 이 어노테이션 사용하면 추후 후술할 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan도 자동으로 사용되어 진다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    ...
}

@Configuration

@Configuration을 선언한 클래스를 ‘구성 클래스’로 선언하며, 스프링 IoC 컨테이너에게 해당 클래스가 구성 클래스로 인식하도록 하는 어노테이션을 의미한다. @Configuration을 선언한 구성 클래스 내 메서드에 @Bean을 선언하여 의존성을 주입할 수 있다.
= “이 클래스는 어플리케이션의 구성 클래스로 빈 객체들의 의존성을 설정해준다”

@Configuration
public class ApplicationConfig {

    @Bean
    public ConveniencePayService conveniencePayService(){
        return new ConveniencePayService(
                new HashSet<>(Arrays.asList(moneyAdapter(), cardAdapter())),
                discountByConvenience()
        );
    }

    @Bean
    public ConveniencePayService methodPayService(){
        return new ConveniencePayService(
                new HashSet<>(Arrays.asList(moneyAdapter(), cardAdapter())),
                discountByPayMethod()
        );
    }

    ...
}

앞서 @SpringBootApplication를 설명할 때 내부적으로 @SpringBootConfiguration을 포함하고 있고 이는 @Configuration을 포함하고 있다. 또한 @Configuration 내부에 @Component로 되어 있어 추후 ComponentScan에서 @Configuration 또한 컴포넌트 스캔의 대상이 된다.

// SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

// Configuration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;

    boolean enforceUniqueMethods() default true;
}

@Bean

@Configuration을 선언한 구성 클래스 내 메서드에 선언하여 해당 메서드가 반환하는 객체를 스프링 컨테이너에 Bean으로 등록해주는 어노테이션이다.
= “이 메서드 반환값은 이제 bean 객체이며 스프링 컨테이너가 관리한다.”

@Configuration
public class ApplicationConfig {

    @Bean
    public ConveniencePayService conveniencePayService(){
        return new ConveniencePayService(
                new HashSet<>(Arrays.asList(moneyAdapter(), cardAdapter())),
                discountByConvenience()
        );
    }

    @Bean
    public ConveniencePayService methodPayService(){
        return new ConveniencePayService(
                new HashSet<>(Arrays.asList(moneyAdapter(), cardAdapter())),
                discountByPayMethod()
        );
    }

    ...
}

@Autowired

스프링에서 빈(Bean)을 의존성 주입해주는 어노테이션이다. 스프링이 이 어노테이션을 이용하여 자동으로 적절한 타입의 빈을 찾아서 자동으로 주입해준다. 주입하는 방법은 다음과 같다.

  • 생성자 주입
  • 필드 주입
  • 메서드 주입(setter)
@Service
public class MyService {

    private final MyRepository myRepository;

    // 생성자 주입
    @Autowired
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    // 필드 주입
    @Autowired
    private AnotherService anotherService;

    // 메서드 주입 (setter 주입)
    @Autowired
    public void setAdditionalService(AdditionalService additionalService) {
        this.additionalService = additionalService;
    }
}

@Component

스프링의 컴포턴트 스캔에 의해 자동으로 빈으로 등록되도록 클래스에 표시하는 어노테이션이다. 컴포넌트 붙인 클래스는 빈으로 관리가 된다. @Component가 포함된 어노테이션으론 @Service, @Repository, @Controller, @Configuration등이 있으며 위 어노테이션 또한 컴포넌트 스캔에 의해 자동으로 Bean으로 등록되어 관리된다.
= “이 클래스는 빈으로 관리된다.”

@Component
public class MyComponent {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

@ComponentScan

특정 패키지 내에서 @Component, @Service, @Repository, @Controller와 같은 어노테이션이 붙은 클래스를 자동으로 검색하고, 이를 스프링 컨테이너에 빈(Bean)으로 등록하는 데 사용하는 어노테이션이다. 이 어노테이션이 있는 클래스가 속한 패키지와 하위 패키지 또는 설정된 패키지 하위까지 스프링 컴포넌트를 검색하고 빈으로 등록한다.
= “이 클래스는 특정 패키지 내 컴포넌트를 탐색해주는 클래스다”

@Configuration
@ComponentScan(basePackages = "com.example.myapp.services")
public class AppConfig {
    // 다른 빈 설정...
}

@Controller

스프링 어플리케이션에서 웹 컨트롤러를 나타내는 어노테이션. 클라이언트 요청을 처리한 다음 응답을 반환하는 클래스. 주로 서비스 이용 후 나온 뷰를 반환하고 있다. 해당 어노테이션은 @Component를 포함하고 있으므로 Controller역할을 하는 컴포넌트라 할 수 있다.
= “이 클래스는 컨트롤러, 요청을 받아 처리된 로직의 뷰를 반환하는 컴포넌트다.”

@Controller
@RequestMapping("/boards")
public class BoardController {

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    @GetMapping
    public String list(Model model) {
        List<Board> boards = boardService.findAll();
        model.addAttribute("boards", boards);
        return "board/list";
    }

    // create
    @GetMapping("/new")
    public String createForm(Model model) {
        model.addAttribute("board", new Board());
        return "board/create";
    }

    ...
}

@Service

스프링 어플리케이션에서 서비스를 나타내는 어노테이션. 비즈니스 로직을 수행하는 역할을 맡는다. 해당 어노테이션은 @Component를 포함하고 있으므로 Service역할을 하는 컴포넌트라 할 수 있다.
= “이 클래스는 서비스, 비즈니스 로직을 처리하는 컴포넌트다.”

@Service
public class BoardService {

    private final BoardRepository boardRepository;

    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    public List<Board> findAll() {
        return boardRepository.findAll();
    }

    public Board save(Board board) {
        return boardRepository.save(board);
    }

    ...
}

@Repository

스프링 어플리케이션에서 데이터 엑세스 계층을 나타내는 어노테이션. 데이터베이스와 상호작용을 하여 DB의 CRUD 작업을 하는 역할을 맡는다. 해당 어노테이션은 @Component를 포함하고 있으므로 Repository역할을 하는 컴포넌트라 할 수 있다.
= “이 클래스는 리포지토리, 데이터베이스와 상호작용하는 컴포넌트다.”

@Repository
public class UserRepository {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, new UserRowMapper());
    }

    ...
}