Java Spring Boot 003-3 | Spring Stereotypes

Yunny.Log ·2022년 2월 11일
0

Spring Boot

목록 보기
17/80
post-thumbnail

Spring Stereotypes

아래의 이미지,설명 출처 블로그

StereoType이란?
스프링 컨테이너가 스프링 관리 컴포넌트로 식별하게 해주는 단순한 마커다. 즉, scan-auto-detection과 dependency injection을 사용하기 위해서 사용되는 가장 기본 어노테이션이다.
Stereo Type이 범용적으로 많이 사용하게 된 시키는 Spring 2.5 부터 였다. 그 이전까지는 xml 파일에 bean을 등록하여 관리 하였다. 그러나 모든 bean들을 xml 파일로 관리 하다보니 다른 보일러플레이트 코드와 함께 xml 파일만 비대해지게 될 뿐이었다. 그래서 Spring 2.0 에서는 @Repository이 등장하였고 Spring 2.5 부터는 @Component, @Controller, @Service, @Configuration 등이 등장하면서 Stereo type에 대한 정의가 범용적으로 사용하게 되었다.

  • @Component
    빈으로 간주되어 DI 컨테이너에서 사용할 수 있게 하는 기본 어노테이션이다
  • @Bean@Component는 어떤 차이가 있는가?
    @Component는 클래스 상단에 적으며 그 default로 클래스 이름이 bean의 이름이 된다. 또한 spring에서 자동으로 찾고(@ComponentScan 사용) 관리해주는 bean이다.
    @Bean은 @Configuration으로 선언된 클래스 내에 있는 메소드를 정의할 때 사용한다. 이 메소드가 반환하는 객체가 bean이 되며 default로 메소드 이름이 bean의 이름이 된다.
  • @Controller
    Dispatcher Sevlet에서 제공되며 여기서 @RequestMapping 어노테이션을 사용하여 클라이언트 요청을 특정 컨트롤러에 전달할 수 있게 해준다
  • @Service
    Service annotation은 단순히 서비스 레이어에서 사용하는 bean이라는 것을 구분하기 위한 용도로 정의한다.
  • @Repository
    검사 되지 않은 예외(DAO 메소드에서 발생)를 Spring DataAccessException으로 변환 할 수 있게 해준다.
  • @Configuration
    한 개 이상의 @Bean 어노테이션으로 정의한 bean 들을 생성하려 할 때 클래스에 정의하는 어노테이션이다.
  • @Bean은 언제 사용하는게 좋을까?
    개발자가 직접 제어가 불가능한 라이브러리를 활용할 때 사용
    초기에 설정을 하기 위해 활용할 때 사용

  • ioc 컨테이너가 만들어놓은 코드에서 빈을 검색해서 필요한 순간순간에 제공하는 것 => 스프링프레임워크의 핵심 기능

  • 이때 이 빈들을 함수, 클래스 단위로 정의가능

  • 클래스 단위로 정의하기 위한 것들이 구현돼있는 패키지 이름이 Spring Stereotypes

Component란?

0. @SpringBootConfiguration

@EnableAutoConfiguration
@ComponentScan

  • spring initializer로 만들어진 main함수 보게 되면 아래와 같음
@SpringBootApplication
public class CrudApplication {

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

}

-> 이때 @SpringBootApplication 이라는 아이는 아래와 같은 설명을 가짐
![]

-> 이를 더 자세히 살펴본다면 아래와 같은데 이때 빨간색 밑줄 친 아이 두개를 같이 사용하게 된다면

-> 여러 패키지가 존재하는데 이 중 어떤 패키지를 사용을 해서, 걔네들을 ioc 컨테이너에 등록할 지 여부 지정해주는 annotation

-> 다만 스프링 부트에서는 마이크로 서비스 아키텍처, 도커 이미지화에 더 유리해서 굳이 검색하지 않을 패키지를 콤포넌트해줄 이유가 없어서 딱히 하지 않을 것

Component Scan : 안에 패키지를 검색할 수 있도록 지정해주는 것이다.


