P3] Ch 05. 스프링을 조금 더 들여다 보기

uuuu.jini·2022년 1월 10일
1
post-thumbnail

목차

  1. 스프링의 핵심
  2. Ioc,DI
  3. AOP
  4. Object Mapper
  5. 여러가지 Annotation

1. 스프링의 핵심

2004년 3월 출시, 20년간 자바 엔터프라이즈 어플리케이션 개발의 최고의 자리를 차지하였다. 스프링 프레임워크의 구성은 20여가지로 구성되어 있으며, 이러한 모듈들은 핵심기능을 제공하여 준다. 그 중 스프링 부트, 스프링 클라우드, 스프링 데이터, 스프링 배치, 스프링 시큐리티에 중점을 둔다. https://spring.io/projects/spring-framework


2. IOC, DI

IOC [Inversion of Control]

제어의 역전 이라고 하며, 이는 개발자가 직접 객체를 관리하는 것이 아닌 스프링 컨테이너가 빈(스프링이 직접 관리하는 객체)을 제어하는 것이다. 제어가 개발자에게서 스프링으로 넘어갔기 때문에 제어의 역전이라고 한다. @Componet 어노테이션을 통해 빈으로 등록이 가능하고 빈의 이름을 새로 지정해주기 위해서는 인자로 새로운 이름을 넘겨주면 된다. @Componet("base74Encoder"). @Qualifier 어노테이션을 사용하여 어떤 빈을 매칭해야 하는지를 직접 지정해주는 것도 가능하다.

즉, New로 객체를 생성하여 개발자가 직접 관리하는 것이 아닌 Spring Container 에게 모두 맡기는 것을 말하며, 이때 DI를 통해 외부에서 객체를 주입받는다.

DI [Dependency Injection]

외부에서 의존을 갖는 객체를 주입받는 것을 DI라고 하며, 코드의 관리나 재사용이 편리하고 해당 주입받는 부분의 객체만 수정하여 간단하게 테스트가 가능하다. 클래스 내부를 수정할 필요 없이 주입받는 객체만 간단하게 수정하여 테스트가 가능하여서 편리하게 사용할 수 있다.

예시로 Encoding의 여러가지 방법을 각 클래스로 구현하고 해당 encoding을 사용하기 위하여 객체를 생성하였다.

  • 첫번째 방법
 	// Baes 64로 encoding
        Encoder encoder = new Encoder();
        String result = encoder.encode(url);
        System.out.println(result);



       //url encoding
        UrlEncoder urlEncoder = new UrlEncoder();
        String result2 = urlEncoder.encode(url);
        System.out.println(result2);

각 encoding 방법을 사용하기 위해 객체를 따로 생성하였다.

  • 두번쨰 방법
        IEncoder baseEncoder = new Base64Encoder();
        String baseResult = baseEncoder.encode(url);
        System.out.println(baseResult);

인터페이스를 생성하여 클래스 생성시 오버라이딩을 통하여 쉽게 메서드를 관리할 수 있었다.

  • 세번째 방법

           Encoder encoder = new Encoder(new Base64Encoder()); 
           String result = encoder.encode(url);
           System.out.println(result);
    
           Encoder urlEncoder = new Encoder(new UrlEncoder());
           String urlResult = urlEncoder.encode(url);
           System.out.println(urlResult);

    객체 생성시 의존을 가진 객체를 주입받았으며, 이것만 수정하여 간단하게 테스트할 수 있게 하였다. 이것이 DI의 특징이며, 이를 활용한 스프링의 특징을 배워볼것이다.


3. AOP[Aspect Oriented Programming]

관점 지향 프로그래밍으로 로직이나 log등 반복되는 부분을 몰아서 처리할 수 있게 해준다. 즉, 여러가지 메소드에서 반복되는 일을 몰아서 한 부분에서 처리하고 각 메소드는 자신만이 처리해야할 작업에 집중하는 것이다.

    @GetMapping("/get/{id}")
    public String get(@PathVariable long id, @RequestParam String name){
	System.out.println("Get Method:" + id,name);
        return id+" "+name;

    }

    @PostMapping("/post")
    public User post(@RequestBody User user) {
	System.out.println(user);
        return user;
    }

위와 같이 요청된 값을 log로 모두 기록을 하려고 한다. 이때, 만약 여러 메소드 전부 log로 기록을 하려구 할 경우 전부 메소드내에 출력을 해주어야 하는 번거로움이 있다. 그래서 우리는 AOP를 사용한다.

@Aspect

해당 클래스가 aop로 사용되는 클래스라는 것을 명시해주는 어노테이션이다. 클래스 위에 작성한다.

