반응형

 


REST(Represetational State Transfer)

기술이 아닌 약속이다.

요청에 대한 JSON 또는 XML형식으로 자원의 상태 응답

자원(데이터)의 상태를 주고 받는 것

 

 

특징

Client, Server 구조 / 클라이언트는 요청하고, 서버는 응답하는 구조

Stateless / 서버에서는 클라이언트의 상태 값을 저장하지 않는다. (인증, 인가에 JWT 사용)

 

HTTP 프로토콜을 이용해서 JSON 데이터를 응답으로 주기 때문에 어플리케이션의 통합과 분리가 용이하다.

 

 

규약

자원의 식별 - URL로 자원을 구분, 식별

메세지를 통한 리소스 조작 - HTTP header에 자원의 타입을 명시, 데이터 일부를 메세지로 전달

자기 서술적 메세지 - 요청하는 데이터가 HTTP methods 중 무엇인지, 필요한 정보를 포함하여 전달

어플리케이션 - 데이터뿐만 아니라 추가 정보도 제공

 

 

URI 설계 패턴

URI - 특정 자원에 대한 URI주소로 접근. 변경될 수 있다.

URL - 특정 자원에 대한 위치. 변경될 수 없다.

 

슬래시로 계층을 구분

하이픈을 사용한다.

소문자가 적합

컬렉션은 복수형을 사용

 

CRUD 명시 하지 않는다.

파일형식, 경로를 명시하지 않는다.

 

 

HTTP Methods

클라이언트가 요청을하고 서버의 응답을 기다린다.

 

GET

자원의 취득

데이터 기준: 안정성 o, 멱등성 o (데이터가 변하지 않는다.)

body를 사용하지 않는다.

 

POST

자원의 생성

데이터 기준: 안전성 x, 멱등성 x (매번 데이터가 생성)

body를 사용한다.

 

PUT

자원의 수정

데이터 기준: 안전성 x, 멱등성 o (한번 수정되고 같은 결과)

body를 사용한다.

 

DELETE

자원의 삭제

데이터 기준: 안전성 x, 멱등성 o (한번 삭제되고 같은 결과)

body를 사용하지 않는다.

 

 

HTTP Status Code

100 - 처리가 계속 되고 있는 상태

200 - 성공

300 - 리다이렉션

400 - 클라이언트 에러

500 - 서버 에러

 

 

 


Java  -- Json

 

 

object-> String(json형태)

ObjectMapper writeValueAsString()

 

 

String(json형태) -> object

ObjectMapper readValue()

 

 

String(json형태) -> json

ObjectMapper readTree()

 

 

JsonNode  값을 변경할 수 없다.ObjectNode  값을 변경할 수 있다.

 

 


 

Jackson

package com.example.jacksontest.dto;

import lombok.*;

import java.util.List;

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private List<Item> items;

    @Getter
    @Setter
    @NoArgsConstructor
    @ToString
    @AllArgsConstructor
    public static class Item{
        private String itemName;
        private int count;

    }

}

 

 

 

{
  "id" : 1,
  "name" : "cha",
  "items" : [
    {
      "itemName" : "coin",
      "count" : 3
    },{
      "itemName" : "Phone",
      "count" : 1
    },{
      "itemName" : "cigarette",
      "count" : 5
    }
  ]

}

 

 

 

ObjectMapper objectMapper = new ObjectMapper();
// 읽기
User user = objectMapper.readValue(new File("src/main/resources/data.json"), User.class);
System.out.println(user);

// 쓰기
File f = new File("src/main/resources/data1.json");
objectMapper.writeValue(f, user);

// Json -> String 으로 변환
String s = objectMapper.writeValueAsString(user);

// String -> ObjectNode 로 변환
ObjectNode objectNode = (ObjectNode) objectMapper.readTree(s);
System.out.println(objectNode.toPrettyString());

// Json -> Java Pojo
JsonNode list = objectNode.get("items");
User user1 = objectMapper.readValue(s, new TypeReference<User>() {});
System.out.println(user1);

// key 를 이용해 하나씩 접근
System.out.println("key 로 접근: " + objectNode.get("name"));
System.out.println("Array 일 경우: " + objectNode.get("items"));

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

Spring boot 기준 Filter는 Spring 외부에 있지 않다.

톰캣이 내장되어 Filter도 Di가 가능.

 

 

interceptor는 dispatcher servlet과 controller 사이에서 요청과 응답을 가공, 처리하고자 할 때 사용한다.

 

 

 

interceptor와 filter

filter는 모든 요청과 공통기능에 적용할 사항

interceptor는 선별한 요청과 응답에 대해 적용할 세부 사항

 

 

interceptor 와 AOP

