JPA에 관하여 공부를 시작했다. JPA에 관하여 정리를 하기 전에 우선 ORM이라는 것에 대한 개념을 정리하려 한다.

 

ORM(Object Relational Mapping)

ORM이란 객체-관계매핑의 줄임말이다. 이는 우리가 프로그래밍을 하면서 쓰는 객체라는 개념과 RDB(Relational DataBase)에서 쓰이는 데이터인 테이블을 자동으로 매핑하는 것을 의미한다. ORM을 통하면 SQL문을 따로 짤 필요없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있게 된다.

여기서 SQL이라는 단어가 등장하는데 SQL은 간단하게 말하면 RDBMS(관계형 데이터베이스 관리 시스템)의 데이터를 관리하기 위해 만들어진 프로그래밍언어이다. 데이터베이스를 관리하기 위한 표준언어이기 때문에 이를 추후에 공부할 예정이다.

다시 돌아와서 ORM에 대해 얘기를 계속 해보자면, ORM은 SQL을 사용하지 않고 객체를 통해 데이터베이스를 조작할 수 있으므로 부수적인 코드가 사라지거나 줄어들어 생산성이 증가한다. 물론 프로젝트의 규모가 커지면 복잡성 또한 증가하면서 단순하게 객체로만 데이터베이스를 조작하는 것이 아닌 SQL을 사용해야할 때가 있을 수 있다.

 

그렇다면 JPA는 무엇인가

JPA란 자바 어플리케이션에서 RDB를 사용하는 방식을 정의한 인터페이스이다. 여기서 인터페이스는 자바프로그래밍을 할 때 사용되는 Interface가 맞다. 인터페이스는 메서드가 정의되어있지 않고 이 애플리케이션이 어떻게 작동하는지를 정의해둔 요약본같은 것이다 라는 말을 들은 적이 있다. 결론은, JPA는 자바 애플리케이션에서 RDB를 어떻게 사용해야하는지를 정의하는 한 방법이라는 것이다.

위의 그림에서 구조를 살펴보자. 

ORM으로 둘러싸인 부분에 spring data jpa와 Hibernate가 있다. Hibernate는 JPA의 구현체이다. 앞에서 JPA는 인터페이스라는 얘기를 했다. 그렇다면 JPA의 구현체라는 말은 Hibernate는 JPA라는 인터페이스를 구현한 라이브러리라는 말인 것이다. 그렇다면 저 옆에 색칠되어있는 spring data jpa는 무엇일까. 바로 spring에서 제공해주는 모듈중 하나로, JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이뤄지는 것이 spring data jpa이다. Hibernate를 직접 사용할 수도 있겠지만 spring data jpa를 통해 Repository를 정의해서 사용하는 방법 또한 존재한다.

위의 설명한 3가지는 서로의 관계만 있을 뿐이지 각각 다른 요소들이다. 특히 spring data jpa는 JPA와는 다른 개념이다. spring data jpa는 Repository의 구현에서 JPA를 사용하고 있다. 그러니까 그냥 spring에서 JPA를 쉽게 사용하기위해 제공하는게 spring data jpa라는 것이다. 그리고 그림에 spring data jpa가 Hibernate안에 들어가 있는 듯한 모양인데 그 이유는 Repository인터페이스의 구현체를 만들 때 Hibernate의 라이브러리를 사용하는 경우가 많기 때문이다.

 


간단하게 spring boot의 기본에 대한 강의를 보고 이제 JPA에 대해 배우고 있다. 아직은 뭔가 내 손으로 웹사이트의 백엔드를 만든다는게 상상이 안간다. 학과 강의의 결과물 같이 머리속에 지식보다는 다 쓸려나가고 걸쭉한 기름때만 남아있는 기분이다. 뭐 어쩌겠나. 또 열심히 채워넣어서 기름때를 늘리다보면 언젠가 1인분하겄지... 그 때까진 죽이 되든 밥이 되든 그냥 X나 하는거다.

'웹 개발 일기 > Spring' 카테고리의 다른 글

Swagger  (0) 2021.06.22
JUnit을 Spring에서 활용하여 테스트 진행하기  (0) 2021.06.22
JUnit  (0) 2021.06.22
Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
Filter / Interceptor  (0) 2021.06.17

Swagger란?

개발한 REST API를 편리하게 문서화해주고 이를 통해서 관리를 용이하게 하거나 제삼자가 사용과 테스트를 용이할 수 있도록 하는 프로젝트이다.

 

