스프링부트 기본 3. AOP

min seung moon·2021년 6월 27일
0

Spring

목록 보기
29/50
  • @Bean, @Component의 차이
  • @Bean은 클래스에 붙일 수 없고 메소드에 붙일 수 있다
  • @Component은 클래스에 붙일 수 있다
  • @Configuration은 여러 개의 @Bean 등록을 시켜준다

1. AOP(Aspect Oriented Programming)

01. AOP(Aspect Oriented Programming)

  • 관점 지향 프로그램
  • 스프링 어플리케이션 대부분 특별한 경우를 제외하고는 MVC 웹 어플리케이션에서는 Web Layer, Business Layer, Data Layer로 정의
  • Web Layer
    • Rest API를 제공하며, CLient 중심의 로직 적용
  • Business Layer
    • 내부 정책에 따른 logic를 개발하며, 주로 해당 부분을 개발
  • Data Layer
    • 데이터베이스 및 외부와의 연동을 처리

02. 횡단 관심

03. 주요 어노테이션

2. 프로젝트 테스트

  • aop 프로젝트 생성(spring initalizr)
  • Package : controller, dto
  • Class : RestApiConroller, User

01. AOP dependencies 추가

  • AOP를 사용하기 위한 dependencies 추가
    implementation 'org.springframework.boot:spring-boot-starter-aop'

02. 간단한 Rest Controller 작성

  • dto / User.java
package com.example.aop.dto;

public class User {

    private String id;
    private String pw;
    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPw() {
        return pw;
    }

    public void setPw(String pw) {
        this.pw = pw;
    }

    public String getEmail() {
        return email;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", pw='" + pw + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}
  • controller / RestApiController.java
package com.example.aop.controller;

import com.example.aop.dto.User;
import org.springframework.web.bind.annotation.*;

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

    @GetMapping("/get/{id}")
    public void get(@PathVariable Long id, @RequestParam String name) {
        System.out.println("get method");
        System.out.println("get method : " + id);
        System.out.println("get method : " + name);
    }

    @PostMapping("/post")
    public void post(@RequestBody User user) {
        System.out.println("post method : " + user);
    }

}
  • 실행 테스트



03. AOP 작업 처리

  • 현재 작업 중 System.out.println으로 Log를 찍는 부분을 한곳에 모아서 처리
  • Log를 찍어야 할 곳에 원하는 Log를 찍을 수 있게 도와줌
  • Package : aop
  • Class : ParameterAop
  • ParameterAop.java
    • @Aspect
      • AOP를 정의하는 Class에 할당
      • 이 클래스가 Aspect를 나타내는 클래스라는 것을 명시
      • 애스펙트는 부가기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용하지를 결정하는 포인트컷(PointCut)을 합친 개념
    • @Component
      • Spring에서 관리하도록 Bean 등록
    • @Pointcut("execution( com.example.aop.controller...*(..))")
      • 어느 부분을 적용할 것인지 지정(@Pointcut)
      • 룰 지정
      • 어디다가 적용(execution) 시킬건지 (com.example.aop.controller. 하위에 있는 모든 메소드들을 aop로 보겠다)
    • @Before("cut()")
      • 들어가기 전에 argument 확인, 메소드 실행 전 어떤 값이 들어가는지 확인
      • (메소드이름)
    • @AfterReturning(value = "cut()", returning = "obj")
      • 들어가고 나서는 어떠한 값이 Return이 되는지 확인인
      • (value = "메소드명()", returning = "오브젝트 이름") 파라메터랑 동일한 이름
package com.example.aop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect // AOP 등록
@Component  // Spring에서 관리하도록 Bean 등록
public class ParameterAop {

    // 어느 부분을 적용할 것인지 지정(@Pointcut)
    // 룰 지정
    // 어디다가 적용(execution) 시킬건지 (com.example.aop.controller. 하위에 있는 모든 메소드들을 aop로 보겠다)
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut() {}

