Spring 4 기반의 RESTful Web Service 구현 Oct. 2015 Youn-Hee Han LINK@KOREATECH http://link.koreatech.ac.kr
RESTful Server LINK@KOREATECH
Spring 프로젝트 내 pom.xml 수정 REST Web Service 구현을 위한 pom.xml 수정 객체를 XML/JSON로 변환하거나 그 역변환을 위한 라이브러리(의존성) 추가 … <!-- XML and JSON --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.6.2</version> </dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <artifactId>jackson-databind</artifactId> </dependencies> LINK@KOREATECH
웹 서비스 Server 구축 (Spring 기반) Resource 정보 구축 온도 센서 정보를 Resource로 활용 MySQL 활용 Temperature 테이블 내 다음과 같은 정보 저장 온도 센서 HTTP Reqeuest HTTP Response 온도 자원을 활용하는 웹 서비스 Client 온도 자원을 구축하고 있는 자원 서버 + 웹 서비스 Server 구축 (Spring 기반) LINK@KOREATECH
Resource 정보 구축 온도 센서 정보를 Resource로 활용 현재 사용중인 DB 스키마 (예: wsc)에 Temperature 테이블 생성 및 가상 온도 정보 입력 프로젝트 소스 내 다음 파일 참고 src/main/resources/common/rest.sql DROP TABLE IF EXISTS `temperature`; CREATE TABLE `temperature` ( `ID` int(32) unsigned NOT NULL AUTO_INCREMENT, `sensor_id` varchar(45) NOT NULL, `temperature` float NOT NULL, `datetime` datetime NOT NULL, `location` varchar(500) NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO `temperature` VALUES (1, 'temp1', 32.4, '2015-05-15 08:18:24', '1st Floor, 4th Engineering Building, KoreaTech'), (2, 'temp2', 34.9, '2015-05-16 08:10:54', '3st Floor, 2th Engineering Building, KoreaTech'); LINK@KOREATECH
Domain 객체 클래스 구성 Temperature 클래스 구성 온도 정보를 담아서 전달시킬 수 있는 기본 클래스 웹 서비스 Server가 Client로 전달할 때 Json 포맷으로 변환됨 프로젝트 소스 내 다음 파일 참고 src/main/java/koreatech/cse/domain/rest/Temperature.java package koreatech.cse.domain.rest; import java.sql.Date; public class Temperature { private int id; private String sensorId; private float temperature; private Date datetime; private String location; public int getId() { return id; } … LINK@KOREATECH
Repository 객체 클래스 구성 MyBatis기반 TemperatureMapper 인터페이스 구성 src/main/java/koreatech/cse/repository/rest/TemperatureMapper.java package koreatech.cse.repository.rest; … @Repository public interface TemperatureMapper { @Insert("INSERT INTO TEMPERATURE (SENSOR_ID, TEMPERATURE, DATETIME, LOCATION) VALUES (#{sensorId}, #{temperature}, #{datetime}, #{location})") @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = int.class) void insert(Temperature temperature); @Update("UPDATE TEMPERATURE SET TEMPERATURE = #{temperature}, DATETIME = #{datetime}, LOCATION = #{location} WHERE ID = #{id}") void update(Temperature temperature); @Select("SELECT * FROM TEMPERATURE WHERE ID = #{id}") Temperature findOne(@Param("id") int id); @Delete("DELETE FROM TEMPERATURE WHERE ID = #{id}") void delete(@Param("id") int id); @Select("SELECT * FROM TEMPERATURE WHERE SENSOR_ID = #{sensorId}") Temperature findOneBySensorId(@Param("sensorId") String sensorId); @Select("SELECT * FROM TEMPERATURE WHERE LOCATION LIKE CONCAT('%', #{location}, '%')") List<Temperature> findByLocation(@Param("location") String location); } LINK@KOREATECH
RestController 구성 Spring 4 기반의 RestController 구성 컨트롤러이므로 @RequestMapping 을 활용하여 접근 패스 지정 {Context Root}/thermometer 이전에 생성한 Repository 객체를 @Inject 로 주입받음 프로젝트 소스 내 다음 파일 참조 src/main/java/koreatech/cse/controller/rest/ TemperatureRestController.java package koreatech.cse.controller.rest; … @RestController @RequestMapping("/thermometer") public class TemperatureRestController { @Inject private TemperatureMapper temperatureMapper; … } LINK@KOREATECH
RestController 구성 GET 서비스 – 1 Path: {Context Root}/thermometer/temperature/{sensorId} 센서 아이디를 받아서 해당 센서의 센싱 정보를 JSON 포맷으로 전달 서비스 요청 예 http://localhost:8080/thermometer/temperature/temp1 센서 ID에 해당하는 ‘temp1’은 PathVariable 형태로 활용됨 @Transactional @RequestMapping(value="/temperature/{sensorId}", method=RequestMethod.GET, produces = "application/json") public ResponseEntity<Temperature> temperature(@PathVariable("sensorId") String sensorId) { Temperature temperature = temperatureMapper.findOneBySensorId(sensorId); if (temperature == null) { System.out.println("Temperature sensor with id (" + sensorId + “) is not found"); return new ResponseEntity<Temperature>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Temperature>(temperature, HttpStatus.OK); LINK@KOREATECH
RestController 구성 GET 서비스 – 2 Path: {Context Root}/thermometer/xml/temperature/{sensorId} 센서 아이디를 받아서 해당 센서의 센싱 정보를 XML 포맷으로 전달 서비스 요청 예 http://localhost:8080/thermometer/xml/temperature/temp1 @Transactional @RequestMapping(value="/xml/temperature/{sensorId}", method=RequestMethod.GET, produces="application/xml") public ResponseEntity<Temperature> temperatureXml(@PathVariable("sensorId") String sensorId) { Temperature temperature = temperatureMapper.findOneBySensorId(sensorId); if (temperature == null) { System.out.println("Temperature sensor with id " + sensorId + " is not found"); return new ResponseEntity<Temperature>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Temperature>(temperature, HttpStatus.OK); } LINK@KOREATECH
RestController 구성 GET 서비스 – 3 Path: {Context Root}/thermometer/temperature/location/{location} 센서 위치 정보를 받아서 해당 위치 정보를 지니고 있는 센서들의 센싱 정보를 JSON 포맷으로 전달 서비스 요청 예 http://localhost:8080/thermometer/temperature/location/KoreaTech 위치 정보에 해당하는 ‘KoreaTech’는 PathVariable 형태로 활용됨 @Transactional @RequestMapping(value="/temperature/location/{location}", method=RequestMethod.GET, produces="application/json") public ResponseEntity<List<Temperature>> temperatureByLocation( @PathVariable("location") String location) { List<Temperature> temperatureList = temperatureMapper.findByLocation(location); if (temperatureList.size() == 0) { System.out.println("Temperature sensors with location of " + location + " are not found"); return new ResponseEntity<List<Temperature>>(HttpStatus.NOT_FOUND); } return new ResponseEntity<List<Temperature>>(temperatureList, HttpStatus.OK); LINK@KOREATECH
RestController 구성 GET 서비스 – 4 Path: {Context Root}/thermometer/xml/temperature/location/{location} 센서 위치 정보를 받아서 해당 위치 정보를 지니고 있는 센서들의 센싱 정보를 XML 포맷으로 전달 서비스 요청 예 http://localhost:8080/thermometer/xml/temperature/location/KoreaTech @Transactional @RequestMapping(value="/xml/temperature/location/{location}", method=RequestMethod.GET, produces="application/xml") public ResponseEntity<List<Temperature>> temperatureByLocationXml( @PathVariable("location") String location) { List<Temperature> temperatureList = temperatureMapper.findByLocation(location); if (temperatureList.size() == 0) { System.out.println("Temperature sensors with location of " + location + " are not found"); return new ResponseEntity<List<Temperature>>(HttpStatus.NOT_FOUND); } return new ResponseEntity<List<Temperature>>(temperatureList, HttpStatus.OK); } LINK@KOREATECH
RestController 구성 POST 서비스 (Create a Temperature Resource) Path: {Context Root}/temperature 새로운 센서 정보를 서버의 자원으로 생성 @Transactional @RequestMapping(value = "/temperature/", method = RequestMethod.POST) public ResponseEntity<Void> createTemperature(@RequestBody Temperature temperature, UriComponentsBuilder ucBuilder) { if (temperatureMapper.findOneBySensorId(temperature.getSensorId()) != null) { System.out.println("A temperature sensor with id (" + temperature.getSensorId() + ") already exists"); return new ResponseEntity<Void>(HttpStatus.CONFLICT); } temperatureMapper.insert(temperature); HttpHeaders headers = new HttpHeaders(); headers.setLocation( ucBuilder.path("/temperature/{sensorId}").buildAndExpand(temperature.getSensorId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); LINK@KOREATECH
RestController 구성 PUT 서비스 (Update a Temperature Resource) Path: {Context Root}/temperature/{sensorId} 기존 센서 정보를 업데이트 @Transactional @RequestMapping(value = "/temperature/{sensorId}", method = RequestMethod.PUT) public ResponseEntity<Void> updateTemperature(@PathVariable("sensorId") String sensorId, @RequestBody Temperature temperature) { Temperature storedTemperature = temperatureMapper.findOneBySensorId(sensorId); if (storedTemperature == null) { System.out.println("No temperature sensor with id (" + sensorId + " not found"); return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); } storedTemperature.setTemperature(temperature.getTemperature()); storedTemperature.setLocation(temperature.getLocation()); storedTemperature.setDatetime(temperature.getDatetime()); temperatureMapper.update(storedTemperature); return new ResponseEntity<Void>(HttpStatus.OK); LINK@KOREATECH
RestController 구성 DELETE 서비스 (Delete a Temperature Resource) Path: {Context Root}/temperature/{sensorId} 기존 센서 정보를 삭제 @Transactional @RequestMapping(value = "/temperature/{sensorId}", method = RequestMethod.DELETE) public ResponseEntity<Temperature> deleteTemperature(@PathVariable("sensorId") String sensorId) { Temperature storedTemperature = temperatureMapper.findOneBySensorId(sensorId); if (storedTemperature == null) { System.out.println("No temperature sensor with id (" + sensorId + " not found"); return new ResponseEntity<Temperature>(HttpStatus.NOT_FOUND); } temperatureMapper.delete(storedTemperature.getId()); return new ResponseEntity<Temperature>(HttpStatus.NO_CONTENT); LINK@KOREATECH
RESTful Client LINK@KOREATECH
RestClient 생성 Spring 4에서 지원하는 RestTemplate 클래스 활용 서버와 별개의 프로젝트로서 구성 org.springframework.web.client.RestTemplate 서버와 별개의 프로젝트로서 구성 콘솔창에서 임의의 폴더로 이동하여 아래 명령어 수행 새로운 프로젝트 폴더 (restful_client)를 IntelliJ 프로젝트로서 등록 mvn archetype:generate -DgroupId=koreatech.link -DartifactId=restful_client -Dpackage=koreatech.cse.rest.client -Dversion=1.0
Spring 프로젝트 내 pom.xml 수정 RestTemplate 활용을 위한 라이브러리(의존성) 추가 … <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.2.1.RELEASE</version> </dependency> </dependencies> LINK@KOREATECH
Spring 프로젝트 내 pom.xml 수정 객체를 XML/JSON로 변환하거나 그 역변환을 위한 라이브러리(의존성) 추가 … <!-- XML and JSON --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.6.2</version> </dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <artifactId>jackson-databind</artifactId> </dependencies> LINK@KOREATECH
Domain 객체 클래스 구성 Temperature 클래스 구성 온도 정보를 담아서 전달시킬 수 있는 기본 클래스 클라이언트 프로젝트 소스 내 다음 파일 참고 src/main/java/koreatech/cse/rest/client/domain/Temperature.java package koreatech.cse.rest.client.domain; import java.sql.Date; public class Temperature { private int id; private String sensorId; private float temperature; private Date datetime; private String location; public int getId() { return id; } … LINK@KOREATECH
Domain 객체 클래스 구성 Temperature 클래스내에 toString() 구성 IntelliJ의 코드 자동 생성 기능 사용 서버로 부터 받은 온도 정보 객체를 클라이언트에서 출력하기 위한 목적 @Override public String toString() { return "Temperature{" + "id=" + id + ", sensorId='" + sensorId + '\'' + ", temperature=" + temperature + ", datetime=" + datetime + ", location='" + location + '\'' + '}'; } LINK@KOREATECH
Domain 객체 클래스 구성 Temperature 클래스내에 toString() 구성 IntelliJ의 코드 자동 생성 기능 사용 서버로 부터 받은 온도 정보 객체를 클라이언트에서 출력하기 위한 목적 @Override public String toString() { return "Temperature{" + "id=" + id + ", sensorId='" + sensorId + '\'' + ", temperature=" + temperature + ", datetime=" + datetime + ", location='" + location + '\'' + '}'; } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 Spring 4 기반의 RestTemplate 활용 서버의 기본 URI 지정 package koreatech.cse.rest.client; import koreatech.cse.rest.client.domain.Temperature; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import java.net.URI; import java.util.Date; import java.util.List; public class App { public static final String REST_SERVICE_URI = "http://localhost:8080/thermometer"; … } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 GET 서비스 요청 – 1 private static void getTemperature() { System.out.println("Testing GET METHOD (1)----------"); RestTemplate restTemplate = new RestTemplate(); try { ResponseEntity<Temperature> temperatureResponseEntity = restTemplate.getForEntity(REST_SERVICE_URI + "/temperature/temp1", Temperature.class); Temperature temperature = temperatureResponseEntity.getBody(); System.out.println(temperature); } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 GET 서비스 요청 – 2 private static void getTemperatureXml() { System.out.println("Testing GET METHOD (2)----------"); RestTemplate restTemplate = new RestTemplate(); try { ResponseEntity<Temperature> temperatureResponseEntity = restTemplate.getForEntity(REST_SERVICE_URI + "/xml/temperature/temp1", Temperature.class); Temperature temperature = temperatureResponseEntity.getBody(); System.out.println(temperature); } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 GET 서비스 요청 – 3 private static void getTemperatureByLocation() { System.out.println("Testing GET METHOD (3)----------"); RestTemplate restTemplate = new RestTemplate(); try { ResponseEntity<List> listResponseEntity = restTemplate.getForEntity(REST_SERVICE_URI + "/temperature/location/KoreaTech", List.class); List<Temperature> temperatureList = listResponseEntity.getBody(); for (int i = 0; i < temperatureList.size(); i++) { System.out.println(temperatureList.get(i)); } } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 GET 서비스 요청 – 4 private static void getTemperatureByLocationXml() { System.out.println("Testing GET METHOD (4)----------"); RestTemplate restTemplate = new RestTemplate(); try { ResponseEntity<List> listResponseEntity = restTemplate.getForEntity(REST_SERVICE_URI + "/xml/temperature/location/KoreaTech", List.class); List<Temperature> temperatureList = listResponseEntity.getBody(); for (int i = 0; i < temperatureList.size(); i++) { System.out.println(temperatureList.get(i)); } } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 POST 서비스 요청 private static void createTemperature() { System.out.println("Testing POST METHOD----------"); RestTemplate restTemplate = new RestTemplate(); Temperature temperature = new Temperature(); temperature.setSensorId("temp3"); temperature.setTemperature((float)37.0); temperature.setDatetime(new Date()); temperature.setLocation("2nd Floor, 4th Engineering Building, KoreaTech"); try { URI uri = restTemplate.postForLocation( REST_SERVICE_URI + "/temperature/", temperature, Temperature.class); System.out.println("Location : " + uri.toString()); } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 PUT 서비스 요청 private static void updateTemperature() { System.out.println("Testing PUT METHOD----------"); RestTemplate restTemplate = new RestTemplate(); Temperature temperature = new Temperature(); temperature.setSensorId("temp1"); temperature.setTemperature((float)31.1); temperature.setDatetime(new Date()); temperature.setLocation("1st Floor, 4th Engineering Building, KoreaTech"); try { restTemplate.put(REST_SERVICE_URI + "/temperature/temp1", temperature); System.out.println("PUT METHOD - SUCCESS!"); } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } LINK@KOREATECH
RESTful Client – CRUD 요청 코드 DELETE 서비스 요청 private static void deleteTemperature() { System.out.println("Testing DELETE METHOD----------"); RestTemplate restTemplate = new RestTemplate(); try { restTemplate.delete(REST_SERVICE_URI + "/temperature/temp2"); System.out.println("DELETE METHOD - SUCCESS!"); } catch (HttpClientErrorException e) { System.out.println(e.getStatusCode() + ": " + e.getStatusText()); } LINK@KOREATECH
크롬 앱을 활용한 Rest Client 테스트 Advanced Rest Client 크롬 앱 활용 LINK@KOREATECH
크롬 앱을 활용한 Rest Client 테스트 GET 요청 테스트 LINK@KOREATECH
크롬 앱을 활용한 Rest Client 테스트 POST 요청 테스트 LINK@KOREATECH
크롬 앱을 활용한 Rest Client 테스트 PUT 요청 테스트 LINK@KOREATECH
크롬 앱을 활용한 Rest Client 테스트 DELETE 요청 테스트 LINK@KOREATECH