Annotation기반으로 contoller를 공개하거나 숨기는 것이 가능하다.

 

우선 Maven repository에서 springfox boot starter를 검색하여 gradle 부분을 복사한다.

저 전체를 그대로 build.gradle의 dependencies에 복사하면 swagger를 사용할 준비가 완료된다.

 

우리가 만든 controller는 다음과 같다.

@Api(tags = {"API 정보를 제공하는 Controller"})
@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    @ApiImplicitParams({
                    @ApiImplicitParam(name = "x", value = "x값", required = true, dataType = "int", paramType = "path"),
                    @ApiImplicitParam(name = "y", value = "y값", required = true, dataType = "int",paramType = "query")
            })
    @GetMapping("/plus/{x}")
    public int plus(@PathVariable int x, @RequestParam int y)
    {
        return x+y;
    }

    @ApiResponse(code = 502, message = "사용자의 나이가 10살 이하일 때")
    @ApiOperation(value = "사용자의 이름과 나이를 리턴하는 메서드")
    @GetMapping("/user")
    public UserRes user(UserReq userReq){
        return new UserRes(userReq.getName(), userReq.getAge());
    }

    @PostMapping("/user")
    public UserRes userPost(@RequestBody UserReq userReq){
        return new UserRes(userReq.getName(), userReq.getAge());
    }

}

이러한 ApiContorller를 작성하였을 때, 여러 정보들을 Swagger를 통해 다음과 같이 확인할 수 있다.

접속은 주소창에 'localhost:포트번호/swagger-ui/' 를 입력해주면 된다.

@Api(tags = {"API 정보를 제공하는 Controller"})를 통해 ApiController에 대한 설명을 넣을 수 있고 사진과 같이 출력된다.

 

값 두개를 더하는 plus를 확인하면

다음과 같이 출력되고 우리는 x와 y값을 @ApiImplicitParam을 통해 속성을 정의하였다. 

 

이렇듯 여러가지 Annotation을 활용하여 API를 쉽게 확인하고 테스트할 수 있다.

'웹 개발 일기 > Spring' 카테고리의 다른 글

JPA(Java Persistance API)  (0) 2021.07.07
JUnit을 Spring에서 활용하여 테스트 진행하기  (0) 2021.06.22
JUnit  (0) 2021.06.22
Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
Filter / Interceptor  (0) 2021.06.17

이전에 일반 자바 클래스에서만 테스트를 활용했는데 이번에는 Spring boot에서 활용을 해볼 것이다.

 

우선 다음과 같은 Controller를 정의했다.

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class CalculatorApiController {

    private final Calculator calculator;

    @GetMapping("/sum")
    public int sum(@RequestParam int x, @RequestParam int y){
        return calculator.sum(x, y);
    }

    @PostMapping("/minus")
    public Res minus(@RequestBody Req req){

        int result = calculator.minus(req.getX(), req.getY());

        Res res = new Res();
        res.setResult(result);
        res.setResponse(new Res.Body());
        return res;
    }

}

 

우리는 sum과 minus가 제대로 작동하는지 테스트 해보고 싶다.

이를 위해서 테스트 코드를 작성하였다.

 

@WebMvcTest(CalculatorApiController.class) //웹에 특화되어 자원을 아낄 수 있음
@AutoConfigureWebMvc
@Import({Calculator.class, DollarCalculator.class})
public class CalculatorApiControllerTest {

    @MockBean
    private MarketApi marketApi;

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    public void init(){
        Mockito.when(marketApi.connect()).thenReturn(3000);
    }

    @Test
    public void sumTest() throws Exception{
        // http://localhost:8080/api/sum
        mockMvc.perform(
                MockMvcRequestBuilders.get("http://localhost:8080/api/sum")
                        .queryParam("x", "10")
                        .queryParam("y", "10")
        ).andExpect(
                MockMvcResultMatchers.status().isOk()
        ).andExpect(
                MockMvcResultMatchers.content().string("60000")
        ).andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void minusTest()throws Exception {
        Req req = new Req();
        req.setX(10);
        req.setY(10);

        String json = new ObjectMapper().writeValueAsString(req);

        mockMvc.perform(
                MockMvcRequestBuilders.post("http://localhost:8080/api/minus")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json)
        ).andExpect(
                MockMvcResultMatchers.status().isOk()
        ).andExpect(
                MockMvcResultMatchers.jsonPath("$.result").value("0")
        ).andExpect(
                MockMvcResultMatchers.jsonPath("$.response.resultCode").value("OK")
        ).andDo(MockMvcResultHandlers.print());
    }
}

