본문 바로가기
Spring

AOP

by step 1 2021. 6. 13.
반응형

관점지향 프로그램

스프링 어플리케이션은 대부분 특별한 경우를 제외 하고는 MVC 웹 어플리케이션에서는 Web Layer, Business Layer, Data Layer로 정의

 

- Web Layer: REST API를 제공하며, Client 중심의 로직 적용

- Business Layer: 내부 정책에 따른 logic를 개발하며, 주로 해당 부분을 개발

- Data Layer: 데이터 베이스 및 외부와의 연동을 처리

 

주요 Annotation

Annotation 의미
@Aspect 자바에서 널리 사용하는 AOP 프레임워크에 포함되며, AOP를 정의하는 Class에 할당
@Pointcut 기능을 어디에 적용시킬지, 메소드? Annotation? 등 AOP를 적용 시킬 지점을 설정
@Before 메소드 실행하기 이전
@After 메소드가 성공적으로 실행 후, 예외가 발생 되더라도 실행
@AfterReturing 메소드 호출 성공 실행 시 (Not Throws)
@AfterThrowing 메소드 호출 실패 예외 발생(Throws)
@Around Before / After 모두 제어

 

예제

AOP 패키지 추가

dependencies {
    //    aop 추가
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

User 클래스 생성

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 + '\'' +
                '}';
    }
}

사용자 지정 어노테이션 클래스 생성

Decode.java -> 디코드 실행하는 메소드에 적용

package com.example.aop.annotation;

public @interface Decode {
}

Timer.java -> 메소드 실행 시간을 잴 때 적용

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)
// @interface: 어노테이션 생성
public @interface Timer {
}

AOP 클래스 생성

ParameterAop.java -> controller 실행시 파라미터값을 확인하고, 리턴값을 확인하는 클래스

package com.example.aop.aop;

import com.example.aop.annotation.Timer;
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 org.springframework.util.StopWatch;

import java.lang.reflect.Method;

// @Aspect: AOP 사용 선언
@Aspect
// @Component: 스프링에서 해당 객체 관리
@Component
public class ParameterAop {

//    @Pointcut: 어디에 AOP를 적용할 지 설정
//    com.example.aop.controller 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){

    }

//    해당 pointcut이 실행되기전에 실행
    @Before("cut()")
    public void before(JoinPoint joinPoint){
        System.out.println("====================================");
//      어떤 메소드를 실행했는지 확인
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println("어떤한 메소드가 실행 되었는가? " + method.getName());
        
//        AOP가 동작하기 전에 어떠한 데이터가 전달되었는지 확인
        Object[] args = joinPoint.getArgs();
        for (Object obj: args){
            System.out.println("type: " + obj.getClass().getSimpleName());
            System.out.println("value: " + obj);
        }
    }

//    해당 pointcut이 실행되고 반환값을 확인하기 위해, returning 이름은 메소드의 파라미터와 일치 시켜줘야 한다.
    @AfterReturning(value = "cut()", returning = "returnobj")
    public void afterReturn(JoinPoint joinPoint, Object returnobj){
//      AOP실행 성공 후 어떤값이 반환되어졌는지 확인
        System.out.println("return obj: " + returnobj);
//        System.out.println(returnobj);
        System.out.println("===================================");
    }

}

TimerAop.java -> 메소드 실행 시간을 재는 클래스

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: AOP 사용 선언
@Aspect
// @Component: 스프링에서 해당 객체 관리
@Component
public class TimerAop {

    //    @Pointcut: 어디에 AOP를 적용할 지 설정
//    com.example.aop.controller 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){

    }

//  Timer 어노테이션이 동작할때만 적용
    @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();

//      메소드 실행
        Object result = joinPoint.proceed();

//      시간 재는거 멈춤
        stopWatch.stop();
        System.out.println("total time: " + stopWatch.getTotalTimeSeconds());
    }
}

DecodeAop -> 인코딩된 데이터를 전달받은 후 인코딩하고 다시 디코딩할때 사용

예시: YWlpaUBuYXZlci5jb20=  ->  aiii@naver.com

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.nio.charset.StandardCharsets;
import java.util.Base64;

// @Aspect: AOP 사용 선언
@Aspect
// @Component: 스프링에서 해당 객체 관리
@Component
public class DecodeAop {

    //    @Pointcut: 어디에 AOP를 적용할 지 설정
//    com.example.aop.controller 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){

    }

    //  Decode 어노테이션이 동작할때만 적용
    @Pointcut("@annotation(com.example.aop.annotation.Decode)")
    private void enableDecode(){

    }

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

        Object[] args = joinPoint.getArgs();

        for (Object arg : args){
//            User 클래스와 매칭될때
            if (arg instanceof User) {
//                User 클래스로 형변환
                User user = User.class.cast(arg);
//                user클래스에 email값을 가져온다.
                String base64Email = user.getEmail();
//                email값을 Base64로 디코딩 한다.
                String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");
//                변환된 값을 user 클래스에 email에 저장
                user.setEmail(email);
                System.out.println("Decode Before: " + email);
            }
        }
    }

    @AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj){

        if (returnObj instanceof User) {
//                User 클래스로 형변환
            User user = User.class.cast(returnObj);
//                user클래스에 email값을 가져온다.
            String email = user.getEmail();
//                email값을 Base64로 디코딩 한다.
            String base64Email = Base64.getEncoder().encodeToString(email.getBytes(StandardCharsets.UTF_8));
//                변환된 값을 user 클래스에 email에 저장
            user.setEmail(email);
            System.out.println("Decode after: " + base64Email);
        }
    }
}

RestApiController.java -> URL에 따라 실행되는 클래스

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 RestApiController {

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

        return id +" " + name + " 보내줄게";
    }

    @PostMapping("/post")
//    Json형태를 받기위해 RequestBody 사용
    public User post(@RequestBody User user){
//        System.out.println("post method: " + user);
        return user;
    }

    //    직접 만든 어노테이션 적용
    @Timer
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {

//        StopWatch 관련 처리를 AOP로 사용
//        StopWatch stopWatch = new StopWatch();
//        stopWatch.start();

//        db logic
//        2초간 대기
        Thread.sleep(1000 * 2);

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

    @Decode
    @PutMapping("/put")
//    Json형태를 받기위해 RequestBody 사용
    public User put(@RequestBody User user){
        System.out.println("put method: " + user);
        return user;
    }
}

 

실행 화면

Get 테스트 -> http://localhost:8080/api/get/100?name=hidd

파라미터 전달

POST 테스트 -> http://localhost:8080/api/post

json 형식으로 전달

Delete 테스트 -> http://localhost:8080/api/delete

2초간 정지후 동작

Put 테스트 -> http://localhost:8080/api/put

Java 코드로 인코딩 하는 방법

@SpringBootApplication
public class AopApplication {

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

}

email 부분에 인코딩한 값을 전달

반응형

'Spring' 카테고리의 다른 글

Json으로 출력된 데이터 확인  (0) 2021.06.13
주요 어노테이션  (0) 2021.06.13
IoC / DI  (0) 2021.06.12
Spring 핵심  (0) 2021.06.12
Object Mapper  (0) 2021.06.12