package com.example.obejctmapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ObjectMapperApplicationTests {
@Test
void contextLoads() throws JsonProcessingException {
System.out.println("--------------");
// Text JSON -> Object
// Object -> Text JSON
// controller req json(text) -> object
// response object -> json(text)
var objectMapper = new ObjectMapper();
// object -> text
// object mapper get 메소드를 활용
var user = new User("jun",20,"010-1111-2222");
var text = objectMapper.writeValueAsString(user);
System.out.println(text);
// text -> object
// object mapper 는 디폴트 생성자를 필요로 한다.
var objectUser = objectMapper.readValue(text, User.class);
System.out.println(objectUser);
}
}
objectMapper는 object 타입을 text로 변환할 때 get메소드를 활용한다. 멤버 변수의 get메소드 이외에 get-으로 시작하는 메소드가 있다면 변환 과정에서 에러가 발생할 수 있으니 주의하자.
text를 object로 변환 할때는 디폴트 생성자가 반드시 있어야 동작한다.
package com.example;
import com.example.dto.Car;
import com.example.dto.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
User user = new User();
user.setName("홍길동");
user.setAge(10);
Car car1 = new Car();
car1.setName("K5");
car1.setCarNumber("11가 1111");
car1.setType("sedan");
Car car2 = new Car();
car2.setName("Q5");
car2.setCarNumber("22가 2222");
car2.setType("SUV");
List<Car> carList = Arrays.asList(car1, car2);
user.setCars(carList);
System.out.println(user);
String json = objectMapper.writeValueAsString(user);
System.out.println(json);
JsonNode jsonNode = objectMapper.readTree(json);
String _name = jsonNode.get("name").asText();
int _age = jsonNode.get("age").asInt();
System.out.println("name : " + _name);
System.out.println("age : " + _age);
JsonNode cars = jsonNode.get("cars");
ArrayNode arrayNode = (ArrayNode) cars;
List<Car> _cars = objectMapper.convertValue(arrayNode, new TypeReference<List<Car>>() {});
System.out.println(_cars);
ObjectNode objectNode = (ObjectNode) jsonNode;
objectNode.put("name","jun");
objectNode.put("age",20);
System.out.println(objectNode.toPrettyString());
}
}
ObjectMapper의 readTree 메소드를 통해 문자열 형태의 JSON 데이터를 JsonNode로 변경할 수 있다. JsonNode에서 get 메소드를 통해 값을 추출할 수 있지만, Array 타입과 같은 경우에는 새로운 노드로 취급하므로 바로 사용이 불가하다. 따라서 다시 JsonNode로 추출하여 형변환 한 후 convertValue로 원하는 클래스 타입으로 변경한다.
JsonNode는 내부적으로 값을 변경할 수 없지만 ObjectNode로 형변환 후 put 메소드를 활용하면 값을 업데이트 하는 방식으로 수정이 가능하다.
이러한 방식을 잘 활용하기 위하면 JSON 표준 스펙을 잘 정의하고 사전에 미리 인지하고 있어야 한다.
원하는 프로젝트 명으로 설정 및 JAVA, Gradle,, JDK, Jar 파일을 설정한다.
의존성 설정에서 Spring 웹 어플리케이션 개발을 위한 Spring Web을 선택
controller 패키지를 생성하고 controller 역할을 수행할 클래스를 생성한다. class에 @RestController, @RquestMapping 어노테이션을 추가한다. @RestController: 해당 클래스를 REST API를 처리하는 컨트롤러로 설정 @RequestMapping : 클라이언트 요청을 받을 URI를 지정 ex) http://localhost:8080/api 추가로 해당 컨트롤러에서 GET방식으로 클라이언트 요청을 처리할 메소드를 생성 @GetMapping : 해당 메소드는 GET 처리 및 URI 지정 ex) GET http://localhost:8080/api/hello251
controller로 사용할 클래스을 생성한다. 이 후 @RestController, @RequestMapping 어노테이션을 추가
1. @GetMapping 메소드에 @GetMapping를 추가하면 해당 메소드는 GET 방식으로 요청을 받는다. 인자의 기본값은 path이며, path = “stringValue” 사용 시 명시적으로 설정 가능
2. @RequestMapping 메소드에 @RequestMapping를 추가 시 모든 방식의 메소드로 요청을 받을 수 있음 필요에 따라 인자로 method = RequestMethod.GET 와 같이 사용할 **HTTP Method**를 직접 지정할 수 있다.
3. PathVariable URI에서 변화 하는 값은 PathVariable로 지정하여 한 URI로 처리가 가능하다. 맵핑 어노테이션에서@GetMapping("/path-variable/{name}") 와 같이 **({)대괄호**로 감싸주면 PathVariable로 지정이 된다.
일반적으로 URI의 지정한 PathVariable와 메소드 안에서 사용할 변수명이 동일하게끔 사용하지만, 필요에 따라 @PathVariable(name="name") String pathName 와 같이 어노테이션 name 속성에 PathVariable을 지정하면 다른 변수명으로 사용이 가능하다.
4. QueryParam URI중 ? 뒤에 key=value의 쌍을 말한다. 여러 개 일 경우 &를 구분자로 사용한다.
1. @RequestParam와 Map<String,String>을 이용하여 임의의 쿼리 파라미터를 받을 수 있다. 2. @RequestParam를 각각 지정하여 지정한 쿼리 파라미터만을 받을 수 있다. 3. DTO 클래스를 만들어 파싱할 쿼리 파라미터 관리할 수 있다. 이 때는 @RequestParam를 사용하지 않는다.
DTO(Data Transfer Object)는 프로세스 간 데이터를 전달하는 객체를 의미합니다. Spring에서는 주로 Controller와 Client 간의 데이터 교환을 위해 사용됩니다.
DTO의 특징: - 순수하게 데이터를 저장하고 전달하는 목적으로만 사용 - 비즈니스 로직을 포함하지 않고 getter/setter 메서드만 포함 - 계층간 데이터 전달을 위한 객체로써 데이터 은닉과 캡슐화를 위해 사용
위 예제에서 UserRequest 클래스는 클라이언트로부터 받은 name, email, age 데이터를 담아 전달하는 DTO 역할을 수행합니다.
package com.example.hello.dto;
public class UserRequest {
private String name;
private String email;
private int age;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserRequest{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
POST API
JSON JSON(JavaScript Object Notation)은 데이터를 쉽게 교환하고 저장하기 위한 텍스트 기반의 데이터 교환 표준입니다. 이는 자바스크립트의 객체 표기법에서 파생된 부분 집합으로, 인간이 읽고 쓰기 쉽고 기계가 분석하고 생성하기도 쉬운 경량 데이터 교환 형식입니다
JSON 구조 JSON 데이터는 **키-값 쌍**으로 구성되며, 중괄호 **`{}`**로 둘러싸여 표현됩니다. 여러 데이터는 쉼표 **`,`**로 구분됩니다
JSON 데이터 타입 JSON에서 사용할 수 있는 기본 데이터 타입은 다음과 같습니다:
- 숫자(number): 정수 또는 부동소수점 숫자로, 큰 따옴표 없이 사용됩니다. - 문자열(string): 큰 따옴표로 묶여 사용됩니다. - 불리언(boolean): true 또는 false로 표현됩니다. - 객체(object): 중괄호 `{}`로 묶여 사용됩니다. - 배열(array): 대괄호 `[]`로 묶여 사용됩니다. - NULL: null로 표현됩니다
JSON 객체 예시
{
"name": "John",
"age": 30,
"city": "New York"
}
ObjectMapper
ObjectMapper는 Jackson 라이브러리의 핵심 클래스로, Java 객체와 JSON 간의 변환을 담당합니다. 이 클래스는 직렬화와 역직렬화 두 가지 주요 기능을 제공합니다.
1. 직렬화 (Serialization) - Java 객체를 JSON으로 변환: `writeValueAsString()` 메서드를 사용하여 Java 객체를 JSON 문자열로 변환합니다. 이 과정은 객체의 필드를 JSON의 키-값 쌍으로 매핑합니다
2. 역직렬화 (Deserialization) - JSON을 Java 객체로 변환: `readValue()` 메서드를 사용하여 JSON 문자열을 Java 객체로 변환합니다. 이 과정은 JSON의 키-값 쌍을 Java 객체의 필드로 매핑합니다. 기본 생성자를 통해 객체를 생성한 후, 리플렉션을 사용하여 필드에 값을 할당합니다
ObjectMapper를 사용하여 Java 객체를 JSON으로 변환할 때, 기본적으로는 camelCase로 변환됩니다.
동작 과정 1. JSON 파싱: JSON 데이터를 파싱하여 키-값 쌍을 추출합니다. 2. 객체 생성: 기본 생성자를 통해 빈 Java 객체를 생성합니다. 3. 리플렉션 사용: 리플렉션을 통해 객체의 필드를 탐색하고, JSON의 키와 일치하는 필드에 값을 할당합니다 4. 결과 반환: 완성된 Java 객체를 반환합니다.
기본적으로 RequestBody를 파싱하기 위하여 @RequestBody 어노테이션을 추가해야한다.
1. Map<String, Object> 메소드에 @PostMapping를 추가하면 해당 메소드는 POST 방식으로 요청을 받는다. JSON타입의 RequestBody를 파싱하기 위해 Map<String, Object>를 사용할 수 있다.
2. DTO 클래스 데이터 클래스를 만들어 RequestBody를 파싱할 데이터를 지정할 수 있다. JSON 형태의 RequestBody를 ObjectMapper가 DTO 객체로 변환 할 때 JAVA 클래스 변수명은 카멜 케이스로 JSON은 스네이크 케이스로 되어 있어 제대로 동작하지 경우에는 @JsonProperty 어노테이션을 사용하여 변수와 매칭될 JSON Key값을 직접 지정할 수 있다.
기존 뼈대 (클래스)는 유지하되, 이후 필요한 형태로 꾸밀 때 사용한다. 확장이 필요한 경우 상속의 대안으로 활용 된다.
SOLID중에서 개방패쇄 원칙과 의존 역전 원칙을 따른다.
- 실습
package com.company.gof.decorator;
public interface ICar {
int getPrice();
void showPrice();
}
package com.company.gof.decorator;
public class Audi implements ICar{
private int price;
public Audi(int price){
this.price = price;
}
@Override
public int getPrice() {
return price;
}
@Override
public void showPrice() {
System.out.println("audi의 가격: "+ this.price);
}
}
package com.company.gof.decorator;
public class AudiDecorator implements ICar{
protected ICar audi;
protected String modelName;
protected int modelPrice;
public AudiDecorator(ICar audi, String modelName, int modelPrice){
this.audi = audi;
this.modelName = modelName;
this.modelPrice = modelPrice;
}
@Override
public int getPrice() {
return audi.getPrice() + modelPrice;
}
@Override
public void showPrice() {
System.out.println(modelName+" 가격 : "+getPrice());
}
}
package com.company.gof.decorator;
public class A3 extends AudiDecorator {
public A3(ICar audi, String modelName) {
super(audi, modelName, 1000);
}
}
package com.company.gof.decorator;
public class A4 extends AudiDecorator {
public A4(ICar audi, String modelName) {
super(audi, modelName, 2000);
}
}
package com.company.gof.decorator;
public class A5 extends AudiDecorator {
public A5(ICar audi, String modelName) {
super(audi, modelName, 3000);
}
}
package com.company.gof;
import com.company.gof.decorator.*;
public class Main {
public static void main(String[] args) {
ICar audi = new Audi(1000);
audi.showPrice();
ICar audi3 = new A3(audi, "A3");
audi3.showPrice();
ICar audi4 = new A4(audi, "A4");
audi4.showPrice();
ICar audi5 = new A5(audi, "A5");
audi5.showPrice();
}
}
Proxy Class를 통해서 대신 전달 하는 형태로 설계되며, 실제 Client는 Proxy로 부터 결과를 받는다.
Cache의 기능으로도 활용 가능
SOLID중에서 개방폐쇄 원칙과 의존 역전 원칙을 따른다.
-브라우저 예제
package com.company.gof.proxy;
public class Html {
private String url;
public Html(String url){
this.url = url;
}
}
package com.company.gof.proxy;
public interface IBrowser {
Html show();
}
package com.company.gof.proxy;
public class Browser implements IBrowser{
private String url;
public Browser(String url){
this.url = url;
}
@Override
public Html show() {
System.out.println("browser loading html from: "+url);
return new Html(url);
}
}
package com.company.gof;
import com.company.gof.proxy.Browser;
public class Main {
public static void main(String[] args) {
Browser browser = new Browser("www.naver.com");
for(int i=0;i<9;i++)
browser.show();
}
}
- 결과 화면
❖매번 show() 함수를 실행할 때 마다 로딩을 해줘야 한다.
- Proxy 패턴을 이용한 캐시 기능
package com.company.gof.proxy;
public class BrowserProxy implements IBrowser{
private String url;
private Html html;
public BrowserProxy(String url){
this.url = url;
}
@Override
public Html show() {
if(html == null){
this.html = new Html(url);
System.out.println("BrowserProxy loading html from: "+url);
}
System.out.println("BrowserProxy use cache html:"+url);
return html;
}
}
캐싱을 하기 위해 속성으로 html을 추가하고, html을 가지고 있지 않으면 로딩을 하고 가지고 있으면 그것을 리턴한다.
package com.company.gof;
import com.company.gof.proxy.Browser;
import com.company.gof.proxy.BrowserProxy;
import com.company.gof.proxy.IBrowser;
public class Main {
public static void main(String[] args) {
// Browser browser = new Browser("www.naver.com");
// for(int i=0;i<9;i++)
// browser.show();
IBrowser browser = new BrowserProxy("www.naver.com");
for(int i=0;i<5;i++)
browser.show();
}
}
-결과 화면
-AOP(Aspect Oriented Programming )기능
관심사의 분리라는 의미로 각각의 비즈니스 로직에서 부가 기능으로 생각되어지는 공통의 모듈들을 분리하여 관리하여 유지보수에 대한 용이성과 비즈니스 로직을 좀 더 이해하기 쉽게 해준다.
package com.company.gof.aop;
import com.company.gof.proxy.Html;
import com.company.gof.proxy.IBrowser;
public class AopBrowser implements IBrowser {
private String url;
private Html html;
private Runnable before;
private Runnable after;
public AopBrowser(String url, Runnable before, Runnable after){
this.url = url;
this.before = before;
this.after = after;
}
@Override
public Html show() {
before.run();
if(html==null){
this.html = new Html(url);
System.out.println("AopBrowser html loading from: "+url);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
after.run();
System.out.println("AopBrowser html cache: "+url);
return null;
}
}
show 함수가 너무 빠를 수 있기 때문에 시간을 측정하기 위해 1.5초의 지연시간과 Runable before, after를 추가했다.
package com.company.gof;
import com.company.gof.aop.AopBrowser;
import com.company.gof.proxy.IBrowser;
import java.util.concurrent.atomic.AtomicLong;
public class Main {
public static void main(String[] args) {
//동시성 문제
AtomicLong start = new AtomicLong();
AtomicLong end = new AtomicLong();
IBrowser aopBrowser = new AopBrowser("www.naver.com",
()->{
System.out.println("before");
start.set(System.currentTimeMillis());
},
()->{
long now = System.currentTimeMillis();
end.set(now - start.get());
}
);
aopBrowser.show();
System.out.println("loading time : "+ end.get());
aopBrowser.show();
System.out.println("loading time : "+ end.get());
}
}
package com.company.gof.adapter;
public interface Electronic110V {
public void powerON();
}
package com.company.gof.adapter;
public interface Electronic220V {
public void connect();
}
package com.company.gof.adapter;
public class Hairdryer implements Electronic110V{
@Override
public void powerON() {
System.out.println("Hair Dryer Power On By 110V");
}
}
package com.company.gof.adapter;
public class Cleaner implements Electronic220V{
@Override
public void connect() {
System.out.println("Cleaner On by 220V");
}
}
package com.company.gof.adapter;
public class AirConditioner implements Electronic220V {
@Override
public void connect() {
System.out.println("AirConditioner ON by 220V");
}
}
package com.company.gof.adapter;
public class SocketAdapter implements Electronic110V{
// 변환 대상
private Electronic220V electronic220V;
public SocketAdapter(Electronic220V electronic220V){
this.electronic220V = electronic220V;
}
@Override
public void powerON() {
electronic220V.connect();
}
}
package com.company.gof;
import com.company.gof.adapter.*;
public class Main {
public static void main(String[] args) {
Hairdryer hairdryer = new Hairdryer();
connect(hairdryer);
Cleaner cleaner = new Cleaner();
// 사용불가 콘센트가 맞지 않아서
// connect(cleaner);
Electronic110V adapter = new SocketAdapter(cleaner);
connect(adapter);
AirConditioner airConditioner = new AirConditioner();
Electronic110V airAdapter = new SocketAdapter(airConditioner);
connect(airAdapter);
}
// 콘센트
public static void connect(Electronic110V electronic110V){
electronic110V.powerON();
}
}
package com.company.gof.singleton;
public class SocketClient {
private static SocketClient socketClient = null;
private SocketClient(){
}
public static SocketClient getInstance(){
if(socketClient == null){
socketClient = new SocketClient();
}
return socketClient;
}
public void connect(){
System.out.println("connect");
}
}
//A.java
package com.company.gof.singleton;
public class A {
private SocketClient socketClient;
public A(){
this.socketClient = SocketClient.getInstance();
}
public SocketClient getSocketClient(){
return this.socketClient;
}
}
//B.java
package com.company.gof.singleton;
public class B {
private SocketClient socketClient;
public B(){
this.socketClient = SocketClient.getInstance();
}
public SocketClient getSocketClient(){
return this.socketClient;
}
}
//main.java
package com.company.gof;
import com.company.gof.singleton.A;
import com.company.gof.singleton.B;
import com.company.gof.singleton.SocketClient;
public class Main {
public static void main(String[] args) {
// write your code here
A a = new A();
B b = new B();
SocketClient aClient = a.getSocketClient();
SocketClient bClient = b.getSocketClient();
System.out.println("두개의 객체가 동일한가?");
System.out.println(aClient.equals(bClient));
}
}