1. public @interface Component 살피기

-> 이 친구는 controller의 상위

-> stereotype (package org.springframework.stereotype;
)패키지 안에 자리잡고 있는 녀석이다

  • 우리가 사용했던 Controller들도 사실상 Component의 일종이라고 볼 수 있는 것

  • 또한 컨트롤러말고도 다른 여러가지 만들기 가능

Component가 붙은 클래스들을 검색해서 얘를 스프링컨테이너에 등록해주는 것이 Compoenet 어노테이션의 역할, 스프링 컨테이너의 관리 하에 있게 해준다

(+) Restcontroller

  • 위와 같이 Controller와 ResponseBody 어노테이션을 가지고 있다
  • 그래서 RestController 사용할 땐 굳이 ResponseBody 안넣어도 되는 것!!

=> 어플리케이션에서 어떤애가 무슨 역할 하는지는 모르지만 스프링 관리 하에 두고싶다고 한다면 component interface
=> request endpoint를 만들고 싶을 땐 controller interface


3. Spring 부트에서 Component는??

  • ComponentScan을 이용해 사용할 Bean의 범위를 지정해준다
    => 함수 단위 : @Bean
    => 클래스 단위 : @Component
  • 위의 단위들을 붙여주게 된다면 Spring Ioc 컨테이너가 이 아이들을 관리해주게 되는 것

4. Component의 아이들 (Controller, Repository, Service)

  • 물론 Controller, Repository, Service 다 컴포넌트의 일종이기 때문에 모든 Bean에 Component 사용해도 작동 가능 ㅇㅇ 그래도 위와 같이 세분화 시켜서 나눠준다!

Service, Repository 사용하기

1) PostRepository Interface 선언

package dev.dongyun.crud.post;

import java.util.List;

public interface iPostRepository {
    boolean save(PostDto dto);
    PostDto findById(int id); //id를 주게 되면 PostDto가 돌아가게 된다
    List<PostDto> findAll();
    boolean update(PostDto dto);
    boolean delete(int id);
}

2) PostinMemoryRepository

package dev.dongyun.crud.post;
//포스트 메모리지만 메모리 뒤에 구현된다
public class PostinMemoryRepository implements iPostRepository{
}
  • 우선 이렇게 하면 에러가 난다

  • 이 implement methods를 통해 메소드를 만들어주기

package dev.dongyun.crud.post;

import java.util.List;

//포스트 메모리지만 메모리 뒤에 구현된다
public class PostinMemoryRepository implements iPostRepository{
    @Override
    public boolean save(PostDto dto) {
        return false;
    }

    @Override
    public PostDto findById(int id) {
        return null;
    }

    @Override
    public List<PostDto> findAll() {
        return null;
    }

    @Override
    public boolean update(PostDto dto) {
        return false;
    }

    @Override
    public boolean delete(int id) {
        return false;
    }
}
  • 그리고 이제 @Repository annotaion 붙이고 crud 함수들 선언
package dev.dongyun.crud.post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
@Repository
//포스트 메모리지만 메모리 뒤에 구현된다
public class PostinMemoryRepository implements iPostRepository{

    private static final Logger logger = LoggerFactory.getLogger(PostinMemoryRepository.class);
    private final List<PostDto>postList;

    public PostinMemoryRepository(){
        this.postList = new ArrayList<>();
    }


    @Override
    public boolean save(PostDto dto) {
        return this.postList.add(dto);
        //-> add가 애초에 boolean값을 돌려준
        //return true;
    }

    @Override
    public PostDto findById(int id) {
        return this.postList.get(id);
    }

    @Override
    public List<PostDto> findAll() {
        return this.postList;
    }

    @Override
    public boolean update(int id, PostDto dto) {
            PostDto targetPost = this.postList.get(id); //업데이트를 위한 목적 타겟
            if (dto.getTitle()!=null){
                targetPost.setTitle(dto.getTitle());
            }
            if (dto.getContent()!=null){
                targetPost.setContent(dto.getContent());
            }
            this.postList.set(id, targetPost);

        return true;
    }
//return 값은 성공 여부 알려주는 것
    @Override
    public boolean delete(int id) {
        this.postList.remove(id);
        return true;
    }
}

