본문 바로가기
5.1 대외활동/Likelion(12기 세션자료)

[Eulji_LikeLion_2024_BackEnd] 5차 의존관계,웹기능설계

by Dohi._. 2024. 4. 27.
728x90

5차 목차  

  5-1 의존관계

    1) 컴포넌트 스캔과 자동 의존관계 설정

    2) 자바 코드로 직접 스프링 빈 등록하기

  5-2 회원 웹 기능

    1) 회원 웹 기능 - 홈 화면 추가

    2) 회원 웹 기능 - 등록 

    3) 회원 웹 기능 - 조회

 

5-1. 의존관계 

스프링 빈을 등록하는 방법은 대표적으로 3가지가 있습니다

  • XML로 설정
  • 컴포넌트 스캔을 이용한 자동 설정
  • 직접 코드로 스프링 빈 설정

이번 세션에서는 컴포넌트와 직접코드로 설정하는 거에 대해서 실습을 통해서 알아가보겠습니다.

 

스프링 빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다. 
빈은 인스턴스화된 객체를 의미하며, 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다. 

즉, 스프링 컨테이너가 관리하는 자바 객체를 뜻하며,  컨테이너는 하나 이상의 빈(Bean)을 관리한다.

간단하게 new 키워드 대신 사용하는 것이라고 이해하면 쉬울 것 같다.

 

가장 큰 이유는 스프링 간 객체가 의존관계를 관리하도록 하는 것에 가장 큰 목적이 있다. 

객체가 의존관계를 등록할 때 스프링 컨테이너에서 해당하는 빈을 찾고, 그 빈과 의존성을 만든다. 

 

1) 컴포넌트 스캔과 자동 의존관계 설정

목표: 회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있게 의존관계 설정

 

회원 컨트롤러에 의존관계 추가

package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {
 private final MemberService memberService;
 
 @Autowired
 public MemberController(MemberService memberService) {
 this.memberService = memberService;
 }
}


생성자에 @Autowired이 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다 이렇게 객체의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection),의존성 주입이라 한다.

이전 테스트에서는 우리가 직접주입했고

여기서는 @Autowired에 의해 스프링이 주입해준다.

 

 

@Autowired

어노테이션이 붙은 필드, 메서드, 생성자 등에 대해서 스프링 컨테이너는 자동으로 해당 타입의 빈(bean)을 찾아서 주입해줍니다. 이를 의존성 자동 주입(Dependency Injection by Autowiring)이라고 합니다.

@Autowired는 다음과 같이 사용 할수 있습니다

  1. 필드에 적용하기
  2. 생성자에 적용하기
  3. 메서드에 적용하기

의존성 자동 주입을 사용하면 코드의 의존성을 줄일 수 있어 유지보수성이 향상되고,

DI(Dependency Injection)를 적용하기 쉬워지는 등의 장점이 있습니다.

참고: 생성자에 @Autowired 를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입한다.
생성자가 1개만 있으면 @Autowired는 생략할 수 있다

 


@controller가 있으면 
스프링 컨테이너 안에 컨트롤러 객체를 만들어놓습니다

Member Controller가 생성이 될 때, Spirng bin에 등록되어 있는 Member Service 객체를 가져다가 넣어줍니다
Autowired하면 스프링 컨테이너에서 가져는데!
처음에 에러가 뜨는 경우가 있을것이다

Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.

 

memberService가 스프링 빈으로 등록되어 있지 않다

helloController는 스프링이 제공하는 컨트롤러여서 스프링 빈으로 자동 등록된다. @Controller 가 있으면 자동 등록됨


순수한 자바코드에서는 아무것도 못가져오기에
@Service를 넣어줘서 스프링이 올라올때 service를 스프링 빈으로 등록해주도록 해야한다

Controller로 통해서 외부 요청을받고
Service: 비지니스 로직만들고
Repository :데이터 저장을하고 
정형화된 패턴이다

 

컴포넌트 스캔 원리

@Component 애노테이션이 있으면 스프링 빈으로 자동 등록된다.

@Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.

@Component 를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다.

  • @Controller
  • @Service
  • @Repositor


스캔 범위

기본적으로 component  스캔의 범위는 @SpringBootApplication이 있는  SpringApplication의 패키지만을 스캔한다.

 

 

@Component 애노테이션이 있으면 스프링 빈으로 자동 등록된다.

Spring에서는 여러 가지 Annotation(애노테이션)을 사용하지만,
Bean을 등록하기 위해서는 @Component을 사용한다.
@Component이 등록되어 있는 경우에는 Spring이 애노테이션을 확인하고 자체적으로 Bean 으로 등록한다.

 

 

회원 서비스 스프링 빈 등록

@Service
public class MemberService {
 private final MemberRepository memberRepository;
 
 @Autowired
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
}

 

회원 리포지토리 스프링 빈 등록 

@Repository
public class MemoryMemberRepository implements MemberRepository {}

스프링 빈 등록 이미지예

memberService 와 memberRepository 가 스프링 컨테이너에 스프링 빈으로 등록되었다.

참고 : 스프링은 스프링컨테이너에 스프링 빈을 등록할때 기본으로 싱글톤으로 등록한다.
따라서 같은 스프링빈으면 모두 같은 인스턴스이다.
설정으로 싱글톤이 아니게 설정할 수 있지만 특별한경우를 제외하면 대부분 싱글톤을 사용한다.Singleton

 

 

2) 자바 코드로 직접 스프링 빈 등록하기

 

일단 회원 서비스와 회원리포지토리의 @service, @Repository, @Autowired 애노테이션제거  (컨트롤러는 그대로)

//@Service  (삭제)
public class MemberService {
    private final MemberRepository memberRepository;   //직접 주입하면 필드주입인데 필드 주입은 스프링이 안좋아하고 세터 주입이있는데 set으로 하는데 단점은 Public으로 열려있어야해서

