즐겁게 하하하 2022. 2. 13. 09:52
728x90
 
1. Controller :  어떻게 처리할지
2. Service    :  어떤 처리를 할지
3. DAO        : DB의 data에 접근하기 위한 객체
4. DTO        : 기준이 되는 틀을 정의
 

1. @Controller(Spring MVC Controller)

[ Controller - View ]

View를 반환하기 위해 사용합니다

ViewResolver 설정에 맞게 View를 찾아 렌더링

public String infoView(Model model, @RequestParam(value = "userName", required = true) String userName){

 

[ Controller - Data ]

@ResponseBody를 사용하여 Client에게 Json 형태로 데이터를 반환

HttpMessageConverter가 동작 ( 반환해야 하는 데이터에 따라 사용되는 Converter가 달라집니다 )

- 단순 문자열인 경우에는 StringHttpMessageConverter가 사용

- 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용

  데이터 종류에 따라 서로 다른 MessageConverter가 작동

  @PostMapping(value = "/info")

  public @ResponseBody User info(@RequestBody User user)

 

2. @RestController(Spring Restful Controller)

Json 형태로 객체 데이터를 반환하는 것입니다.

주로 VueJS + Spring boot 프로젝트를 진행하며 Spring boot를 API 서버로 활용할 때

또는 Android 앱 개발을 하면서 데이터를 반환할 때 사용 합니다

 

 


 

프로젝트 생성

 
 

 

 

 

톰캣 포트 변경

 
 

 

JSON

{
  "phone_number" : "value",
  "phoneNumber"  : "value",
  "age"          : 10,
  "isAgree"      : false,
  "account"      : { "email" : "aa@naver.com",  "password" : "1234: },
  "user_list"    : [ {"account" : "a"} , {"account" : "b"}]
}

string  : value
number  : value
boolean : value { }
object  : value
array   : vaule [ ]
__________________________________
스네이크 케이스 private String phone_number
카멜 케이스 private String phoneNumber
 
 

 

 

 

@RequestParam(required=false)

★ @RequestParam(required=false)
  :: 기본 true , false 인경우 해당 인자값이 없더라도 실행가능
  public String main( @RequestParam( name="year", required=false ) int year ){
    http://localhost/ch2/requestParam       
      ---->> year=null 
    http://localhost/ch2/requestParam?year   
      ---->> year=""
 ______________________________________________________________________
  public String main( @RequestParam( name="year", required=true ) int year ){   
    http://localhost/ch2/requestParam         
      ---->> year=null   400 Bad Request. required=true라서 
    http://localhost/ch2/requestParam?year    
      ---->> year=""
 
 

 

GET , POST , DELETE , PUT

 
================== GET API ================== 
@RestController , @RequestMapping
@GetMapping , @RequestParam , @PathVariable , Object
______________________________________________
@RestController
@RequestMapping("/api/get")
public class ApiGet {
    ___________________________________________________
    :: http://localhost:9090/api/get/hello
    @GetMapping(path = "/hello")
    public String hello(){ return "get Hello"; }
    ___________________________________________________
    :: http://localhost:9090/api/get/hi
    @RequestMapping(path = "/hi" , method= RequestMethod.GET)
    public String hi(){ return "hi"; }
    ___________________________________________________
    :: http://localhost:9090/api/get/path-variable
    :: {name} @PathVariable String name 이름 같아야 한다.
    @GetMapping("/path-variable/{name}")
    public String pathValuable(@PathVariable String name){
        System.out.println("pathVariable : "+ name);
        return name;
    }
    ___________________________________________________
    :: http://localhost:9090/api/get/path-variable
    @GetMapping("/path-variable/{name2}")
    public String pathValuable2(
          @PathVariable(name="name2") String pathName ,
          String otherName 
    ){
        System.out.println("pathVariable : "+ pathName);
        return pathName;
    }
    ___________________________________________________ 
    :: http://localhost:9090/api/get/query-param?user=steave&email=aa@naver.com
    @GetMapping(path = "query-param")
    public String queryString(@RequestParam Map<String,String> queryParam ) {

        StringBuilder sb = new StringBuilder();
        queryParam.entrySet().forEach( entry -> {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
            System.out.println("\n");
            sb.append(entry.getKey() + " = " + entry.getValue() + "\n");
        });
        return sb.toString();
    }
    ___________________________________________________
    :: http://localhost:9090/api/get/query-param02?name=steave&email=aa@naver.com
    @GetMapping("/query-param02")
    public String queryParameter02(
            @RequestParam String name,
            @RequestParam String email
    ){
        System.out.println(name + " " + email );
        return name + " " + email;
    }
    ___________________________________________________
    :: http://localhost:9090/api/get/query-param03?name=steave&email=aa@naver.com&age=11
    @GetMapping("/query-param03")
    public String queryParameter03( UserRequest userRequest ){
        System.out.println(userRequest.getName());
        System.out.println(userRequest.getEmail());
        System.out.println(userRequest.getAge());
        return userRequest.toString();
    } 
    ___________________________________________________
}
 
 

 

================== DELETE API ================== 
@RestController , @RequestMapping
@DeleteMapping , @RequestParam , @PathVariable {userId} , Object
______________________________________________

@RestController
@RequestMapping("/api")
public class DeleteApiController {

    @DeleteMapping("/delete/{userId}")
    public void delete(@PathVariable String userId , @RequestParam String account){
        System.out.println(userId);
        System.out.println(account);
    }
}
 
 
 
 
 

 

================== POST API ================== 
@RestController , @RequestMapping
@PostMapping , @RequestBody(필수) , @PathVariable {userId}
@JsonProperty , @JsonNaming
______________________________________________
@RestController
@RequestMapping("/api")
public class PostApiController {
    ___________________________________________________
    ::http://localhost:9090/api/post
    @PostMapping("/post")
    public void post(@RequestBody Map<String,Object> requestData ){ 
        StringBuilder sb = new StringBuilder();
        requestData.entrySet().forEach(entry ->{
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
            sb.append(entry.getKey() + " " + entry.getValue());
        });
    }
    ___________________________________________________
    @PostMapping("/post2")
    public void post2(@RequestBody PostRequestDto requestDto ){
        System.out.println(requestDto.toString());
    }  
    ___________________________________________________
}


POST로 넘어오는 JSON 데이터는 스네이크 케이스로 이루어져 있는 반면에 
자바 엔티티는 케멀 케에스로 이루어져 있다.
이러한 경우에는 컨트롤러 단에서 JSON 데이터와 자바 엔티티를 매핑시켜준다고 해도
Key가 일치하지 않아 값을 제대로 받아올 수 없다.
@JsonNaming

@JsonNaming : 해당 클래스는 object mapper 모듈이 동작할때 class 모두 스네이크 케이스로 인식한다.

@JsonNaming(value= PropertyNamingStrategy.SnakeCaseStrategy.class)

 
@JsonProperty : key를 매핑

 

 

 

 

================== PUT API ================== 
@RestController , @RequestMapping
@PutMapping , @RequestBody(필수) , @PathVariable {userId}
@JsonProperty , @JsonNaming
______________________________________________
@RestController
@RequestMapping("/api")
public class PutApiController {

    ::http://localhost:9090/api/put2/100
    @PutMapping("/put/{userId}")
    public PostRequestDto put(@RequestBody PostRequestDto requestDto ,
        @PathVariable Long userId
    ){

        System.out.println(requestDto);
        return requestDto;
    }
}
______________________________________________
 

 


 

 

Response 
  String : 일반 Text Type 응답
  Object : 자동으로 Json 변환되어 200 ok
  ResponseEntity : RestController에서 주로 사용.
                   Body내용을 Object로 설정 상황에 따라서 Http Status Code 설정

  @RequestBody  : HTTP 요청 본문에 담긴 값들을 자바 객체로 변환 시켜, 객체에 저장
  @ResponseBody   : RestController가 아닌 Controller 에서 json응답을 내릴때,
                    자바 객체를 HTTP 응답 본문의 객체로 변환하여 서버에서 클라이언트로 
                    응답 데이터를 전송

  @JsonInclude   : response 내릴때 null 같은 값 포함 안되게
                   @JsonInclude(JsonInclude.Include.NON_NULL)
______________________________________________
1. ResponseEntity 
    ResponseEntity.status : 상태코드
    HttpStatus.CREATED : 201
    body(user) : body에 user return

    @PutMapping("/put")
    public ResponseEntity<User> put(@RequestBody User user){
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
______________________________________________
2. @ResponseBody
    @Controller  
    public class PageController { 
        @ResponseBody
        @GetMapping("/user")
        public User user(){
            var user = new User();
            user.setName("steve");
            user.setAddress("패스트");
            return user;
        }
    }
 

 


 

 

DI , IOC , AOP

 
DI
   - 외부에서 사용하는 객체를 주입받는 형태
   - 의존성으로 부터 격리시켜 코드 테스트 용이
   - Mock와 같은 기술을 통해 외부와의 통신이 있는 경우에도 테스트 가능 
 

 

 

IOC
   - bean 들이 관리 되는곳 spring container ( 제어의 역전 )
   - spring 에서 객체(bean)를 직접 관리
   - bean 을 주입 받을수 있는 장소: 변수, 생성자 , set method

   ::Bean과 Component차이 
     - @Bean : method 가능 , class에 불가능
     - @Component :  클래스 단위로 Bean 등록 가능 
     - @Configuration : 해당 클래스에서 1개 이상의 Bean을 생성하고 있음을 명시
 
======= ApplicationContext =======
IOC를 구성하기위해 필수적인 소스 코드( 인터넷에 많다.. )

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext(){
        return context;
    }
}
 
@Component

@Component

- 클래스명 base64Encoder 으로 Bean 등록(맨앞 소문자 주의)

- @Component(bean이름)도 가능

예시 1

 

예시 2

 

예시 3

 


 

AOP : MVC 관점 지향 프로그래밍
  - 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고
    그 관점을 기준으로 각각 모듈화하겠다는 것이다.
  - method 실행 시간을 가지고 서버의 부하, 상태등을 로깅으로 남길수 있다.

  - dependencis 추가
    implementation 'org.springframework.boot:spring-boot-starter-aop'
 
 
@Aspect
관심사를 모듈화,. 주로 부가기능을 모듈화
@Pointcut
기능을 어디에 적용 시킬지, AOP를 어디에 적용시킬지 지점 설정
@Before
메소드 실행 이전 공통 로직을 적용
@After
메소드 실행 후 공통 로직을 적용
@AfterReturing
메소드 호출 성공실행시(Not Throws)
@AfterThrowing
메소드 호출 실패 예외 발생( Throws )
@Around
메서드의 실행 전/후에 공통로직을 적용하고 싶을 때 사용
AOP 적용전
===== DTO =====
public class User { 
    private String id; private String name;  private String email; 

    public String getId() { return id;  } 
    public void setId(String id) { this.id = id; }
 
    public String getName() { return name; } 
    public void setName(String name) { this.name = name;  } 

    public String getEmail() { return email;  } 
    public void setEmail(String email) {  this.email = email;  }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}
 
===== Controller =====
@RestController
@RequestMapping("/api")
public class RestApiController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable Long id , @RequestParam String name ){
        return id + " " + name;
    }

    @PostMapping("/post")
    public User post(@RequestBody User user){
        return user;
    }

    @Timer // 생성된 annotation
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        Thread.sleep(1000*2);   // do logic
    }

    @Decode // 생성된 annotation
    @PutMapping("/put")
    public User put(@RequestBody User user ){
        System.out.print("===> put method Run : ");
        System.out.println( "   " + user);
        return user;
    }
}
 
===== AOP 1 =====
@Aspect
@Component
public class ParameterAop {

    // AOP 적용위치 ( 수식은 찾아보고 입력하기 )
    @Pointcut("execution(* com.example.aop2.controller..*.*(..)))")
    private void parameter(){ }

    // 메소드 실행이전
    @Before("parameter()")
    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.print("===> ParameterAop Before : ");
            System.out.println("type : " + obj.getClass().getSimpleName() );
            System.out.println("value : " + obj );
        }
    }

    // 메소드 호출 성공실행시(Not Throws)
    @AfterReturning(value = "parameter()" , returning = "returnObj")
    public void afterReturn( JoinPoint joinPoint , Object returnObj ){
        System.out.print("===> ParameterAop returnObj : ");
        System.out.println(returnObj);
    }
}
 