이전의 자바클래스의 테스트코드보다 뭔가 복잡하다. 하나씩 살펴보자.

 

일단 @MockBean과 @Mock의 차이점을 짚고 넘어가자. 복잡한 설명은 생략하고 @MockBean은 Spring Boot Container가 필요하고 Bean이 container에 존재 해야 한다면 @MockBean을 사용하면 되고 그런게 아니라면 @Mock를 사용하면 된다. (Spring Boot Container : 주입을 이용한 객체를 관리하는 컨테이너. Bean의 사용, 생명주기 등을 관장한다.)

 

@Autowired를 통해 상황의 타입에 맞는 Bean을 자동으로 주입해주게 되고

 

sumTest, minusTest를 통해 각각의 메서드가 정상적으로 작동하는지 확인한다.

 

sumTest만을 봤을 때

@Test
    public void sumTest() throws Exception{
        // http://localhost:8080/api/sum
        mockMvc.perform(
                MockMvcRequestBuilders.get("http://localhost:8080/api/sum")
                        .queryParam("x", "10")
                        .queryParam("y", "10")
        ).andExpect(
                MockMvcResultMatchers.status().isOk()
        ).andExpect(
                MockMvcResultMatchers.content().string("60000")
        ).andDo(MockMvcResultHandlers.print());
    }

MockMvcRequestBuilders는 요청데이터를 설정할 때 사용할 메서드이다. get이하에서 설정한 uri에 따라 메서드가 올바르게 작동하는지 테스트를 진행한다.

 

andExpect(MockMvcResultMatchers.status().isOk())는 MockMvcResultMatchers메서드를 통해 결과의 상태값을 출력했을 때 'OK'가 나와야하고

 

.andExpect(MockMvcResultMatchers.content().string("60000"))는 결과값이 문자열 60000이 나와야 하는 것을 나타내고

 

마지막으로 .andDo(MockMvcResultHandlers.print())를 통해 실행결과를 출력하고 테스트를 종료하게 된다.

 

gradle의 경로에 한글이 섞여 해당 테스트를 진행할 때 오류가 났지만 제대로 테스트가 진행된 것을 확인하였다.

 

 

 

'웹 개발 일기 > Spring' 카테고리의 다른 글

JPA(Java Persistance API)  (0) 2021.07.07
Swagger  (0) 2021.06.22
JUnit  (0) 2021.06.22
Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
Filter / Interceptor  (0) 2021.06.17

JUnit이란?

자바프로그래밍용 단위 테스트 프레임워크이다. Annotation가반으로 테스트를 지원한다.

 

코드에 잘못된 부분이 있을 때 각각의 객체를 확인하고 객체에 정의된 메서드를 하나씩 확인해 어떤 부분이 잘못된 것인지 찾는 것은 얼핏봐도 매우 비효율적이다. 이런 과정을 간략화하기 위해 테스트클래스를 작성하여 테스트를 진행한다. 이 때 JUnit을 사용하면 더욱 간단하게 테스트를 진행할 수 있다.

 

 

build.gradle의 dependencies에 다음과 같이 추가를 해주면 사용이 가능하다. 

 

public class DollarCalculator implements ICalculator{

    private int price = 1;
    private MarketApi marketApi;


    public DollarCalculator(MarketApi marketApi){
        this.marketApi = marketApi;
    }

    public void init(){
        this.price = marketApi.connect();
    }


    @Override
    public int sum(int x, int y) {
        x *= price;
        y *= price;
        return x + y;
    }

    @Override
    public int minus(int x, int y) {
        x *= price;
        y *= price;
        return x - y;
    }
}

위는 내가 작성한 간단한 계산기이다. 위의 계산기가 제대로 작동하는 방법을 알기 위해서 main메서드에 객체를 생성하고 각각의 파라미터를 넣어 제대로 작동하는지를 확인할 수 있으나 이는 매우 번거로울 수 있다. 특히 spring boot에서 테스트를 진행하거나 매우 복잡한 클래스를 사용할 때 오류가 발생한 점을 찾는 것은 꽤나 시간낭비일 것이다.

 