interceptor는 URL을 통해 적용할 메서드를 선별

AOP는 포인트컷을 통해 적용할 메서드를 선별

 

 

 


 

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Login {
    
}

메서드에서 사용할 Login 어노테이션

 

 

 

 


 

 

 

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;

            if (hm.hasMethodAnnotation(Login.class)){
                log.info("true");
                return true;
            }
        }
        log.info("false");
        return false;
    }

LoginInterceptor

인터셉터의 preHandle에서 URL 매핑  메서드에 Login어노테이션이 있는지 확인한다.

 

return true이면 controller로 진입하여 로직을 처리한다.

return false이면 controller로 진입하지 못한다.

 

 

HandlerInterceptor의 메서드

preHandler() - 컨트롤러 로직 처리 전

postHandler() - 컨트롤러 로직 처리 후

afterCompletion() - 뷰가 렌더링된 이후에 호출된다.

예외가 발생하면 postHandler()는 동작하지 않지만, afterCompletion()은 동작한다.

 

Object handler

URL 로 접근하는 매핑된 메서드 

HandlerMethod로 형변환하여 매핑 메서드 정보를 읽어낼 수 있다.

 


 

 

 


@Configuration
public class WebMvc implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor());
    }
}

작성한 LoginInterceptor 인터셉터를 WebMvcConfigurer 에 등록한다.

WebMvcConfigurer는 어플리케이션 구동시 실행된다.

 

 

@Slf4j
@Configuration
public class WebMvc implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")// 적용할 url 패턴
                .excludePathPatterns("/ic/get2")  // 배제할 url 패턴
                .order(1);
    }
}

인터셉터를 적용할 URL 패턴을 적용하고, 제외할 수 있다.

인터셉터의 적용 순서도 적용할 수 있다.

 

URL pattern