===== AOP 2 (annotation 생성함) =====
@Component
public class TimerAop {

    // AOP 적용위치 ( 수식은 찾아보고 입력하기 )
    // @Pointcut("execution(* com.example.aop2.controller..*.*(..)))")
    // private void cut(){}

    @Pointcut("@annotation(com.example.aop2.annotation.Timer)")
    private void enableTimer(){}

    //@Around("cut() && enableTimer()")
    @Around("enableTimer()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();

        System.out.print("===> TimerAop Around");
        System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
    }
}

===== annotation =====
@Target({ElementType.TYPE , ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)  // 런타임
public @interface Timer {}
 
===== AOP 3 (annotation 생성함) =====
@Aspect
@Configuration
public class DecodeAop {

    // AOP 적용위치 ( 수식은 찾아보고 입력하기 )
    @Pointcut("@annotation(com.example.aop2.annotation.Decode)")
    private void enableDecode(){}

    @Before("enableDecode()")
    public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
        //디코딩 = 복호화
        Object[] args = joinPoint.getArgs();
        for (Object obj : args){
            if(obj instanceof User){
                User user = User.class.cast(obj); // 형변환
                String base64Email = user.getEmail();
                String email = new String(Base64.getDecoder().decode(base64Email) , "UTF-8");
                user.setEmail(email);
            }
        }
    }

    @AfterReturning(value = "enableDecode()" , returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint , Object returnObj){
        // 다시 인코딩
        if( returnObj instanceof  User){
            User user = User.class.cast(returnObj); // 형변환
            String email = user.getEmail();
            String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
            user.setEmail(base64Email);
        }
    }
}

