- AOP(Aspect Oriented Programming)
AOP(Aspect Oriented Programming)는 관점지향프로그래밍이라고 불린다. 즉 문제를 바라보는 관점을 기준으로 프로그래밍 하는 방법론이라고 할수 있다.
예를들어, 하나의 커다란 프로젝트가 진행되고 있을때, 그 프로그램들 안의 여러개의 객체단위의 로직들의 수행됨에 있어서 이것을 핵심적인 관점과 부가적인 관점으로 나누어 문제를 해결해 나가는 프로그래밍 방법론이라고 할수 있다.
- 핵심관심사항(core consern)
즉, 각각 수행되는 프로그램들에게는 모두 핵심적으로 수행시키고자 하는 부분들(예: 은행이라면 '입금/출금/대출처리~'등)이 있기에 거기에 맞춰서 각각의 프로그램들이 모듈화 되어 작업되어져 있을 것이다. 이런 것들을 '핵심관심사(core consern)'라고 하고 이것이 AOP가 적용되게될 타겟오브젝트(Target Object)가 된다.
- 공통관심사항(cross cutting concern)
이런 각각의 타겟오브젝트안에는 공통적으로 수행시키고자 하는 코드들(공통관심사:cross cutting concern)이 존재할수 있는데, 예를 들어 '로그/트랜잭션/인터셉터/보안/수행시간비교~'등등.... 이런것들은 각각의 프로그램안에서 공통적으로 수행될수 있는 코드들로 만들어줄수 있다. 즉, 이런 공통관심사는 중복된 코드라고 할수 있다.
따라서 핵심코드를 가지고 있는 클래스(부모클래스)는 건드리지 않고, 상속받는 자식클래스에서 공통코드를 오버라이드해서 재사용할수 있게 하는 기술이 AOP라고 할수 있다.
스프링은 이런 AOP의 적용이 런타임시에 프록시를 이용하여 적용(위빙:Weaving)되게 된다.
- AOP 주요 용어
ㆍ타겟오브젝트(Target Object) : 상속받는 부모클래스로 AOP를 적용하고 싶은 클래스 이다.
ㆍ 조인포인트(JoinPoint) : 타겟오브젝트(부모클래스)에 있는 여러 메소드들중에서, 처리해야 할 모든 메소드들을 말한다. 즉, 재정의 하는 모든 메소드라고 할수 있다.
ㆍ포인트컷(Pointcut) : 타겟오브젝트안에서 사용하고자 하는 공통코드와 그 공통코드가 언제 실행하게 할지에 대해서 처리하는것으로 5가지의 시점으로 보고 있다.
ㆍ어드바이서(Advisor) : 포인트컷과 어드바이스를 합친것(적용된것) - aspect와도 같은의미로 해석된다.
before advice, after advice, after-throwing advice, after-returning advice, around advice
ㆍ위빙(Weaving) : 타겟오브젝트안의 재정의된 메소드중에서 핵심코드에 공통코드가 삽입되는것을 말한다.
ㆍ애즈팩트(Aspect) : AOP의 색심으로 포인트컷과 어디바이스의 결합을 의미한다. 즉, 해당 포인트컷이 어느시점에 수행되게 하는가의 관점을 말한다.
- AOP를 구현하는 방법
1) XML 기반의 POJO클래스를 이용한 AOP구현방법 - 현재 가장 많이 사용하는 방법이라고 한다.
2) AspectJ 5/6에서 정의한 @Aspect 어노테이션 기반의 AOP구현방법이다.
3) 스프링 API를 이용한 AOP구현방법 - 현업에서는 거의 사용되지 않고 있다고 한다.
- 스프링 프레임워크(스프링 레거시프로젝트)에서의 AOP 간단 사용 예제
: 서비스객체에서 수행하는 업무중 ①메세지를 받아서 처리하여 돌려주는 메소드, ②수를 입력받아서 구구단을 구하는 메소드, ③DB에 저장된 회원리스트들을 모두 출력하는 메소드~~~ 등등... 이런 종류의 작업이 구현되는 클래스가 있다고 하자. 이때 1~3번과 같은 핵심로직을 가지고 있는 클래스에 공통적으로 '로그/수행시간비교'와 같은 코들를 추가해서 넣는다고 가정해보자.
그렇게 되면 같은코드를 앞의 3개의 메소드안에 모두 포함해서 넣어야 되므로, 많은 중복으로 인한 비효율적인 작업상황이 발생되게 된다.
이러한 공통관심사를 뽑아서 처리할수 있도록 AOP 적용을 해 보도록 한다.
실습을 위해 프로젝트를 1개 만들고, 기본적인 설정을 하도록 한다.
프로젝트명 : TestAOP
설정파일 셋팅 :
pomx.ml의 의존성 주입하기
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
servlet-context.xml 에 autoproxy설정
<aop:aspectj-autoproxy />
root-context.xml : 데이터베이스(MySQL)을 사용하기위한 JDBC드라이버와 템블릿 설정
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/javaclass?allowMultiQueries=true&autoReconnect=true&verifyServerCertificate=false&useSSL=true" />
<property name="username" value="root" />
<property name="password" value="1234" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
작업에 필요한 '패키지/폴더'와 '파일'들을 만들어준다.
- 패키지명 : controller / service / vo / aop
- 컨트롤러파일(controller패키지) : HomeController.java
- 서비스 단(service패키지) : 인터페이스 - AopTestService.java
구현클래스 - AopTestServiceImpl.java
- DTO/VO(vo패키지) : HoewonVO.java
- AOP 파일(aop패키지) : AopTest.java
- 폴더명 : aopTest
- view파일(jsp파일로 aopTest폴더에 만든다) : aopMain.jsp
작업에 필요한 사항들 - 요구사항 -
① '성명'을 입력후 '성명검색'버튼을 누르면 서비스단에서 '성명'을 검색하고, 검색된 성명(닉네임)에 'Hello~' 문구를 추가헤서 돌려주면 브라우저에 출력시켜준다.
② 화면에서 숫자를 입력후 '구구단출력'버튼을 누르면 서비스단에서 처리후 그 결과를 돌려주면 view를 통해서 브라우져에 출력한다.
③ '회원조회'버튼을 누르면 DB에서 회원들 정보를 모두 가져와서 view에 출력처리한다. - 추후 작업예정.....
작업에 필요한 소스코드들을 각각의 파일에 넣어준다.
컨트롤러 : HomeController.java
package com.spring.TestAOP1.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.spring.TestAOP1.service.AopTestService;
@Controller
public class HomeController {
@Autowired
AopTestService aopTestService;
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
@GetMapping("/hello")
public String helloGet(Model model, String name) {
String message = "";
if(name == null) message = aopTestService.getHelloPrint("홍길동");
else message = aopTestService.getHelloPrint(name);
model.addAttribute("message", message);
return "aopTest/aopMain";
}
@GetMapping("/gugudan")
public String gugudanGet(Model model, int dan) {
String gugudan = aopTestService.getGugudan(dan);
model.addAttribute("gugudan", gugudan);
model.addAttribute("message", "구구단");
return "aopTest/aopMain";
}
}
서비스 인터페이스 : AopTestService.java
package com.spring.TestAOP1.service;
public interface AopTestService {
public String getHelloPrint(String name);
public String getGugudan(int dan);
}
서비스 구현객체 : AopTestServiceImpl.java
package com.spring.TestAOP1.service;
import org.springframework.stereotype.Service;
@Service
public class AopTestServiceImpl implements AopTestService {
@Override
public String getHelloPrint(String name) {
System.out.println("이곳은 getHelloPrint메소드입니다.");
return "Hello~~ " + name;
}
@Override
public String getGugudan(int dan) {
System.out.println("이곳은 getGugudan메소드입니다.");
String str = dan + "단<br/>";
int res = 0;
for(int i=1; i<=9; i++) {
res = dan * i;
str += dan + " * " + i + " = " + res + "<br/>";
}
return str;
}
}
AOP 적용하기위한 코드 : AopTest.java
package com.spring.TestAOP1.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.springframework.stereotype.Component;
@Component
@Aspect
public class AopTest {
@Pointcut(value="execution(* com..AopTestServiceImpl.*(..))")
private void logPointcut() {}
@Before("logPointcut()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
String methodName = joinPoint.getSignature().getName();
System.out.println("실행하는 핵심 코드(메소드): " + methodName);
String methodName2 = joinPoint.getSignature().toLongString();
System.out.println("전체이름: " + methodName2);
String methodName3 = joinPoint.getSignature().toShortString();
System.out.println("짧은이름: " + methodName3);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println("\n(공통관심사항)>>>LOG<<< : " + new java.util.Date());
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println();
}
@AfterReturning(pointcut = "logPointcut()", returning = "result")
public void log(JoinPoint joinPoint, Object result) {
System.out.println("***************************************");
String methodName = joinPoint.getSignature().getName();
System.out.println("실행하는 핵심 코드(메소드): " + methodName);
String methodName2 = joinPoint.getSignature().toLongString();
System.out.println("전체이름: " + methodName2);
String methodName3 = joinPoint.getSignature().toShortString();
System.out.println("짧은이름: " + methodName3);
System.out.println("핵심코드가 반환한 값 : " + result.toString());
System.out.println("\n(공통관심사항)>>>LOG<<< : " + new java.util.Date());
System.out.println("===================================================");
}
}
내용을 출력할 view :
home.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
Hello world!
</h1>
<P> The time on the server is ${serverTime}. </P>
<hr/>
<div><a href="<%=request.getContextPath()%>/hello">Hello호출</a></div>
<hr/>
</body>
</html>
aopMain.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="ctp" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>aopMain.jsp</title>
<script>
'use strict';
function fCheck() {
let name = document.getElementById("name").value;
location.href="${ctp}/hello?name="+name;
}
function gugudan() {
let dan = document.getElementById("dan").value;
location.href="${ctp}/gugudan?dan="+dan;
}
</script>
</head>
<body>
<p><br/></p>
<h2>AOP 연습(${message})</h2>
<hr/>
<div><a href="${ctp}/">Home으로</a></div>
<hr/>
<div>
<input type="text" name="name" id="name" value="관리자" />
<input type="button" value="성명출력" onclick="fCheck()" />
</div>
<hr/>
<div>
<c:if test="${empty gugudan}"><input type="number" name="dan" id="dan" value="5" /></c:if>
<c:if test="${!empty gugudan}"><input type="number" name="dan" id="dan" value="${param.dan}" /></c:if>
<input type="button" value="구구단출력" onclick="gugudan()" />
<div>${gugudan}</div>
</div>
<hr/>
</body>
</html>
실행결과는 다음과 같다.
========== ================
앞의 실행 결과들을 보면 공통관심사는, 핵심코드 이전과 이후에 찍히고 있음을 확인할수 있다.
'프로그래밍언어 > Springframework' 카테고리의 다른 글
Spring Container에 Spring Bean 등록하기 (0) | 2024.11.27 |
---|
댓글