5 minute read

Untitled

원격 프로그램 실행 (RequesstMapping)

우리는 client에서 오는 요청을 처리하기 위한 api url을 매핑할 때 Spring의 @RequestMapping이라는 어노테이션을 사용한다. Spring은 사용자의 편리를 위해 RequestMapping을 http에서 지원하는 4가지 method인 @GetMapping@PostMapping@PutMapping@DeleteMapping과 추가적으로 @PatchMapping까지 여러가지 방법의 매핑 방식으로 확대하여 제공하기도 한다.

@RequesstMapping 이란?

Spring 개발 시 특정 URL로 요청(Request)을 보내면 Controller에서 어떠한 방식으로 처리할지 정의한다.

이때 들어온 요청을 특정 method와 매핑하기 위해 사용하는 어노테이션이 바로 @RequestMapping이다.

  • @RequestMapping은 Controller단에서 사용되는데, DispatcherServlet이 Controller 파일을 찾고, 논리적 주소가 매핑된 Method를 찾기 위해서는 @Controller와 @RequesetMapping이 작성되어야 한다.
  • URL와 Controller의 method 매핑을 설정하는 어노테이션
  • URL 이외에도 다양한 속성을 지정 가능
  • URL 템플릿 기능을 이용하면 URL속의 값을 쉽게 얻을 수 있음

사용 이유

@RequesstMapping 작성 예시

package com.fastcampus.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller // 1. 프로그램 등록
public class SpringTest {

    @RequesstMapping("/hello") //2. URL과 main()을 연결
    public void main() {
        System.out.println("Hello");
    }
}

로컬로 프로그램을 실행시킨다는 가정 하에 우리는 http://localhost:8080/hello 로 Hello 출력 요청을 보낸다.

여기서 알 수 있다싶이 RequestMapping은 요청이 들어왔을 시에 컨트롤러와 매핑해주고, 그 컨트롤러를 실행시켜 응답을 받는 것을 알 수 있다.

또한 실무에서 사용되는 대부분의 컨트롤러는 @RequestMapping을 사용한다. 컨트롤러의 메서드에 @RequestMapping 어노테이션을 붙이면 해당 URL이 호출될 때 이 메서드가 호출된다. 어노테이션을 기반으로 동작하므로 메서드의 이름을 임의로 지을 수 있다.

@RequestMapping이 받는 옵션

이제는 @RequestMapping이 받을 수 있는 옵션에 대해서 알아보도록 하자.

RequestMapping은 기본적으로 같은 url과 모든 옵션이 같을 수 없다.

이렇게 지정하는 옵션은 handler에서 매핑하는 기준이 된다.

value, method, params, headers, consumes, produces 중에서 하나라도 달라야지 올바르게 handler가 매핑해줄 수 있다.

value

value는 연결할 url을 말한다. 보통 호스트 주소와 포트 번호를 제외하고 api 설계 규약에 따라 이름을 짓는다. 다음과 같이 설정할 수 있다.

@RequestMapping(value = "/example")

value를 제외하고 다른 옵션이 주어지지 않을 경우에는 value =을 생략할 수도 있다.

@RequestMapping("/example")

Ant pattern을 적용한 url mapping도 가능하다.

예를 들어, 아래와 같이 매핑하면 /example/1/example/2/example/123 전부 다 연결할 수 있다.

@RequestMapping("/example/**")

{}사이에 변수를 넣어서 url을 매핑하는 것도 가능하다. 이렇게 매핑한 경우 메서드 인자로 @PathVariable을 id로 받아야만 한다.

@RequestMapping("/example/{id}")

method

RequestMethod로 명명되어 있는 아래 8가지 중 하나를 사용하는 것이 일반적이다.

public enum RequestMethod {
	GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

적용 예시는 다음과 같다.

@RequestMapping(method = RequestMethod.GET, value = "/example")

GET, POST, PUT, PATCH, DELETE 메서드에 대해서는 각각에 맞는 메서드 옵션이 적용된 어노테이션이 존재해서 다음과 같이 사용할 수 있다.

@GetMapping("/example")

이렇게 하면 코드가 간결해지고 가독성이 좋아져 많이 사용되는 기법이다.

params

params는 api url을 /example?id=1&password=2와 같이 전달하고 싶을 때 사용하는 것으로 params에 전달한 것과 일치하는 param이 붙으면 이 메서드로 매핑해준다.

@RequestMapping(method = RequestMethod.GET, value = "/example", params = {"id", "password"})
public ResponseEntity<Example> getExample(@RequestParam("id") int paramId, @RequestParam("password") String password) {
   Example example = new Example("example" + paramId + password);
   return ResponseEntity.ok(example);
}

headers

header의 정보를 전달해주는 옵션이다. 예를 들어 아래와 같이 content-type을 지정해준 경우

@PostMapping(value = "/example/{text}", headers = "content-type=text/plain")
public ResponseEntity<Example> getExample3(@PathVariable("text") String text) {
   Example example = new Example("example" + text);
   return ResponseEntity.ok(example);
}

produces

response의 accept-request header가 특정 옵션으로 반환될 것을 지정하는 옵션이다.

응답이 무엇인지 미리 예측하는 것이 불가능하기 때문에 같은 url로 요청이 들어왔을 때 produces 옵션으로 구분해서 실행하는 것은 불가능하다.

그리고 @RestController에서 ResponseEntity를 반환하는 경우에는 기본으로 application/json으로 값이 지정되어 있기 때문에 특정 옵션을 설정할 수 없다.

produces는 필수 옵션이 아니므로 따로 지정해주지 않아도 application/json으로 기본 값이 전달되거나 와일드카드로 값이 전달된다.

@GetMapping(value = "/test", produces = MediaType.TEXT_PLAIN_VALUE)
public String getExample() {
    return "example";
}

위와 같이 사용할 수 있다.

consumes

request의 content-type request header가 일치하는 것을 찾는 옵션이다.

요청에 들어오는 인자 값이 무엇인지에 따라서 같은 url, method임에도 구분이 가능하다.

하지만 @RequestBody와 @ModelAttribute의 경우 HttpMessageConverter에서 객체로 변환하는 과정을 지나며 기본 값으로 application/json이 지정되어 있기 때문에 특정 옵션을 줄 수 없다.

consumes는 필수 옵션이 아니므로 지정해주지 않아도 된다.

이 경우 @RequestBody@ModelAttribute는 application/json이 기본 값, @RequestParam@PathVariable은 text/plan이 기본 값으로 전달된다.



메인 메서드에 static이 빠진 이유

package com.fastcampus.ch2;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

//1. 원격 호출 가능한 프로그램으로 등록
@Controller
public class Hello {
	int iv = 10;
	static int cv = 20;


