관점 지향 프로그래밍 의미

 

컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다. 

코드 그 자체를 수정하지 않는 대신 기존의 코드에 추가 동작(어드바이스)을 추가함으로써 수행하며, "함수의 이름이 'set'으로 시작하면 모든 함수 호출을 기록한다"와 같이 어느 코드가 포인트 컷(pointcut) 사양을 통해 수정되는지를 따로 지정한다. 

이를 통해 기능의 코드 핵심부를 어수선하게 채우지 않고도 비즈니스 로직에 핵심적이지 않은 동작들을 프로그램에 추가할 수 있게 한다. 

관점 지향 프로그래밍은 관점 지향 소프트웨어 개발의 토대를 형성한다.

 

https://ko.wikipedia.org/wiki/%EA%B4%80%EC%A0%90_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

 

관점 지향 프로그래밍 - 위키백과, 우리 모두의 백과사전

컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다. 코드 그 자체

ko.wikipedia.org

 


 

이슈

 

서비스를 개발하면서 다양한 공통 기능을 필요로 하게된다.

대표적으로 로깅, 보안, 트랜잭션을 프로젝트 전반에 걸쳐 적용되는 공통 기능이 존재합니다.

해당 기능들은 특정 메서드에만 필요한 기술이 아니고, 핵심 비즈니스 로직과는 구분되는 기능입니다.

그래서 이 공통 관심 사항을 별도로 분리하기로 했습니다.

 

 

만약 시간 체크 메서드가 서비스 전체를 해야 한다면? (예를 들어 1000개)

복사/붙여 넣기를 이용하더라도 모든 메서드에 시간 체크 기능을 만든다면 많은 시간이 소요될 것입니다.

그래도 복사/붙여넣기를 이용해서 겨우 모든 메서드에 시간체크 기능을 만들었는데

시간 체크 확인 후 모든 시간체크 기능을 제거해야 한다면 다시 많은 시간을 사용하여 제거작업을 해야할 것입니다.

 

이러한 시간체크 기능을 공통으로 관리를 한다면 많은 시간을 사용하지 않아도 되고, 코드도 보다 깔끔해집니다.

개발자는 핵심 비즈니스 로직에 집중할 수 있게 됩니다.

 

AOP 적용 전

 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cg.park</groupId>
    <artifactId>aop-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aop-test</name>
    <description>aop-test</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

ApiController

package cg.park.aoptest.controllers;

import cg.park.aoptest.service.impl.ApiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    ApiServiceImpl apiServiceImpl;

    @RequestMapping("/test")
    public void apiTest() {

        try {
            StopWatch stopWatchA = new StopWatch();
            stopWatchA.start();
            apiServiceImpl.serviceA();
            stopWatchA.stop();

            StopWatch stopWatchB = new StopWatch();
            stopWatchB.start();
            apiServiceImpl.serviceB();
            stopWatchB.stop();

            logger.info("서비스: {}, 소요시간: {}","serviceA",stopWatchA.getTotalTimeMillis());
            logger.info("서비스: {}, 소요시간: {}","serviceB",stopWatchB.getTotalTimeMillis());
        }
        catch (Exception e) {
            logger.error("오류");
        }

    }

}

 

ApiServiceImpl

package cg.park.aoptest.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class ApiServiceImpl {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    public void serviceA () throws Exception {
        logger.info("serviceA 실행");
        Thread.sleep(1000);
    }

    public void serviceB () throws Exception {
        logger.info("serviceB 실행");
        Thread.sleep(2000);
    }

}

 

 

기존에는 각 서비스의 실행 시간을 체크할 경우 수작업으로 모든 서비스에 체크 기능을 만들었습니다.

위의 ApiController를 보면 

serviceA의 시간을 체크하고 serviceB의 시간을 체크하는 로직이 각각 구현되어 있습니다.

 


 

AOP 적용

 

ApiController (수정)

package cg.park.aoptest.controllers;

import cg.park.aoptest.service.impl.ApiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    ApiServiceImpl apiServiceImpl;

    @RequestMapping("/test")
    public void apiTest() {

        try {
            apiServiceImpl.serviceA();
            apiServiceImpl.serviceB();
        }
        catch (Exception e) {
            logger.error("오류");
        }

    }

}

 

ApiServiceImpl (수정)

package cg.park.aoptest.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class ApiServiceImpl {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    public void serviceA () throws Exception {
        Thread.sleep(1000);
    }

    public void serviceB () throws Exception {
        Thread.sleep(2000);
    }

}

 

AopConfig (신규)

package cg.park.aoptest.comm;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect
@Component
public class AopConfig {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Around("execution(* cg.park.aoptest..impl.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        joinPoint.proceed();
        stopWatch.stop();

        logger.info("서비스: {}, 소요시간: {}",joinPoint.getSignature().toShortString(), stopWatch.getTotalTimeMillis());
        return joinPoint.proceed();
    }

}

 

 

코드 전/후 비교

왼쪽: Before  |  오른쪽: After

 

왼쪽 코드보다 오른쪽 코드가 깔끔하지 않나요?

그리고 매번 시간 체크 기능을 만들 필요가 없으니 비즈니스 기능 개발에만 집중할 수가 있습니다.

 


 

후기

 

AOP는 Spring의 핵심 기능이라고 생각합니다.

현재 작성된 글은 시간체크 기능이지만 로깅, 트랜잭션, 시큐리티 기능을 적용하게 된다면 필수 기능입니다.

서비스를 개발하고 운영하면 AOP의 필요성이 느껴질 때가 있습니다.

하지만 AOP를 사용해보지 않고 어렵다고 느낄 수 있기에 간편한 예제를 만들어봤습니다.

감사합니다.

 

Git : https://github.com/qkrcksrbs8/aop-test

+ Recent posts