Split View: Spring Boot service, repository bean 등록 방법
Spring Boot service, repository bean 등록 방법
일반적인 웹 애플리케이션(Backend)은 5가지 구성요소로 구분된다.
- 컨트롤러: 웹 MVC의 컨트롤러 역할
- 서비스: 핵심 비즈니스 로직 구현
- 리포지토리: 데이터베이스의 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인: 비즈니스 도메인 객체
- DB: 실제 데이터를 저장하는 별도의 프로세스(Spring의 범위 밖)
Spring에서는 서비스나, 리포지토리와 같은 요소를 Bean이라는 객체로 관리하여, 필요한 곳 필요한 시점에 자동으로 주입해준다.(이를 Dependency Injection 라고 한다)
해당 요소들을 Spring에서 관리하게 하는 이유에 대해서는 다음 포스팅에서 자세히 다뤄보겠다. Spring에서 Bean으로 관리되도록 하기 위해서 필요한 최소한의 Annotation은 @Component 이지만, 일반적으로service의 경우 @Service를 아래처럼 달아준다.(@Service 어노테이션은 내부적으로 @Component 를 포함하고 있다.)이는 Component Scan 방식을 이용한 Bean 등록방식이고, annotation이 아닌 Java Code로 명시적으로 Spring Bean을 등록하는 방법도 있다.
Component Scan 방식으로 자동 의존관계 설정
MemberService 등록
예를들어 MemberService라는 서비스를 등록한다면, 아래처럼 @Service annotation을 붙여준다. @AutoWired를 생성자에 붙여준 이유는 Spring에서 관리하는 Bean중 memberRepository를 자동으로 주입하게끔 하기 위함이다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/*
회원 가입
*/
public Long join(Member member){
// 같은 이름이 있는 중복 회원 X
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m ->{
throw new IllegalStateException("이미 존재하는 회원입니다");
});
}
/*
전체 회원 조회
*/
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
MemoryMemberRepository 등록
Repository의 경우 @Repository annotation을 붙여준다.
@Repository
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter((member) -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
@Repository Annotation 또한 @Component를 가지고 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
Note 해당 어노테이션들은 모든 패키지에서 동작하는 것은 아니고,
@SpringBootApplication어노테이션이 붙어있는 패키지와 그 하위 패키지들에만 적용 된다. Spring Bean은 싱글톤(SingleTon)으로 생성되며, 1개만 생성되어 관리된다.
Java 코드로 Spring Bean 등록
@service, @Repository, @AutoWired 어노테이션을 사용하지 않고, Spring Bean을 등록하는 방법을 알아보겠습니다.
기존에 Bean 주입을 위해서 달아두었던 위의 3개의 어노테이션을 지우고 SpringConfig.java라는 파일을 생성하여 아래와 같이 작성해줍니다.
@Configuration 어노테이션을 통해 Spring과 관련된 파일임을 등록하고 @Bean어노테이션을 붙여 Bean에 등록할 객체들을 생성하여 Return하는 함수를 작성합니다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
DI에는 생성자 주입, 필드 주입, 세터주입 3가지 방식이 있다. 필드주입의 경우, 생성할 때를 제외해서는 필드를 변경할 수 없다는 단점이 있고, 세터 주입의 경우 setter가 Public으로 열려있어야 하기 떄문에 노출이 된다는 점에서 좋지 않다. 제일 권장되는 방식은 생성자를 통해서 주입되는 방식이다. 실무에서는 컴포넌트 스캔 방식으로 개발이 진행되지만, 상황에 따라 구현클래스를 다른 Bean으로 변경해야 할 경우 기존 코드를 건드리지 않고 변경하기 위해서 Java code로 Bean을 설정하는 방식도 사용된다.
References
- 김영한님의 인프런 강의: 스프링-입문-스프링부트
How to Register Spring Boot Service and Repository Beans
A typical web application (backend) consists of five components:
- Controller: Handles the controller role in web MVC
- Service: Implements core business logic
- Repository: Accesses the database, stores and manages domain objects in the DB
- Domain: Business domain objects
- DB: A separate process that stores actual data (outside the scope of Spring)
In Spring, components such as services and repositories are managed as objects called Beans, which are automatically injected where and when needed. (This is called Dependency Injection.)
The reasons for having Spring manage these components will be covered in detail in the next post. The minimum annotation needed to have Spring manage something as a Bean is @Component, but for services, the @Service annotation is typically used as shown below. (The @Service annotation internally includes @Component.) This is a Bean registration method using Component Scan. There is also a way to explicitly register Spring Beans using Java code instead of annotations.
Setting Up Automatic Dependency Injection with Component Scan
Registering MemberService
For example, to register a service called MemberService, add the @Service annotation as shown below. The reason @Autowired is added to the constructor is to have Spring automatically inject the memberRepository Bean that it manages.
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/*
Join membership
*/
public Long join(Member member){
// No duplicate members with the same name
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m ->{
throw new IllegalStateException("A member with this name already exists");
});
}
/*
Look up all members
*/
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
Registering MemoryMemberRepository
For a repository, add the @Repository annotation.
@Repository
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter((member) -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
The @Repository annotation also contains @Component.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
Note These annotations do not work in all packages -- they only apply to the package containing the
@SpringBootApplicationannotation and its sub-packages. Spring Beans are created as singletons, meaning only one instance is created and managed.
Registering Spring Beans with Java Code
Let's learn how to register Spring Beans without using the @Service, @Repository, and @Autowired annotations.
Remove the three annotations mentioned above that were used for Bean injection, and create a file called SpringConfig.java with the following content.
Use the @Configuration annotation to register the file as Spring-related, and write functions with the @Bean annotation that create and return the objects to be registered as Beans.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
There are three types of DI: constructor injection, field injection, and setter injection. Field injection has the disadvantage that fields cannot be changed after creation. Setter injection is undesirable because the setter must be public, which exposes it. The most recommended approach is injection through the constructor. In practice, development is typically done using the component scan approach, but in situations where the implementation class needs to be swapped with a different Bean, the Java code-based Bean configuration method is also used to make changes without modifying the existing code.
References
- Kim Young-han's Inflearn course: Spring Introduction - Spring Boot
Quiz
Q1: What is the main topic covered in "How to Register Spring Boot Service and Repository
Beans"?
Learn how to register service and repository beans in Spring Boot.
Q2: What is Registering MemberService?
For example, to register a service called MemberService, add the @Service annotation as shown
below. The reason @Autowired is added to the constructor is to have Spring automatically inject
the memberRepository Bean that it manages.
Q3: Explain the core concept of Registering MemoryMemberRepository.
For a repository, add the @Repository annotation. The @Repository annotation also contains
@Component. Registering Spring Beans with Java Code Let's learn how to register Spring Beans
without using the @Service, @Repository, and @Autowired annotations.