	//2. URL과 메서드를 연결
	@RequestMapping("/hello")
	public void main() { //인스턴스 메서드 - iv, cv를 둘다 사용 가능

		System.out.println("Hello ");
		System.out.println(cv);	//	ok
		System.out.println(iv);	// ok
	}

	public static void main2() {  //static 메서드 - cv만 사용 가능
		System.out.println(cv);	//ok
//		System.out.println(iv);	//error

	}
}

메인 메서드에서 static이 안들어가있는 이유?

인스턴스 메서드는 객채 생성 후 호출 가능 URL로 원격프로그램을 생성하면 Tomcat 내부에서 객체를 생성하기에 인스턴스 메서드 임에도 호출이 가능하다. 또한 인스턴스 메서드일 경우 static메서드와 인스턴스 모두 사용 가능하지만 static 메서드 일 경우 인스턴스 메서드는 사용할 수 없기에 인스턴스 메서드를 사용하는 편이 더 낫다.

정확히 말하자면, 인스턴스 메서드와 static 메서드는 클래스의 변수에 접근할 때 다른 규칙을 따른다

인스턴스 메서드는 객체의 인스턴스를 생성한 후에 호출되는 메서드이며, 해당 클래스의 인스턴스 변수에 직접 접근할 수 있다.

인스턴스 메서드 내에서는 인스턴스 변수와 static 변수 모두에 접근이 가능하다.

그래서 main() 메서드에서 ivcv 모두에 접근할 수 있었다.

반면에 static 메서드는 객체의 인스턴스 없이 호출되는 메서드이기 때문에 인스턴스 변수에 직접 접근할 수 없다.

static 메서드에서는 오직 static 변수에만 접근할 수 있다.

따라서 main2() 메서드에서는 cv 변수에는 접근할 수 있지만 iv 변수에는 접근할 수 없다.

요약하자면:

  • 인스턴스 메서드는 인스턴스 변수와 static 변수에 모두 접근할 수 있다.
  • static 메서드는 static 변수에만 접근할 수 있고, 인스턴스 변수에는 직접 접근할 수 없다.

따라서, 변수를 사용할 수 있는지 여부는 해당 메서드가 인스턴스 메서드인지 static 메서드인지에 따라 달라진다.

private 으로 변수 호출이 가능할까?

package com.fastcampus.ch2;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

//1. 원격 호출 가능한 프로그램으로 등록
@Controller
public class Hello {
	int iv = 10;
	static int cv = 20;


	//2. URL과 메서드를 연결
	@RequestMapping("/hello")
	private void main() { //인스턴스 메서드 - iv, cv를 둘다 사용 가능

		System.out.println("Hello ");
		System.out.println(cv);	//	ok
		System.out.println(iv);	// ok
	}

	public static void main2() {  //static 메서드 - cv만 사용 가능
		System.out.println(cv);	//ok
//		System.out.println(iv);	//error

	}
}

가능하다. @RequestMapping("/hello") : 해당 메서드들을 외부에서 호출 가능하게 하겠다. 라는 뜻. 즉, URL과 외부 메서드들을 연결했기 때문에 가능하다. 또한 접근제어자와 상관없이 호출 가능하다.

외부 메서드에서 호출이 가능한가

package com.fastcampus.ch2;

public class Main {
	public static void main(String[]arge) {
		Hello hello = new Hello();
		hello.main();
	}
}

불가능하다. “The method main() from the type Hello is not visible” Error - Private 라서 외부 호출이 불가 하다!

하지만 hello 파일에서는 호출이 가능하지 않았나? 👇🏻 Reflection API 를 사용 했기 때문이다

Reflection API란? 클래스 정보를 얻고 다룰 수 있는 강력한 기능 제공 java.lang.reflect 패키지 제공

private 메서드를 호출해보자

package com.fastcampus.ch2;

import java.lang.reflect.Method;

public class Main {
	public static void main(String[]arge)throws Exception {
//		Hello hello = new Hello();
//		hello.main();

		// Reflection API - 클래스 정보를 얻고 다룰 수 있는 강력한 기능 제공
		// java.lang.reflect 패키지 제공
		// Hello 클래스의 Class객체 (클래스 정보를 담고 있는 객체)를 얻어온다.
		Class helloClass = Class.forName("Com,fastcapus.ch2.Hello");

		//class 객체가 가진 정보로 객체 생성
		Hello hello = (Hello)helloClass.newInstance(); //반환타입이 obj이기 때문에 형변환해줘야 한다.
		Method main = helloClass.getDeclaredMethod("main"); //main 메서드 정보 가져옴
		main.setAccessible(true); //private 인 main()을 호출 가능하게 한다.

		main.invoke(hello); //hello.main()

	}
}

spring 에서 Reflection API를 자주 쓴다고 하니 기억해두자!

Leave a comment