우리는 그래서 저기 보이는 test폴더의 DollarCalculatorTest를 활용할 것이다.

@ExtendWith(MockitoExtension.class)
public class DollarCalculatorTest {

    @Mock
    public MarketApi marketApi;

    @BeforeEach
    public void init(){
        Mockito.lenient().when(marketApi.connect()).thenReturn(3000);
    }

    @Test
    public void testHello(){
        System.out.println("hello");
    }

    @Test
    public void dollarTest(){

        MarketApi marketApi = new MarketApi();
        DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
        dollarCalculator.init();

        Calculator calculator = new Calculator(dollarCalculator);

        System.out.println(calculator.sum(10, 10));

        Assertions.assertEquals(22000, calculator.sum(10, 10));

    }

    @Test
    public void mockTest(){
        DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
        dollarCalculator.init();

        Calculator calculator = new Calculator(dollarCalculator);

        Assertions.assertEquals(60000, calculator.sum(10, 10));

    }


}

위에서 부터 하나씩 살펴보자. 우선 처음보는 Annotation이 나왔다.

 

Mock란 무엇일까?

실제 객체를 만들어서 사용하기에는 객체간의 의존성이 강하거나 만들기에 비용이나 시간이 너무 오래 걸리는 경우 테스트를 위한 가짜 객체를 만들어 사용하는 방법이다.

MarketApi객체를 Mocking해줌으로써 가상의 객체를 형성해준다.

 

test메서드가 실행될 때 다른 메서드를 호출하는 위치를 정해주는 Annotation이 몇가지 있다.

우선 @Test는 IDE로 직접 실행하는 test메서드이다.

 

@BeforeAll, @AfterAll : 클래스에 존재하는 모든 메서드를 실행한다고 할 때 각각 메서드가 실행되기 전, 메서드가 전부 실행된 후에 실행되는 단위들을 나타낸다.

 

@BeforeEach, @AfterEach : 각각의 Test메서드가 실행될 때 호출되는 메서드이다. 해당 Annotation이 달리면 각각 Test메서드가 실행되기 전, 후에 실행된다.

 

위의 테스트 코드를 보면 @BeforeEach가 다음의 메서드에서 쓰인다.

@BeforeEach
    public void init(){
        Mockito.lenient().when(marketApi.connect()).thenReturn(3000);
    }

 

lenient()는 현재 코드에 쓰이지 않는 스터빙을 했을 때 에러가 뜨지 않도록 제약을 느슨하게 해주는 역할을 한다. 그렇다면 stubbing을 무엇일까?

 

stubbing은 가짜(Dummy)객체를 하나 만드는 것과 같다. 좀 더 자세히는 Dummy객체에서 원하는 값을 미리 정해놓고 그것을 리턴하게 만드는 것이다. 위에 근거하여 when(marketApi.connect()).thenReturn(3000);이 의미하는 바는 다음과 같다. '만약 marketApi.connect()를 요청하는 경우 return값은 3000' 과 같은 말이다.

 

이제 여러 test메서드로 어떻게 코드를 테스트하는지 보도록 하자.

dollarTest메서드를 보면

@Test
    public void dollarTest(){

        MarketApi marketApi = new MarketApi();
        DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
        dollarCalculator.init();

        Calculator calculator = new Calculator(dollarCalculator);

        System.out.println(calculator.sum(10, 10));

        Assertions.assertEquals(22000, calculator.sum(10, 10));

    }

제일 아래에 Assertions.assertEquals(22000, calculator.sum(10, 10));이 보일 것이다. 이것이 의미하는 바는 객체 22000과 calculator.sum(10, 10)이 같으면 테스트를 통과시키는 코드이다. if문을 JUnit을 사용하여 간단하게 변경할 수 있도록 한 것이다. 이렇게 함으로써 우리가 작성한 sum이라는 메서드가 정상적으로 작동하는지 알 수 있다.

'웹 개발 일기 > Spring' 카테고리의 다른 글

Swagger  (0) 2021.06.22
JUnit을 Spring에서 활용하여 테스트 진행하기  (0) 2021.06.22
Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
Filter / Interceptor  (0) 2021.06.17
예외 처리  (0) 2021.06.15

이번 강의는 2번을 들었는데도 이해가 잘 되지 않아 쓰면서 정리하는 게 나을 것 같다.

 