@PointCut()

어느 부분에 해당 메소드를 적용시킬 것인지를 지정하는 부분으로서 안에 들어가는 인자의 형식에 따라 적용시키는 지점이 정해진다. 이때 인자의 규칙은 다양한 방식이 존재하므로 사용시에 찾아보라고 하셨다.

  @Pointcut("execution(* com.example.aop.controller..*.*(..)))") 
    private void cut(){
    }

위의 규칙은 com.example.aop.controller하위의 모든 클래스에 적용시키겠다는 의미이다.

@annotation 을 해당 @PointCut 어노테이션의 인자로 받을 경우 지정된 어노테이션을 가진 메소드에만 원하는 메소드를 실행시킬 수 있다.

@Before()

해당 메소드 직전의 실행시킬 메소드 위에 붙이는 어노테이션으로 인자로 해당 메소드의 이름을 작성한다. @Before("cut()") 은 cut()메소드 실행 전에 메소드를 실행하겠다는 의미이다. 메소드는 JointPoint라는 객체를 인자로 받으며, 이는 들어가는 지점에 관한 정보를 가진 객체이다.

    @Before("cut()") 
    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.println("type: " + obj.getClass().getSimpleName()); //타입
            System.out.println("value: " + obj); //값
        }
    }

MethodSignature 객체를 통해 해당 메소드의 이름을 불러올수 있다. JoinPoint객체의 getArgs()메소드를 통해 해당 메소드에 들어가는 매개변수를 배열로 받아온다. 이후 타입과 값을 출력하였다.

@AfterReturing()

메소드 실행 후 실행된다. 정상 실행 후 메소드에서 리턴이 될때 해당 리턴되는 값을 returning 이라는 속성으로 받아올수 있으며, value 로 해당 메서드의 이름을 지정한다. @AfterReturning(value = "cut()", returning = "returnObj")은 cut()메소드 이후 실행되며 반환되는 값을 returnObj라는 Object로 받아온다.

    @AfterReturning(value = "cut()",returning = "returnObj") 
    public void afterReturn(JoinPoint joinPoint, Object returnObj){
        System.out.println("return obj: ");
        System.out.println(returnObj);
    }

매개변수로 joinPoint객체와 반환되는 객체를 받으며, 이때 반환되는 객체의 이름은 위의 returning속성값의 이름과 동일해야 한다. 해당 반환되는 값을 출력하는 형태를 메소드 실행 완료후에 실행한다.

@Around()

해당 메소드의 시작과 종료 모두 제어하는 어노테이션으로 예로 실행시간을 측정할때에 사용된다.


4. Object Mapper

object -> text, text -> object 로 변환해주는 mapper이다. json 노드에 접근하는 방법을 배워본다.

dependencies 찾는 방법: maven repository 에서 원하는 것들 검색하고 원하는 버전을 선택하여 해당 코드를 build.gradle의 dependency에 추가하여준다.

에러 : Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
해결 - file>settings>Compiler>java Compiler > Additional command> '--warning-mode all'

한글깨짐 문제
[참고] https://knoow.tistory.com/195
Json Validate
사이트

object mapper 는 json데이터를 object 객체로 object 객체를 json 데이터로 변경해주는 역할을 한다.

Object -> JSon

object mapper 객체 생성 후에 , 해당 객체의 writeValueAsString() 을 통하여 object를 json으로 변한다.

ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);

Json -> Object

이전에 배운 것과 다르게 이번에는 json노드의 값들에 접근하는 방법을 알아보았다. JsonNode 객체를 생성한 후 objectMapper의 readTree메소드를 사용하여 값을 노드로 읽어들인다. 이후 jsonNode객체의 get메소드를 사용하여서 각 값들을 개별로 읽어들인다. 이때, json데이터의 배열 형식을 가져오기 위해서는 ArrayNode 객체를 생성하여 JsonNode 객체를 강제 형변환하여 사용하여야 한다. 이후 해당 arrayNode의 값을 obejctMapper객체의 convertValue메소드를 사용하여 원하는 obejct형을 typeReference에 넣어 변환하여 주면 된다. (말로 설명한는 것은 어려우므로, 코드를 참고한다.)

특정 Json의 값을 바꾸기 위한 객체

ObjectNode 객체를 사용하여 해당 Json의 특정 값을 변경할 수 있다.

        ObjectNode objectNode = (ObjectNode) jsonNode;
        objectNode.put("name","yoo");
        objectNode.put("age",20);
        System.out.println(objectNode.toPrettyString());

5. 여러가지 Annotation


profile
멋쟁이 토마토

0개의 댓글