===== annotation =====
@Target({ElementType.TYPE , ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)  // 런타임
public @interface Decode {}
 
 

 


 

ObjectMapper( Jaon Text ↔ Object )

ObjectMapper
  :: https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
  implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.0'

  :: objectMapper가 참조하는 클래스에는 모든 get method를 참조하므로 
     참조하지 않는 method 이름에 get이 들어가면 에러가 난다.
  :: public User getDefaultUser(){ return new User("default" , 0 , ""); } (X)
      _________________________________________________
        ObjectMapper objectMapper = new ObjectMapper();
        Car car1 = new Car("K5", "11가 1111" , "A1");
        Car car2 = new Car("Q5", "22가 2222" , "A2");
        List<Car> carList = Arrays.asList(car1,car2); 
        User user = new User("홍길동" , 30 , carList );
        System.out.println(user);
      _________________________________________________

      :: Object -> Jaon Text
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);
      _________________________________________________
        
      :: 순수한 노드에 접근
        JsonNode jsonNode = objectMapper.readTree(json); 
        String _name = jsonNode.get("name").asText(); //asText : 형변환
        int _age = jsonNode.get("age").asInt(); //asInt : 형변환
        System.out.println("_name = " + _name);
        System.out.println("_age = " + _age);

        JsonNode cars = jsonNode.get("cars"); //array
        ArrayNode arrayNode = (ArrayNode) cars;
        List<Car> _cars = objectMapper.convertValue( arrayNode, new TypeReference<List<Car>>(){} );
        System.out.println("_cars = " + _cars);

      :: json 값 안의 내용 바꾸기
        ObjectNode objectNode = (ObjectNode) jsonNode;
        objectNode.put("name" , "steave");
        objectNode.put("age" , "22");
        System.out.println(objectNode.toPrettyString());
      _________________________________________________
      :: Jaon Text -> Object :: objectMapper : default 생성자를 필요로 한다.
      :: User{name='steave', age=10, phoneNumber='010-222-2222'}
         var objectUser = objectMapper.readValue( json , User.class );
		 System.out.println(objectUser); 
 

 

 

 

728x90