    //@Autowired (삭제) 
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    /**
     * 회원가입
     */
    public Long join(Member member) {
        validateDuplicateMember(member); //중복 회원 검증 (컨알v)
        memberRepository.save(member);
        return member.getId();
    }

 

//@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;
    }

 

직접 해보기

우선 SpringConfig라는 클래스 생성 하고 @Configuration 붙여줍니다

package hello.hellospring;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository());
 }
 
 @Bean
 public MemberRepository memberRepository() {
return new MemoryMemberRepository();
 }
}
Q. 왜 컨트롤러는 왜 직접안하나 
컨트롤러는 스프링이 오차피 관리하는 아이기 때문에 그대로 쓴다.

Q지금 직접 자바코드로 빈을 설정하는지?
지금 리포지트리를 변경하려고 하는 컨셉이기때문에 컴포넌트 스캔 방식대신에 자바코드로 빈을 설정 중 입니다

 

 

자바코드로 직접 설정하지않고 
DI에는 필드주입, setter주입, 생성자 주입 이렇게 3가지 방법이 있다 
의존관계가 실행중에는 동적으로 변하는 경우가 거의 없으므로 생성자 주입을 권장한다.

생성자
처음에 생성하는 시점에만 호출하고 그이후로는 막아버린다.

필드
필드자체를 @autowired를 하는건데 중간에 바꿀수 없는 단점이 있기 때문에  권장하지 않는다.

 

새터(setter)
누군가가 호출했을때 사용하기 위한 메소드라  public으로 있어야한다.
public으로 오픈되어있어서 얻는 장점보다 단점이 많아서 잘 사용하지 않는다(DB의 일관성, 보안성)

실무에서는 주로 정형화된 컨트롤러,서비스,리포지트리같은 코드는 컴포넌트 스캔을 사용한다
*그리고 정형화되지 않거나 상황에따라 구현 클래스를 변경해야하면 설정을 통해 스프링빈으로 등록한다.*

 

주의
@Autowired를 통한 DI는 helloConroller,MemberService등과 같이 스프링이 관리하는 객체에서만 동작한다.
스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.

5-1. 회원 웹 기능

1) 회원 웹 기능 - 홈 화면 추가 

Homecontroller

@Controller
public class HomeController {
 @GetMapping("/")
 public String home() {
 return "home";
 }
}

제작후에 이제는 바로 컨트롤러에 맵핑된게 있으니까 정적.html이 안불러지고 

이제는 HomeController로인해 home.html이 불러옴

 

참고: 컨트롤러가 정적 파일보다 우선순위가 높다.

 

2) 회원 웹 기능 - 등록

 

회원 등록 폼 개발

회원 등록 폼 컨트롤러

@Controller
public class MemberController {
 private final MemberService memberService;
 @Autowired
 public MemberController(MemberService memberService) {
 this.memberService = memberService;
 }
 
 @GetMapping(value = "/members/new")
 public String createForm() {
 return "members/createMemberForm";
 }
}

회원 등록 폼 HTML ( resources/templates/members/createMemberForm )

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
 <form action="/members/new" method="post">
 <div class="form-group">
 <label for="name">이름</label>
 <input type="text" id="name" name="name" placeholder="이름을 입력하세요">
 </div>
 <button type="submit">등록</button>
 </form>
</div> <!-- /container -->
</body>
</html>

회원 등록 컨트롤러

웹 등록 화면에서 데이터를 전달 받을 폼 객체

package hello.hellospring.controller;

public class MemberForm {
	private String name; 
    
	public String getName() { 
		return name;
	}
    
	public void setName(String name) { 
		this.name = name; 
	} 
}

회원 컨트롤러에서 회원을 실제 등록하는 기능

@PostMapping(value = "/members/new") 
public String create(MemberForm form) { 
	Member member = new Member(); 
	member.setName(form.getName());
	memberService.join(member); 
	return "redirect:/";  //홈화면으로 돌려보냄
}

 

@PostMapping

스프링 프레임워크에서 제공하는 애노테이션으로,

HTTP POST 요청을 처리하는 핸들러 메서드에 적용됩니다.
@PostMapping을 사용하여 요청 URL과 POST 메서드를 매핑하면 해당 요청이 발생했을 때

스프링은 해당 메서드를 실행하여 요청을 처리합니다.

 

또다른 예시

@Controller
public class MyController {

    @PostMapping("/users")
    public String createUser(User user) {
        // POST 요청 처리 로직
        // ...
        return "redirect:/users";
    }
위의 예시에서createUser()메서드는 /users URL로의 POST 요청을 처리합니다.
요청이 발생하면 createUser() 메서드가 실행되며, User 객체를 인자로 받아 처리합니다.
메서드 내에서는 POST 요청을 처리하는 로직을 구현한 뒤, 결과에 따라 적절한 응답을 반환하게 됩니다

 

3) 회원 웹 기능 - 조회

회원 컨트롤러에서 조회 기능

@GetMapping(value = "/members") 

public String list(Model model) { 
	List members = memberService.findMembers(); 
    model.addAttribute("members", members); 
    return "members/memberList";
}

 

회원 리스트 Html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
 <div>
 <table>
 <thead>
 <tr>
 <th>#</th>
 <th>이름</th>
 </tr>
 </thead>
 <tbody>
 <tr th:each="member : ${members}">
 <td th:text="${member.id}"></td>
 <td th:text="${member.name}"></td>
 </tr>
 </tbody>
 </table>
 </div>
</div> <!-- /container -->
</body>
</html>

이 글은 을지대학교 백엔드 세션 강의를 위해 제작된 게시글입니다 
언제나 조언부탁드립니다

 

 

728x90

댓글