3) PostService 만들기

1) postservice 인터페이스 구현

레포지토리랑 서비스
데이터를 회수해서 그것을 확인하는 것 -> 레포지토리 (데이터 회수)
회수된 데이터에서 현재 요청을 보내는 사용자의 권한 여부 체크 -> 서비스
(+) 서비스는 실제 데이터 사용 전 검증 거칠 때 used

(+)아래 설명 출처 블로그

  • @Contoller 어노테이션을 붙이면 핸들러가 스캔할 수 있는 빈(Bean) 객체가 되어 서블릿용 컨테이너에 생성됩니다. 마찬가지로 @Repository, @service 어노테이션은 해당 클래스를 루트 컨테이너에 빈(Bean) 객체로 생성해주는 어노테이션입니다.
  • 둘 다 Bean 객체를 생성해주고 딱히 다른 기능을 넣어주는게 아니라서 뭘 써도 상관 없긴한데 명시적으로 구분해주기 위해 각자 분리해서 사용합니다. 부모 어노테이션인 @Component를 붙여줘도 똑같이 루트 컨테이너에 생성되지만 가시성이 떨어지기 때문에 잘 사용하지 않습니다.
  • 참고로 객체 내에서 데이터 변경 작업이 있는 VO(DTO) 객체와 같은 경우는 동기화 문제로 인해 Bean 객체로 사용하지 않습니다. Bean 객체는 항상 데이터 변경이 없는 객체에 한해 사용하는 점에 유의해야 합니다.
  • 컨트롤러 : @Controller (프레젠테이션 레이어, 웹 요청과 응답을 처리함)
  • 로직 처리 : @Service (서비스 레이어, 내부에서 자바 로직을 처리함)
  • 외부I/O 처리 : @Repository (퍼시스턴스 레이어, DB나 파일같은 외부 I/O 작업을 처리함)
package dev.d~n.crud.post;

import java.util.List;

public interface PostService {
    void createPost(PostDto dto);
    List<PostDto> readPostAll();
    PostDto readPost(int id);
    void updatePost(int id, PostDto dto);
    void deletePost(int id);
}

2) postservice 인터페이스 사용할 postservicesimple 클래스 만듦

package dev.dongyun.crud.post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class PostServiceimple implements PostService {
    private static final Logger logger = LoggerFactory.getLogger(PostServiceimple.class);

    private final iPostRepository postRepository;
    //레포지토리 in이 아닌
    //우리는 얘 (인터페이스) 이후, 뒤의 구현이 어떻게 되어있는지 상관않겠다는 뜻

   public PostServiceimple(iPostRepository postRepository){
//여기에 autowired annotation을 붙여줘서 레포지토리 중 알맞은 레포지토리 찾게 해줘야  함
       this.postRepository = postRepository;
   }

    @Override
    public void createPost(PostDto dto) {

    }

    @Override
    public List<PostDto> readPostAll() {
        return null;
    }

    @Override
    public PostDto readPost(int id) {
        return null;
    }

    @Override
    public void updatePost(int id, PostDto dto) {

    }

    @Override
    public void deletePost(int id) {

    }
}
  • 위와 같이 메소드 부르고 로그 선언하고 이제 메소드들 메꿔주기
package dev.dongyun.crud.post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class PostServiceimple implements PostService {
    private static final Logger logger = LoggerFactory.getLogger(PostServiceimple.class);

    private final iPostRepository postRepository;

    public PostServiceimple(
           @Autowired iPostRepository postRepository
    ) {
        this.postRepository = postRepository;
    }


    @Override
    public List<PostDto> readPostAll() {
        return this.postRepository.findAll();
    }

    @Override
    public void createPost(PostDto dto) {
        if(!this.postRepository.save(dto)){
            throw new RuntimeException(("save failed"));
        }
        this.postRepository.save(dto);
    }
    @Override
    public PostDto readPost(int id) {
        return this.postRepository.findById(id);
    }

    @Override
    public void updatePost(int id, PostDto dto) {
        this.postRepository.update(id,dto);

    }

    @Override
    public void deletePost(int id) {
        this.postRepository.delete(id);
    }
}