주요 과제는 server와 client간의 연결을 구현하는 것이다.

 

@RestController
@RequestMapping("/api/client")
public class ApiController {

    private final RestTemplateService restTemplateService;

    public ApiController(RestTemplateService restTemplateService) {
        this.restTemplateService = restTemplateService;
    }

    @GetMapping("/hello")
    public UserResponse getHello(){
       return restTemplateService.hello();
    }


}

우선 ApiController를 작성하였다. Talend API Tester에 GET메서드로 http://localhost:8080/api/client/hello를 보내게 되면 restTemplateService.hello()가 실행되는데 RestTemplateService클래스는 다음과 같이 선언된다.

 

@Service
public class RestTemplateService {


    //http://localhost/api/server/hello
    //response
    public UserResponse hello(){

       URI uri = UriComponentsBuilder
               .fromUriString("http://localhost:9090")
               .path("/api/server/hello")
               .queryParam("name", "steve")
               .queryParam("age", 10)
               .encode()
               .build().toUri();

        System.out.println(uri.toString());

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<UserResponse> result = restTemplate.getForEntity(uri, UserResponse.class);
        //uri : http://localhost:9090/api/server/hello?name=steve&age=10

        System.out.println(result.getStatusCode());
        System.out.println(result.getHeaders());
        System.out.println(result.getBody());

        return result.getBody();
    }

hello 메서드는 우선 uri를 생성하는데 UriComponentBuilder를 통해 뒤에 입력되는 여러 가지 파라미터들을 통해 http://localhost:9090/api/sever/hello?name=steve&age=10인 URI를 생성할 것이다. 

 

새로 생성한 객체 result에는 어떤 값들이 들어갈까. 아래를 보면 status, header, body를 출력하는 것을 알 수 있다. 

restTemplate.getForEntity(uri, UserResponse.class) 는 result객체를 생성할 때 3가지의 값을 넣어주는구나~라고 생각하고 넘어가자.

 

그럼 이제 client와 통신하는 서버를 생성을 해줘야한다.

@Slf4j
@RestController
@RequestMapping("/api/server")
public class ServerApiController {