    // 들어가기 전에 argument 확인, 메소드 실행 전 어떤 값이 들어가는지 확인
    // 언제 실행을 시킬거냐
    // (메소드이름)
    // JoinPoint, 들어가는 지점에 대한 정보를 가져옴
    @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);
        }
    }

    // 들어가고 나서는 어떠한 값이 Return이 되는지 확인인
    //(value = "메소드명()", returning = "오브젝트 이름") 파라메터랑 동일한 이름
   @AfterReturning(value = "cut()", returning = "obj")
    public void afterReturn(JoinPoint joinPoint, Object obj) {
       System.out.println("return obj");
       System.out.println(obj);
    }
}



3. AOP 실제 사례

  • 메소드 실시간을 가지고 서버의 부하와 상태 로깅
  • AOP와 Custom Annotaition 사용

01. AOP 생성

  • TimerAop.java
    • @Around("cut() && enableTimer()")
      • 실행 전 후를 한번에 묶은 어노테이션
      • .proceed()를 기준으로 함
package com.example.aop.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

// 특정 메소드의 실행 시간 측정
@Aspect
@Component
public class TimerAop {

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

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

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

        // proceed() 메소드 실행
        // result는 메소드 실행에 대한 결과 값 저장장
       Object result = joinPoint.proceed();

       stopWatch.stop();

        System.out.println("total time : "+stopWatch.getTotalTimeSeconds());
    }

}

02. Annotaion 생성

  • Package : annotation
  • Class : Timer.annotation
  • Timer.annotation
package com.example.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}

03. Controller에 delete 추가

  • RestApiController.java
package com.example.aop.controller;

import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.web.bind.annotation.*;

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

    @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
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        // db logic
        Thread.sleep(1000*2);
    }

}


4. 값의 변환

  • 암호화된 값이 들어온다던지 또 다른 인터셉터에서 변환을 해야 될 때 톰캣 자체에서 한번 body를 읽으면 변환하기 어려운데 AOP 구간에서는 값을 이미 받아왔기에 변환하거나 수정 할 수 있다
  • 복호화를 코드가 아닌 aop 단계에서 진행 가능

01. AOP 생성

  • aop / DecodeAop.java
package com.example.aop.aop;

import com.example.aop.dto.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.util.Base64;

@Aspect
@Component
public class DecodeAop {

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

    @Pointcut("@annotation(com.example.aop.annotation.Decode)")
    private void enableDecode() {}

    // decode
    @Before("cut() & enableDecode()")
    public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {

        Object[] args = joinPoint.getArgs();

        for(Object arg : args) {
            // 특정 객체에 대한 특정 변수 decode
            if(arg instanceof User) {
                User user = User.class.cast(arg);
                String base64Email = user.getEmail();
                String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");
                user.setEmail(email);
            }
        }
    }

    // encode
    @AfterReturning(value = "cut() & enableDecode()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj) {
        if(returnObj instanceof User) {
            User user = User.class.cast(returnObj);
            String base64Email = user.getEmail();
            String email = Base64.getEncoder().encodeToString(base64Email.getBytes());
            user.setEmail(email);
        }
    }
}

02. Annotation 생성

  • annotation / Decode
package com.example.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}

03. Controller에 put 추가

  • RestApiController.java
package com.example.aop.controller;

import com.example.aop.annotation.Decode;
import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.web.bind.annotation.*;

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

    @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
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        // db logic
        Thread.sleep(1000*2);
    }

    @Decode // 값을 변환
    @PutMapping("/put")
    public User put(@RequestBody User user) {
        System.out.println("put");
        System.out.println(user);
        return user;
    }

}
  • 테스트를 위해 AopApplication.java에서 값 변환
package com.example.aop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Base64;

@SpringBootApplication
public class AopApplication {

	public static void main(String[] args) {
		SpringApplication.run(AopApplication.class, args);
		System.out.println(Base64.getEncoder().encodeToString("steve@gmail.com".getBytes()));
	}

}


profile
아직까지는 코린이!

0개의 댓글