아래 3번 부분은 무시, Deprecated의 개념 용도만 체크


3) 그리고 이제 우리는 postcontroller가 아닌 restcontroller 사용할 예정
-> 따라서 post rest controller 위에 @Deprecated 붙여주기

//Deprecated는 당장 지원은 해주지만 곧 쓰지 못할 버전이라고 알려주는 것
//오로지 응답을 받고 요청을 보내는 역할로만
@Deprecated

그럼 이렇게 빗금 처리됨 & 더 이상 아래 항목들 관리 진행하지 않음


    public PostServiceimple(
           @Autowired iPostRepository postRepository
    ) {
        this.postRepository = postRepository;
    }

-> iPostRepository인터페이스에 해당하는 아이들을 ioc가 찾아서 강제로 넣어주는 것- Dependency injection 이라는 특징이었지


4) 이제 service와 postcontroller 연결해서 사용

(+)참고 postservice interface

package dev.dongyun.crud.post;

import java.util.List;

public interface PostService {
    void createPost(PostDto dto);
    List<PostDto> readPostAll();
    PostDto readPost(int id);
    void updatePost(int id, PostDto dto);
    void deletePost(int id);
}

기존에 지정해뒀던 postlist -> postservice의 메소드 호출 방식으로 진행

package dev.dongyun.crud.post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
//Deprecated는 당장 지원은 해주지만 곧 쓰지 못할 버전이라고 알려주는 것
//오로지 응답을 받고 요청을 보내는 역할로만
@Controller
@ResponseBody
//이렇게 붙여놓으면
//클래스 안 모든 함수들이 responsebody가
// 붙은 형태로 함수 선언 완료

@RequestMapping("post")
public class PostController {
    private static final Logger logger = LoggerFactory.getLogger(PostController.class);
    private final PostService postservice;

    public PostController(

          @Autowired PostService postservice) {
        this.postservice = postservice;
        //왜 위에는 list고 아래는 arraylist냐
        // list는 인터페이스, arraylist는 구현체
    }
    @PostMapping("create")
    public void createPost(@RequestBody PostDto postDto){
        logger.info(postDto.toString());
        this.postservice.createPost(postDto);
    }

    @GetMapping("read-all")
        public List<PostDto>readPostAll(){
            logger.info("read all");
            return this.postservice.readPostAll();
        }
    @GetMapping("read-one")
    public PostDto readPostOne(@RequestParam("id") int id){
        logger.info("read one");
        return this.postservice.readPost(id);
    }

    @PatchMapping("update")
    public void updatePost(
            @RequestParam("id") int id,
            @RequestBody PostDto postDto //포스트 요청의 body

    ){
        PostDto targetPost = this.postservice.readPost(id); //업데이트를 위한 목적 타겟
        if (postDto.getTitle()!=null){
            targetPost.setTitle(postDto.getTitle());
        }
        if (postDto.getContent()!=null){
            targetPost.setContent(postDto.getContent());
        }
        this.postservice.updatePost(id, targetPost);
    }

    @DeleteMapping("delete")
    public void deletePost(@RequestParam("id") int id){
        this.postservice.deletePost(id);
    }
}

5) RestController도 postlist 없애고 postservice 다루는 방향으로 고쳐봅세~~

package dev.dongyun.crud.post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("post")
public class PostRestController {
    private static final Logger logger = LoggerFactory.getLogger(PostRestController.class);
    private final PostService postservice;
    public PostRestController(
            @Autowired PostService postservice
    ){
        this.postservice = postservice;
    }

    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public void createPost(@RequestBody PostDto postDto){
        logger.info(postDto.toString());
        this.postservice.createPost(postDto);
    }