    @GetMapping("/hello")
    public User hello(@RequestParam String name, @RequestParam int age){

        User user = new User();
        user.setName(name);
        user.setAge(age);
        return user;
    }


}

우리는 클라이언트와 서버를 연결하여 서버로부터의 응답(reponse)을 출력하고 싶다. 위에서부터 하나씩 살펴보면 이전에 클라이언트에서 URI를 http://localhost:9090/api/sever/hello?name=steve&age=10로 생성했다 여기에 name과 age값이 차례로 들어가 있는데 이 값들이 서버에 생성한 User의 name과 age를 set해준다. 

 

우리가 이전에 클라이언트에서 출력하려 한 3개의 값이 있다. status, header, body 이 3개의 값들은 Talend API Tester를 통해 URI가 전달될 때 client의 콘솔에 우리가 생성한 URI와 값들이 출력되도록 설정하였는데 그 결과는 다음과 같다.

순서대로 URI, status, header, body이다. 우린 여기서 body의 name과 age에 주목을 해야 하는데 생각해보면 우리는 URI를 설정해준 적은 있지만 UserReponse객체(client내부에 선언해둔)의 name과 age에 직접 값을 초기화 해준 적이 없다. 그럼 어떻게 저 값들이 출력된 것일까?

 

ResponseEntity<UserResponse> result = restTemplate.getForEntity(uri, UserResponse.class);

result객체는 restTemplate.getForEntity(uri, UserResponse.class)의 값을 통해 생성되는데 도대체 이 안에 무슨 값이 있는지 자세히 살펴보니

 

잘 보이지는 않지만 순서대로 보면 status, body, header의 값이 순서대로 출력된다. 결국 result는 서버가 요청(request)을 받고 다시 client에게 보내준 응답(response)의 내용들을 포함하는 객체인 것이다.

 

이렇게 요청에 의한 응답을 확인할 수 있었다.

'웹 개발 일기 > Spring' 카테고리의 다른 글

JUnit을 Spring에서 활용하여 테스트 진행하기  (0) 2021.06.22
JUnit  (0) 2021.06.22
Filter / Interceptor  (0) 2021.06.17
예외 처리  (0) 2021.06.15
AOP(Aspect Oriented Programming)  (0) 2021.06.15

Filter란?

 

Filter는 웹 애플리케이션에서 관리되는 영역으로써 Spring Boot Framework에서 클라이언트로부터 오는 요청/응답에 대해서 최초/최종단계에 위치한다. Filter는 요청/응답의 정보를 변경하거나 데이터를 확인할 수 있다.

 

말 그대로 우리가 평소 알고 있는 필터의 역할을 한다고 생각하면 된다. 데이터가 알맞는지를 확인한다고 생각하자. 클라이언트가 보낸 정보를 필터로 걸러서 내용물을 확인하고 버리거나 원하는 것만 골라내서 요청을 보낸다.

 

이런 특징때문에 보안의 용도로 사용될 수 있는데, 해당 요청이 올바른지를 확인하여 요청을 승인하지 않는 등의 역할을 한다.

 

지금부터 작성할 내용은 요청이나 응답을 출력하는 필터를 적용한 것이다.

package com.example.filter.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/api/user/*")
public class GlobalFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest)request);
        ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse)response);
       


        chain.doFilter(httpServletRequest, httpServletResponse);
      

        String url = httpServletRequest.getRequestURI();

        String reqContent = new String(httpServletRequest.getContentAsByteArray());
        log.info("request url : {}, request body : {}", url, reqContent);

        String resContent = new String(httpServletResponse.getContentAsByteArray());
        int httpStatus = httpServletResponse.getStatus();
        httpServletResponse.copyBodyToResponse();
        log.info("response status : {}, responseBody : {}", httpStatus, resContent);

    }
}

하나씩 살펴보도록 하자.

우선, GlobalFilter는 인터페이스 Filter를 상속받는다. 아래의 doFilter는 Filter의 메서드인데 request(요청), reponse(응답), chain, 이렇게 3가지의 파라미터를 포함한다. 여기서 chain은 여러 개의 필터가 이어진 것을 생각하면 된다.

 

httpServletRequest와 httpServletReponse는 각각 HTTP요청의 정보와 응답을 저장하는 객체이다.

chain.dofilter를 통해 정달된 reponse를 전달하여 그 결과를 클라이언트에 전달한다.

 

그 아래는 우리가 어떤 url에서 request를 받았는지, 또 그 request는 어떻게 생겼는지, response에 대한 상태는 어떤지, response는 어떻게 클라이언트에게 전달됐는지를 출력하는 코드이다.

 

정상적인 url을 통해 json형식의 데이터를 요청했다면

이런 식으로 정보가 출력된다.


Interceptor란?

 

Filter와 유사하지만 Filter와는 다르다.

보이는 바와 같이 Interceptor는 Spring Context내부에 존재한다. 그렇기 때문에 Filter와 Interceptor는 실행되는 시점이 다르다.

 

 

 

 

 

 

'웹 개발 일기 > Spring' 카테고리의 다른 글

JUnit  (0) 2021.06.22
Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
예외 처리  (0) 2021.06.15
AOP(Aspect Oriented Programming)  (0) 2021.06.15
Spring Boot Annotation(2)  (0) 2021.06.15

우리는 웹애플리케이션을 제작할 때 몇가지의 예외처리를 통해 프로그램이 안정해지도록 한다. 그리고 예외처리를 통해 발생된 오류들에 관한 메세지가 정확히 어떤 부분에서, 어떤 것 때문에 벌어졌는지를 알고 싶어 한다.

 

하지만 에러가 나면 웹애플리케이션이 우리에게 전해주는 정보는 그리 많지 않다. 기껏해야 에러코드 정도에 불과한 정보를 전달해준다.

 

원하는 에러정보를 얻기 위해서 우리는 @ExceptionHandler를 사용할 것이다.


@ExceptionHandler

package com.example.exception.advice;


import ...


@RestControllerAdvice
public class GlobalControllerAdvice {


    @ExceptionHandler(value = Exception.class)
    public ResponseEntity exception(Exception e){
        System.out.println(e.getClass().getName());
        System.out.println("--------------");
        System.out.println(e.getLocalizedMessage());
        System.out.println("--------------");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("");
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }
}

위에서 보이는 코드 중 @ExceptionHandler(value = Exception.class)를 주목하자. 이는 발생하는 모든 예외를 캐치하겠다는 것이다. 예외를 캐치하면 아래의 로직이 진행된다.

 

아래의 @ExceptionHandler(value = MethodArgumentNotValidException.class)부분은 원하는 에러만을 골라서 메세지를 출력하는 과정이다.

 

위에서의 경우는 프로그램을 실행했을 때 발생하는 모든 오류에 대한 예외처리지만 내가 원하는 부분에서만 에러가 발생한 것을 캐치하고 싶을 때는 다음과 같이 설정하면 된다.

package com.example.exception.controller;


import com.example.exception.dto.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/user")
public class ApiController {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
        System.out.println("api Controller");
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }
}

위의 코드는 중간이 생략된 코드이다.

아까전에 GlobalControllerAdvice에 예외처리를 위한 ExceptionHandler를 사용했는데 좀 더 범위를 좁혀서 ApiController내의 에러를 캐치하는 ExceptionHandler를 사용하였다. ApiController에서 ExceptionHandler를 제외한 상태에서 예외가 발생했을 때는 GlobalControllerAdvice에서 사용한 ExceptionHandler가 작동해 해당 메세지를 출력하는데 위처럼 ApiController에 ExceptionHandler를 사용하면 ApiController내에서 예외를 캐치할 때 "api Controller"가 출력된다.

 

'웹 개발 일기 > Spring' 카테고리의 다른 글

Rest Template를 사용한 Server간의 연결  (0) 2021.06.18
Filter / Interceptor  (0) 2021.06.17
AOP(Aspect Oriented Programming)  (0) 2021.06.15
Spring Boot Annotation(2)  (0) 2021.06.15
Spring Boot Validation  (0) 2021.06.15

AOP는 관점 지향 프로그래밍이라고 불린다.

 

프로그래밍을 하다 보면 우리가 원하는 핵심기능을 실행할 때마다 로그를 남기거나 메서드가 작동할 때까지의 시간을 출력하는 등의 부가적인 기능을 반복적으로 수행해야 될 상황이 온다. 이런 기능들을 사용하는 모든 메서드에 적용하여 코드를 짜는 것은 매우 비효율적이다.

 

이러한 이유로 AOP가 등장했는데 AOP는 핵심기능과 공통기능을 분리하여 공통 기능을 필요로 하는 핵심 기능들에서 다시 사용하는 방식이다.

 

AOP는 부가기능을 Aspect로 정의한다.

@Aspect
@Component
public class ParameterAop {
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){

    }

그림과 같이 @Aspect를 붙여 해당 클래스가 Aspect, 즉 부가기능을 나타내는 클래스인 것을 명시한다.

 

@Pointcut은 어떤 Join Point에 Advice가 적용될 것인지 지정하는 어노테이션이다.

여기서 Join Point는 Advice가 적용될 위치를 나타내고 Advice는 원하는 곳에 제공할 부가기능을 담고 있는 모듈이다.

com.example.aop패키지의 controller 패키지의 내부에는 전부 Advice를 적용한다는 뜻이다.

 

spring에는 총 5가지 관점이 있는데 각각의 어노테이션을 붙여줌으로써 무엇을 언제 실행할지 정의하는 역할을 한다.

@Before("cut()")
    public void before(JoinPoint joinPoint){
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    System.out.println(method.getName());

        Object[] args = joinPoint.getArgs();
        for(Object obj : args){
            System.out.println("type : " + obj.getClass().getSimpleName());
            System.out.println("value : " + obj);
        }

    }

@Before을 통해 내부에 있는 cut메서드 실행 전에 아래의 부분(Advice)을 실행해준다고 명시하게된다.

 

@AfterReturning(value = "cut()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj){
    System.out.println("return");
    System.out.println(returnObj);

    }

@AfterReturning을 통해 cut메서드가 성공 후 해당 Advice를 실행한다고 명시하였다.

 

이외에

After : 메서드 실행 전 Advice 실행

After Throwing : 메서드 예외 발생 후 Advice 실행

Around : 메서드 실행 전과 후 모두 Advice 실행

 

이렇게 총 5가지의 관점이 있다. 위처럼 AOP를 사용하면 부가적인 기능을 매번 정의할 필요 없이 한 번의 정의로 사용이 가능하다.

 

'웹 개발 일기 > Spring' 카테고리의 다른 글

Filter / Interceptor  (0) 2021.06.17
예외 처리  (0) 2021.06.15
Spring Boot Annotation(2)  (0) 2021.06.15
Spring Boot Validation  (0) 2021.06.15
CRUD  (0) 2021.03.29

+ Recent posts