/** [ /모든 경로 ] 

ex) user/board/1

 

/*   [ /경로 하나 ] 

ex) /ic , /user, /board

 

 

 


 

@Controller
@RequestMapping("/ic")
public class Icontroller {

    @GetMapping("/get")
    @ResponseBody
    @Login
    public String test(){
        return "hello interceptor";
    }
}

Login체크를 진행할 컨트롤러 메서드에 @Login을 붙여준다.

 


 

 

 

interceptor를 이용한 로직은 목적에 따라 달라질 수 있다.

 

 

 

 

 

 

 

이전에는 Filter가 스프링 컨테이너에 등록되지 않아, Filter에서 DI를 이용하는 것이 불가능 했다.

(Spring boot 기준) Filter 구현체를 Bean으로 등록하므로, 현재는 가능하다.

 

 

@Component
@RequiredArgsConstructor
public class FirstFilter implements Filter {

   private final FilterDiTestService filterDiTestService;

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      System.out.println("필터 동작");
      filterDiTestService.test();
      chain.doFilter(request,response);

   }
}

 

 


 

 

 

 

 

반응형
반응형

 

 

resoruces에 위치한 data.json을 읽어야 한다.

 

 

 

{
  "hello": "123"
}

data.json의 내용은 다음과 같다.

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");
        BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));

        // br.readLine() 이 null 인지 검사할 때 한번 사용되므로 String 에 먼저 저장해둬야한다.
        String s = "";
        while((s = br.readLine()) != null){
            System.out.println(s);

        }
    }
}

스프링에서 resources 파일을 위해 사용되는 ClassPathResource를 사용하면 파일 경로를 src/main/resoruces/부터 바로 읽을 수 있다.

 

 

 

 

File file = new File("src/main/resources/data.json");

ClassPathResource를 안쓰고 경로를 전부 적어서 읽어도 되지만, 경로가 달라질 가능성이 있으므로 ClassPathResource 권장

 

 

 

 

사용법

ClassPathResoruce의 getInputStream() 메서드를 사용하여 InputStream으로 변환한다.

InputStream를 문자를 위한 Reader로 바꾸는 InputStreamReader로 한번 더 감싼다.

InputStreamReader를 성능 향상을 위한 보조스트림 BufferedReader로 한번 더 감싼다.

 

BufferedReader는 Line 단위로 읽는다.

 

출력을 할 때는 while(br.readLine() != null)을 하게 되면, 버퍼의 내용을 한번 소모하므로 String에 저장 대입 후 null인지 검사해야 한다.

while( (s = br.readLine())!= null)

 

 

 


String s = "";
StringBuilder sb = new StringBuilder();
while((s = br.readLine()) != null){
    sb.append(s);
}
ObjectMapper om = new ObjectMapper();
JsonNode jsonNode = om.readTree(sb.toString());
System.out.println(jsonNode);

 Json으로 사용할 경우

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");

        File file = new File("src/main/resources/data.json");
        File copyFile = new File("src/main/resources/data1.json");

        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(copyFile)));

        int size = 0;
        char[] arr = new char[1024];
        while((size = br.read(arr)) != -1){
            bw.write(arr, 0, size);
        }
        
        br.close();
        bw.flush();
        bw.close();
    }
}

파일을 복사할 경우

 

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");
        ClassPathResource resourceCopy = new ClassPathResource("data1.json");
        File file = new File(resourceCopy.getURI());

        BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));

        int size = 0;
        char[] arr = new char[1024];
        while((size = br.read(arr)) != -1){
            bw.write(arr, 0, size);
        }
        br.close();
        bw.flush();
        bw.close();
    }
}

ClassPathResource로 사용하면 build/resources/main/ 경로에 생성된다.

 

 

 

반응형
반응형

 

 

Rest API 서버에 요청을 해서 해당 값을 Controller에서 사용할 때

 

RestTemplate를 이용해서 요청을 보낼 수 있다.

* 현재는 Spring이 RestTemplate보다는 WebClient를 지원

 

@GetMapping("/test3")
public void test3() {
    // 요청할 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:8080")
            .path("/api/test")
            .build()
            .toUri();

    RestTemplate rt = new RestTemplate();
    // HTTP GET, 응답을 객체로 받기.
    String s1 = rt.getForObject(uri, String.class);

    // HTTP GET, 응답을 ResponseEntity 로 받기.
    ResponseEntity<String> responseEntity = rt.getForEntity(uri, String.class);
    System.out.println(responseEntity.getStatusCode());
    System.out.println(responseEntity.getBody());

    // HTTP GET, 응답을 JSON 을 받을 때
    // dto 객체로 받는다.
    ResponseEntity<User> responseEntity1 = rt.getForEntity(uri, User.class);


}

 

URL를 만든 후 RestTemplate를 이용하여 요청을 한다.

 

 

 

 

URI uri = UriComponentsBuilder
        .fromUriString("http://localhost:8080")
        .path("/api/test")
        .queryParam("name", "cha")
        .queryParam("age", 25)
        .build()
        .toUri();

UriComponentsBuilder의 queryParam() 메서드를 이용해 Parameter 값을 요청해줄 수 있다.

 

 

 

 

URI uri = UriComponentsBuilder
        .fromUriString("http://localhost:8080")
        .path("/api/test/{name}/{age}")
        .encode()
        .build()
        .expand("cha", 30)
        .toUri();

UriComponentsBuilder의 expand() 메서드를 이용해 PathVariable 값을 요청해줄 수 있다.

 

 

 

 

 

RestTemplate rt = new RestTemplate();
// HTTP POST, 서버에 객체를 보내서 생성하기
User user = new User("cha",33);
// uri, request 객체, response type
ResponseEntity<User> userResponseEntity = rt.postForEntity(uri, user, User.class);

POST 요청 

서버의 명세를 보고 맞게 dto 를 작성하면 된다.

 

 

 

 


RequestEntity<User> request = RequestEntity
        .post(uri)
        .contentType(MediaType.APPLICATION_JSON)
        .header("headerDummy", "asdf")
        .body(new User("user",22));

RestTemplate rt1 = new RestTemplate();
ResponseEntity<User> responseEntity = rt1.exchange(request, User.class);

HTTP header를 커스텀해서 요청을 보낼 수도 있다.

헤더를 추가할 경우 RestTemplate의 exchange() 메서드를 사용

 

 

 


kakao Oauth 요청

 

@GetMapping("/complete")
public String complete(@RequestParam String code){
    System.out.println(code);

    // uri
    URI uri = UriComponentsBuilder
            .fromUriString("https://kauth.kakao.com/oauth/token")
            .encode(StandardCharsets.UTF_8)
            .build()
            .toUri();

    // header
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.add("Content-type","application/x-www-form-urlencoded;charset=utf-8");

    // body
    MultiValueMap<String, String> map = new LinkedMultiValueMap();
    map.add("grant_type" ,"authorization_code" );
    map.add("client_id","================" );
    map.add("redirect_uri","http://localhost:8080/complete" );
    map.add("code", code );

    // Header + body
    HttpEntity<Map<String, String>> httpEntity = new HttpEntity(map, httpHeaders);

    RestTemplate rt = new RestTemplate();
    ResponseEntity<HashMap> responseEntity = rt.exchange(uri, HttpMethod.POST, httpEntity, HashMap.class);
    responseEntity.getHeaders().forEach((k, v) -> System.out.println(k + " " + v));
    responseEntity.getBody().forEach((k, v) -> System.out.println(k + " " + v));

    return "completion";

 

 

 

 

 

네이버 api 사용

 

URI uri = UriComponentsBuilder
        .fromUriString("https://openapi.naver.com/v1/search/book.json")
        .queryParam("query", word)
        .queryParam("display", 20)
        .queryParam("sort", "sim")
        .encode(StandardCharsets.UTF_8)
        .build()
        .toUri();

RequestEntity requestEntity = RequestEntity
        .post(uri)
        .header("X-Naver-Client-Id", "아이디")
        .header("X-Naver-Client-Secret", " 키 ")
        .body(" ");

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<NaverBook> responseEntity = restTemplate.exchange(requestEntity, NaverBook.class);

RequestEntity

 

HttpEntity, HttpHeaders를 사용하지 않고

header, body, contentType 을 좀 더 편하게 만들어줄 수 있다.

 

 


 

정리

 

UriComponentsBuilder: 요청할 uri를 만든다.

RequestEntity: 요청[HTTP header, body]을 만들고 RestTemplate으로 요청한다.
ResponseEntity: 응답[HTTP header, body]을 RestTemplate으로부터 받아 사용한다.
RestTemplate: HTTP 프로토콜을 이용해서 서버에 요청, 전달받은 응답을 사용할 수 있게 해준다.

 

 

반응형
반응형

 

 

JAVA SE 위에서 동작하는 기업형 응용 프로그램 (JAVA EE or SPRING)

현재는 EE가 유료이므로, SPRING을 주로 사용한다.

 

스프링의 대표 기능

MVC(DI), TRANSACTION(AOP), 인증과 권한(FILTER)

 

 


DI와 IOC컨테이너

객체를 생성해주고 인터페이스를 통해 조립해준다.

bean으로 생성하고, 인터페이스에 맞는 bean을 주입한다.

 

✔ IOC(inversion of control) 컨테이너라고 하는 이유

(1) 기존 프로그래밍

A -> B -> C -> D

A에서 B객체를 생성, B에서 C 객체를 생성, C에서 D객체를 생성

 

(2) 스프링 컨테이너

D -> C -> B -> A

D를 C에 주입, C를 B에 주입, B를 A에 주입

 

 

IOC 컨테이너의 사용

ApplicationContext - 스프링 설정 지시서를 읽는 인터페이스

구현체

ClassPathXmlApplicationContext ( " 스프링 bean 설정.xml ")  // 지시서가 xml로 설정되어있는 경우

AnnotationConfigApplicationContext ( ConfigClass.class ) // @Configuration 지정 클래스

 

 

컨테이너에서 Bean을 사용하기

(1) class로 꺼내기: 참조 인터페이스 타입 = context.getBean(클래스.class)

* 구현체.class 를 적으면 구현체를 가져오고, 인터페이스를 적으면 인터페이스에 지정된 구현체를 가져온다.

(2) id로 꺼내기: 참조 인터페이스 타입 = (bean 형변환 필요) context.getBean("bean name")

 

 

의존 객체로 Bean을 사용하기

@Autowierd  - 생성자 주입, 필드 주입, setter주입 

* 인터페이스에 맞는 구현체 Bean을 사용한다.

@Qualifier("id") 

* Configuration에서 bean 생성, class에 @Component도 달아준 경우 (인터페이스에 맞는 Bean이 여러 개 일 때)

id로 지정해서 하나의 Bean만 사용할 수 있게 한다.

id를 지정하지 않았다면 맨 앞 클래스명을 소문자로 사용

XML - 생성자 주입, 필드 주입, setter주입 설정 필요

 

 

컴포넌트 스캔을 통해 어노테이션 지정 객체 생성

xml에서 Bean으로 등록되지 않은 클래스는 사용할 수가 없다.

 

@Component는 xml에 Bean으로 등록되지 않아도 스프링 컨테이너에 Bean으로 등록시킬 수 있다.

대신 xml에 <context: component scan base-package=" 베이스 패키지 "> 컴포넌트 스캔으로 Bean의 생성이 필요하다.

<context:annotation-config> 도 필요 없어진다.

<context:annotation-config> : 등록된 Bean에 대해서 어노테이션(@Autowired, @Qualifier) 활성화

 

 

Xml사용 없이 스프링 컨테이너의 관리 

@ComponentScan(" {base-package 패키지명, 패키지명 } ")  // 컴포넌트 스캔으로 Bean을 생성

@Configuration  //  내, 외부 라이브러리 Bean을 생성, 주로 외부 라이브러리 클래스 Bean 생성

 

@Bean  // 함수명이 id로 사용

반환 타입 class

return new 구현체

@Configuration
@ComponentScan({"com.ky.test"})
class Config{
    @Bean
    public RClass rc() {
        return new RClass();
    }
}

RClass로 생성해놓고, RClass가 구현하고 있는 interface에 @Autowired를 지정하면 자동 바인딩된다.

 

 

@Bean과 @Component 차이

@Bean - 외부 라이브러리 클래스를 Bean으로 등록할 때

@Component - 직접 설계한 class를 Bean으로 등록할 때

 

 

 


AOP

주 업무 로직과 공통 관점 로직을 분리하여 유지보수성, 응집도를 높인다.

로그 처리, 트랜잭션 처리, 보안 처리

 

Advice : 공통 관점 로직 적용 시점 - Befor, After, Around.. (어느 위치에 끼어들건지)

JoinPoint : 주 업무 로직 

PointCut : 적용 대상 JoinPoint 선별(위빙)

Weaving : 주요 관점에 공통 관점을 엮는 행위 

Aspect (Advisor) : 객체, 공통 관점 사항, Advice + PointCut (어떤 대상에, 어느 위치에)

 

 "(주 업무1)JoinPoint"에 "(PointCut1)"을 "weaving"하겠다. 

 

 

프록시를 이용한 aop 구현

xml

<proxy bean class="...ProxyFactoryBean" >

    <property name="target" ref= " aop 적용할 객체 ">

    <property name="interceptorNames">

        <list> 공통 관점 로직 구현 객체 </list>

 

공통 관점 객체는 인터페이스 구현 

MethodInterceptor - invoke 메서드 

MethodBeforeAdvice - before 메서드 

AtferReturningAdvice - afterReturning 메서드 ThrowsAdvice - afterThrowing 메서드 (처리할 예외 지정)

 

 

 


 

intellij 환경설정

 

프로젝트 설정: gradle project, web

톰캣 설정: add configuration - tomcat server - local - artifact 

* 톰캣 설정 후 gradle 빌드 설정

 

 

Artifact : 서버에서 실행할 파일, 프로젝트 결과물

archive

압축된 채로 배포, was에 의해 해제

한 개의 파일만 전송

 

exploded

압축 해제된 디렉터리 상태로 배포

원본 소스를 건드리지 않은 채로 배포

 

 

war, jar

https://server-engineer.tistory.com/315

 

JAR와 WAR

# JAR와 WAR JAR와 WAR와 EAR은 압축파일의 한 유형(Format)이다. # 단위 class < jar < war < ear # 확장자 - 일반파일 압축 :  zip - 클래스파일을 압축 : jar - 웹어플리케이션을 통째로 압축 : war - j..

server-engineer.tistory.com

 


프로젝트 설정 xml

web.xml

dispatcher-servlet.xml

applicationContext.xml

 

https://mrgamza.tistory.com/729

 

IntelliJ. spring-webmvc + gradle + tomcat. web application 구조로 만들기

이전에 글을 남길때는 gradle을 선택하고 Java만 선택한 이후에 web은 선택하지 않고 프로젝트를 세팅하였습니다. web을 선택하여서 main아래에 webapp이 노출되는 방식으로 한번 설명해 보겠습니다. Sp

mrgamza.tistory.com

 


 

servlet/jsp와 스프링의 차이

(1) 톰캣.xml에는 dispatcher-servlet만 올리고, dispatcher-servlet.xml에서 servlet, service, mybatis, security 설정을 한다.

톰캣은 모든 URL을 dispatcher-servlet에게 전달한다.

 

(2) 기존 Dispatcher & Controller 기능이 합쳐진 여러 개의 HttpServlet을 구현한 Servlet 상태에서

하나의 Front Controller Servlet만 두고 jsp로 향하는 Dispatcher forward를 담당하게 하고,

Contoller는 POJO JAVA로 구성하여 Model 데이터 처리를 진행 후 Model, View를 Front Controller로 돌려주게 한다.

 


 

URL pattern

스프링에서는 모든 URL요청이 dispatcher-servlet을 통해 요청과 응답이 돼야 한다.

하지만, pattern을 " / "로 지정하고  "jsp 경로"로 요청하게 되면 jsp 화면을 바로 보여준다. view를 바로 보여주면 안 된다.

그래서 pattern을 " /* "로 지정하고 모든 요청이 dispatcher-servlet을 무조건 거치게 한다.

 

이 과정에서 WEB-INF 경로 안에 dispatcher-servlet.xml에 URL과 매핑된 bean 있어야 한다. (물론 어노테이션도 가능)

* <bean> - id /URL (실제 controller의 경로가 아닌 URL) , 클래스/implements Controller 

해당 빈은 URL이 요청되면 Controller 인터페이스 ModelAndView handlerRequest 메서드를 호출한다.

 

pattern을 " /* " 사용했을 때도 문제가 발생한다. ModelAndView에서 View를 보여줄 때 jsp파일을 한번 더 요청을 하게 되는데

이때는 jsp 파일 경로로 접근해야 되는데 모든 요청을 distpatcher-servlet을 타게 했으므로 문제가 발생한다.

 

그래서 pattern을 다시 " / "로 만들어주고, URL에 매핑되는 컨트롤러가 없다면 webapp 파일을 이용할 수 있게 한다.

URL 우선순위: URL은 매핑된 contoller가 있다면 contoller를 실행하고, 없다면 webapp 파일을 요청한다.

여기까지는 기존 " / " pattern과 같다.

 

view를 바로 보여줄 수 없도록 하는 방법으로 WEB-INF 경로가 등장했다.

 

WEB-INF

비공개 영역. URL 요청이 view로 바로 접근할 수 없도록 숨긴다.

숨기는 이유는 controller에서 요청을 받아 처리하고, 페이지를 보여줘야 한다. view 페이지로 바로 접근하면 안 된다.

클라이언트에서는 접근이 불가능하고, 서버 쪽에서는 접근이 가능하다.

 

View Resolver

view 파일들의 경로가 길어졌다. 이를 해결하기 위해 만들어짐. view 경로의 복잡성을 줄인다.

view를 찾을 때 절대 경로로 찾게끔 만들어둔다.

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

 

static 파일

정적 파일들은 servlet이 아니기 때문에 default pattern도 뚫지 못하여, 경로로 요청해도 막힌다. (jsp는 되는데, html은 안되는 이유)

이를 해결하기 위한 방법이 있다.

설정하게되면 정적 파일을 클라이언트에서 요청할 수 있다.

<mvc:resources mapping="/**" location="/static/" />

 

기본적인 프로젝트 환경 설정이 완료되었다.

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

package singletoneTest;

public class Object1 {
    private static Object1 o; // 싱글톤
    private static int oNumber; // 상태 저장

    public static Object1 getInstance(){
        if(o == null)
            o = new Object1();
        return o;
    }

    public int func(int n){
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return n;
    }

    public int sfunc(int n){
        oNumber = n;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return oNumber;
    }
}

 

Object1 클래스는 싱글톤으로 사용

 

func()함수 - 입력받은 n을 return

sfunc()함수 - 입력받은 n을 oNumber 필드에 대입하고 oNumber를 return

 

 

 

 

public class MainClass1{
    public static void main(String[] args) {
        Thread t1 = new Thread1();
        Thread t2 = new Thread2();
        Thread t3 = new Thread3();

        t1.start();
        t2.start();
        t3.start();
    }
}

class Thread1 extends Thread{
    private int number = 1;
    @Override
    public void run() {
        while(true){
            Object1 o = Object1.getInstance();

            int n = o.func(number);
            System.out.println(n + " Thread1 의 넘버");
            int n1 = o.sfunc(number);
            System.out.println(n1 + " Thread1 의 넘버, 상태 공유 사용할 경우");

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Thread2 extends Thread{
    private int number = 2;
    @Override
    public void run(){
        while(true){
            Object1 o = Object1.getInstance();

            int n = o.func(number);
            System.out.println(n + " Thread2 의 넘버");
            int n1 = o.sfunc(number);
            System.out.println(n1 + " Thread2 의 넘버, 상태 공유 사용할 경우");

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Thread3 extends Thread{
    private int number = 3;
    @Override
    public void run(){
        while(true){
            Object1 o = Object1.getInstance();

            int n = o.func(number);
            System.out.println(n + " Thread3 의 넘버");
            int n1 = o.sfunc(number);
            System.out.println(n1 + " Thread3 의 넘버, 상태 공유 사용할 경우");

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

3개의 쓰레드를 생성한 후 

각 쓰레드는 자신의 number 변수를 func(), sfunc() 메서드에 각각 넣은 후 출력

 

 

 

 

 

 

sfunc() - 자신의 번호를 출력하지 못하는 문제가 발생

이유는 공유 변수 oNumber가 스레드에 안전하지 않기 때문이다.

 

func() - 자신의 번호에 맞게 정확하게 출력한다.

 


 

스레드가 생성되면 스택이 새로 생성되고, 생성된 스택에서 메서드를 호출하여 사용한다.

각 스레드 스택의 스택 프레임은 지역 변수들이 사용된 후 소멸한다.

지역 변수가 Heap영역을 참조하여 사용한다면 공유 데이터 사용에 주의해야한다. (싱글톤은 공유 데이터를 갖지 않는 것이 원칙)

 

sfunc() - Object1의 상태 데이터(공유 데이터)를 사용하므로 스레드에 안전하지 못하다.

func() -  Object1의 메서드만 사용하고 지역 변수는 자신이 넘겨주므로, Heap의 상태 데이터(공유 데이터)는 사용하지 않아 스레드에 안전하다.

 

 

 

Thread Safe

 

(1) 인스턴스 데이터를 사용하지 않는다. (싱글톤)

싱글톤 객체를 여러 쓰레드에서 사용할 때는 Heap의 인스턴스 데이터(상태 데이터)를 사용하지 않게 설계해야한다.

데이터는 스레드가 넘겨주고, Heap의 인스턴스는 메서드만 사용한다.

 

(2) 스레드마다 인스턴스를 생성하여 사용한다. (프로토타입)

Heap의 인스턴스 데이터를 사용해야 하는 경우엔 스레드마다 사용하는 인스턴스를 새로 생성하여 사용한다.

사용자에 따라 상태 정보가 달라지는 DTO, Entity가 싱글톤이 아닌 이유이기도 하다.

 

(3) Lock을 걸어서 다른 스레드가 접근하지 못하게 한다.

 

 

 

 


 

반응형
반응형

 

gradle을 이용해서 validation jar파일을 의존성 추가 해준다.

 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-devtools'
    implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.6'

 

 

implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.6'

 

 


Parameter validation

클래스에 @Validated를 추가하고, 매개변수에 @Size를 지정하여 유효성 검증을 할 수 있다.

 

@RestController
@RequestMapping("/api")
@Validated
public class GetController {
    
    @GetMapping("/get")
    public String val(@Size(max = 5) @RequestParam String name){
        return name;
    }

 

 

 

DTO validation

 

dto model 객체에 validation 어노테이션을 이용해 유효성 검증을 진행할 객체 속성에 추가해준다.

예외가 발생했을 경우 message를 이용해서 사용자에게 검증 실패를 알릴 수 있다.

 

@Getter
@Setter
@ToString
@NoArgsConstructor
public class Dto {

    @Size(min = 2, max = 10, message = "이름은 2~10자 여야 합니다.")
    private String name;
    @Email(message = "이메일 형식이 맞지 않습니다.")
    private String Email;


}

 

 


 

validation.BindingResult 객체를 이용해서 dto model에 적용한 유효성 검증이 알맞게 들어왔는지

인터셉터처럼 동작해서 확인해줄 수 있다.

 

hasErrors()를 이용해서 에러가 발견된다면 모든 에러 메세지를 StringBuilder에 추가한 후,

ResponseEntity 를 이용해 결과를 반환하게끔 했다.

 

BindingResult는 AOP처리해줄 수 있다.

 

@RestController
@RequestMapping("/val")
public class VController {

    @PostMapping("/dto")
    public ResponseEntity createDto(@Valid @RequestBody Dto dto, BindingResult bindingResult){
        StringBuilder sb = new StringBuilder();

        if(bindingResult.hasErrors()){
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            allErrors.forEach((error) -> {
                sb.append(error.getDefaultMessage()).append(" ");
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
        }

        System.out.println(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }

 

 


 

@AssertTrue

직접 유효성검증 함수를 만들어줄 수 있다.

true면 정상적으로 동작하고, false면 HTTPSTATUS.400 error가 발생한다.

 

@AssertTrue(message = "이름은 6자 이하여야 합니다.")
public boolean isNameValidation(){
    if(this.name.length() >= 7)
        return false;
    return true;
}

 

 


 

@RestControllerAdvice

Controller일 경우 @ControllerAdvice

Global하게 지정할 경우 클래스에 어노테이션 추가

Global로 동작하지 않게 basePackage, baseClass 지정가능

 

@ExceptionHandler 

value에 지정된 예외가 발생하면 해당 메서드가 동작한다.

매개변수로 예외를 조작할 수 있다.

컨트롤러에 메서드를 만들어서 해당 컨트롤러에서만 동작하도록 할 수 있다.

 

@RestControllerAdvice
public class GlobalAdvice {

    @ExceptionHandler(value = Exception.class) // 예외 잡기
    public ResponseEntity ex(Exception e){
        e.printStackTrace();

        return ResponseEntity.status(HttpStatus.OK).body("예외잡기");
    }
}

 

 

 

 

예외를 일부러 발생시켰다.

@RestController
@RequestMapping("/adv")
public class AController {

    @GetMapping("/get")
    public String get() throws Exception{
        throw new Exception();
    }
}

 

 

 

예외가 글로벌Advice, @ExceptionHandler에 의해 처리되고, Body에 지정한 값을 응답하며 정상 동작한다.

 

 

 


 

반응형
반응형

빌드 도구 gradle 사용

 

공통사항

 

application.properties - DB연결

spring.datasource.url=jdbc:mysql://localhost:3307/스키마명?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
spring.datasource.username=유저명
spring.datasource.password=유저비밀번호

 

build.gradle - mysql 종속성 추가

dependencies {
    implementation 'mysql:mysql-connector-java:8.0.29'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'

 


 

JPA 연동

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 


 

myBatis 연동

 

build.gradle

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'

 

application.properties - mapper 패키지 추가 // db snake_case와 java camelCase 매핑 

mybatis.type-aliases-package = com.example.project.mapper
mybatis.configuration.map-underscore-to-camel-case=true

 

반응형
반응형

 

https://www.amitph.com/spring-entity-to-dto/

 

Convert Entity To DTO In Spring REST API

A tutorial on How to Entity to DTO Conversion and DTO to Entity Conversion in a Spring REST API manually as well as by using Model Mapper.

www.amitph.com

 

변환하는 방법을 소개하는 좋은 글이 있어서 남긴다.

 

  • 생성자를 사용하는 방법
  • 메서드를 사용하는 방법
  • ModelMapper를 사용하는 방법
  • MapStruct를 사용하는 방법

 

메서드를 사용하는 방법이 직관적

만약 변경해야할 필드가 많다면 ModelMapper, MapStruct를 사용하는 것이 용이할 것 같다.

 


MapStruct 사용

 

 

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

 

gradle에서 mapstruct가 lombok과 이슈를 일으킨다.

lombok이 mapstruct보다 위에 있으면 해결된다.

 

 

 

 

@Mapper
public interface UserMapper {

    UserMapper mapper = Mappers.getMapper(UserMapper.class);

    // dto -> entity
    @Mapping(target = "userAge", source = "age")
    @Mapping(target = "name" , source = "name", defaultValue = "Park")
    UserEntity userDtoToEntity(UserDto userDto);


    @Mapping(target = "age", source = "userAge")
    UserDto userEntityToDto(UserEntity userEntity);

}

@Mapper 

구현체는 mapstruct가 만들어준다.

import할 때 Mybatis의 @Mapper와 구분

 

@Mapping

필드명 불일치를 해결할 수 있다.

defaultValue도 설정할 수 있다.

 

 

 

 

 

@Test
void UserDtoToEntity(){
    UserDto userDto = new UserDto(1, "cha", "email", 30);
    UserEntity userEntity = UserMapper.mapper.userDtoToEntity(userDto);

    System.out.println(userEntity);
}

 

 

 


참고

 

 

 

https://mapstruct.org/documentation/stable/reference/html/

 

MapStruct 1.5.2.Final Reference Guide

If set to true, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also showWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

mapstruct.org

 

 

http://modelmapper.org/getting-started/

 

ModelMapper - Getting Started

Getting Started This section will guide you through the setup and basic usage of ModelMapper. Setting Up If you’re a Maven user just add the modelmapper library as a dependency: org.modelmapper modelmapper 3.0.0 Otherwise you can download the latest Mode

modelmapper.org

 

 

https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=kbh3983&logNo=220988245343 

 

[JAVA] ModelMapper Customizing

주의!! 일부 버전에서 Custom Mapping 이 됬다 안됬다하는 버그가 있음 아오....하루종일을 날렸네........

blog.naver.com

 


 

반응형
반응형

IoC 컨테이너 - 객체가 자신이 의존할 객체를 직접 생성하지 않는다. 사용할 객체의 생성과 바인딩을 외부에 넘긴다.

의존할 객체의 결정권을 객체에 두지 않고, 스프링 프레임워크에 둔다.

 

DI - 두 클래스가 의존 관계에 있을 때 인터페이스를 사이에 두고 다형성을 통해 필요한 구현체(bean)를 외부에서 주입시킨다.

(스프링 IoC 컨테이너에 있는 Bean을 주입받는다.)

 


 

IOC 컨테이너에 클래스를 등록하여 관리하고 싶을 때 Bean을 생성하는 방식은 두 가지가 있다.

(어노테이션 이용)

  • @Bean을 사용하는 방법
  • @Component를 사용하는 방법

 

@Component

@Component는 클래스에 지정한다.

Component Scan에 의해 해당 클래스가 bean으로 생성되어 IoC 컨테이너에 등록된다.

DI를 클래스 내부에서 @Autowired를 사용해서 setter주입, 생성자 주입, field주입 등으로 의존 관계를 만들어주어야 한다.

 

 

@Bean 

@Bean은 @Configuration클래스의 메서드에 지정한다.

수동으로 bean을 생성하는 방식이다. 

직접 설계한 클래스가 아니면 @Component 지정이 불가능하기 때문에 외부 라이브러리 클래스를 bean으로 등록할 때 사용한다.

 


 

반응형

+ Recent posts