    @GetMapping()
    public List<PostDto> readPostAll(){
        logger.info("read-all");
        return this.postservice.readPostAll();
    }

    //GET
    //GET /post?id=0

    @GetMapping("{id}}")
    public PostDto readPost(@PathVariable("id") int id){
        logger.info("in read post");
        return this.postservice.readPost(id);
    }

    @PutMapping("{id}")
    public void updatePost(
            @PathVariable("id") int id,
            @RequestBody  PostDto postDto
    ){
        PostDto targetPost = this.postservice.readPost(id); //업데이트를 위한 목적 타겟
        if (postDto.getTitle()!=null){
            targetPost.setTitle(postDto.getTitle());
        }
        if (postDto.getContent()!=null){
            targetPost.setContent(postDto.getContent());
        }
        this.postservice.updatePost(id, targetPost);
    }

    @DeleteMapping("{id}")
    public void deletePost(@PathVariable("{id}") int id){
        this.postservice.deletePost(id);
    }
}
  • 추가적으로 건네줄 때 HttpServelet도 넣기 가능
    => 기존코드
    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public void createPost(@RequestBody PostDto postDto){
        logger.info(postDto.toString());
        this.postservice.createPost(postDto);
    }

=> HttpServelet 추가적으로 넣기

    public void createPost(@RequestBody PostDto postDto, HttpServletRequest request){
        logger.info(postDto.toString());
        this.postservice.createPost(postDto);
    }

=> HttpServlet의 구조는 아래와 같음 (설명,이미지출처)

  • 클라이언트 요청에 따라 서블릿 컨테이너는 service() 메서드를 호출하고, service() 메서드는 요청이 GET인지 POST인지 구분하여 각각 doGet(), doPost() 메서드를 호출한다.

  • http servlet test
    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public void createPost(@RequestBody PostDto postDto, HttpServletRequest request){
        logger.info("content");
        logger.info(postDto.toString());
        logger.info("full request");
        logger.info(String.valueOf(request));
        logger.info("header of request");
        logger.info(request.getHeader("Content-type"));
        this.postservice.createPost(postDto);
    }


=> 위와 같이 request는 json 형태로 온다는 걸 알 수 있고

        logger.info("method of request");
        logger.info(request.getMethod());
  • getMethod를 통해서 request 로부터 사용된 메소드 정보도 get 가능

그 외에도 request에서 얻어오는 함수들 (이미지, 설명 출처 블로그)


에러 디버깅

1) Parameter 0 of constructor in dev.dongyun.crud.post.PostController required a bean of type 'dev.dongyun.crud.post.PostService' that could not be found.Action:
Consider defining a bean of type 'dev.dongyun.crud.post.PostService' in your configuration.
=> bean이 지정되지 않았단다 -> 이는 적절한 어노테이션을 붙여주지 않았다는 말이지
실제로 PostService 인터페이스를 상속받는 구현체아잉 들어가보니 어노테이션 부분이 깨끗..^^

  • @Service 붙여줘서 빈으로 생성 ok

2) PostService와 같은 인터페이스 새로 선언 시에
=> private final PostService postservice; 코드 추가

@RestController
@RequestMapping("post")
public class PostRestController {
    private static final Logger logger = LoggerFactory.getLogger(PostRestController.class);
    private final List<PostDto> postList;
    private final PostService postservice;
    public PostRestController(){
        this.postList = new ArrayList<>();
    }

-> 그럼 해당 줄에 빨간 줄 뜰텐데, 이때 아래와 가팅 autowired 써서 생성자 수정해주면 됨

public class PostRestController {
    private static final Logger logger = LoggerFactory.getLogger(PostRestController.class);
    private final PostService postservice;
    public PostRestController(
            @Autowired PostService postservice
    ){
        this.postservice = postservice;
    }

(+)
postman에서 postcontroller 따로, restcontroller 따로 테스트해보고 싶을 떈 requestmapping 죽이고 살리는거 선택함 따라서

0개의 댓글