[Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (1)
참고로 AOP는 Spring-Framework에만 적용되는 방법론이다.
새로 만들어진 개념은 아니고, 자바의 Proxy(패턴 아님. 탈취기법이 있음.)에서 핵심 영역의 관점으로 진화한 개념이다.
교과서나 시중 책 등에서는 이런 표현은 안 쓰긴 하지만, "탈취 방법"이라고 하는 게 적절하다고 주장하다.
해당 작업은 Spring Framework에서만 가능한 작업이다.
IDE: Spring Tool-Suite 4-4.7.2 Release(2020-08월)
Library:
- Spring Framework 5.2.9.RELEASE (2020-09-15)
- Java Version: 14
- AspectJ: 1.9.6 (2020-07-22)
- AspectJ Weaver: 1.9.6 (2020-07-22)
- javax.servlet-api 4.0.1 (2018-04-20)
(순정 상태로 셋팅하였음.)
Project: Spring Legacy Project로 생성할 것
-> 안 보이는 사람들은 Help->Eclipse Marketplace -> STS 검색 후 "Spring Tools 3 Add-On for Spring Tools 4 3.9.14.RELEASE" 설치할 것
그림 1. STS-Addon (Eclipse Marketplace)
1. 이전의 개발 방법론(Proxy)
지금도 물론 이런 방법은 안 쓰일 수는 없다.
AOP가 먼저 나온 것이 결코 아니다. 아마 c#에도 이런 게 있는 걸로 알고 있다. (오래되서)
프로그래밍 개발을 하다보면, 로직을 구현하는데 있어서 코어 코드를 수정해서 작업을 하는 일이 많이 있다.
핵심 규칙은 x, y, z만 더해서 결과만 출력하면 되는 요구사항을 가진 문제인데 구현을 하다보면, 현실적인 문제라는 게 발생하게 된다.
그림 2. 이전의 로직 구현 방법 - 개발
그림 1과 같은 문제는 자주 생기는 문제이기도 하다.
이런 방법으로 구현하는 것이 아예 사라졌다는 게 아니다.
그림 1에서 (1단계)의 핵심 규칙을 재사용할 기회를 상실해버리는 문제가 발생하게 된다.
그림 3. 문제 인식
그림 2처럼 문제가 생겼다고 하자.
물론 머리가 조금 좋은 분들은 객체의 상속 관계로 처리해도 해결할 수 있지 않겠냐고 할 수도 있다.
(객체지향 프로그래밍에서도 설계를 다시 해서 이런 문제를 나눠볼 수도 있겠음.)
허나 쉬운 일이 결코 아니다. 코드라는 게 이쪽에서 사용되고 있는지, 저기에서 사용되고 있는지 작성을 하게 되면 수정이 정말 어렵다는 것이다.
물론 C#을 논하는 자리가 아니어서 자바를 예로 들면, Proxy 탈취 방법이 존재한다.
그림 4. Proxy 탈취 방법 - 자바
자바에서는
Type of Classes username new Proxy.newProxyInstance(Classes.class.getClassLoader(), new Class[] { classes.class } ,
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object args[] throws Throwable{
// 전단계 호출
Object result = method.invoke(sum, args);
// 후단계 호출
}
}
};
슈도 코드(Pseudo Code) 1. 자바의 Proxy 호출 방법
이런 방법으로 핵심 로직을 탈취하는 방법이 있다.
기본적인 원리는 이런 원리에 입각해서 만들어졌다고 보면 된다.
AOP의 용어 설명은 나중에 할 것이다. 절대로 지금 바로 알면 안 된다고 생각한다.
3단계를 기본으로 한다. "전 단계, 핵심 로직, 후 단계"
이 단계의 확장적인 개념이다.
Spring AOP에서는 5단계로 확장되었다.
전단계: {Around 단계, Before 단계},
핵심로직: 핵심 로직(중간)
후단계: {after Method 호출, AfterThrowing(선택 - 예외 처리 발생 때만)}
조금 더 이해하기 쉬워졌을 것으로 보인다.
[첨부(Attachments)]
2. 코드로 살펴보는 AOP
AOP를 이론으로만 봐서는 이해가 무척 안 될 것이다. 구현을 통해서 단계적으로 살펴보도록 하겠다.
프로젝트 생성부터 단계적으로 소개하겠다.
그림 5. 프로젝트 생성하기
File을 클릭한다.
New -> Spring Legacy Project를 클릭한다.
그림 6. 프로젝트 생성하기
Spring MVC Project를 선택한다.
Project name을 입력한다.
Next를 누른다.
그림 7. 프로젝트 생성하기
Top-level-package를 입력한다. (예: com.local.example)
Finish를 누른다.
3. POM.xml 설정하기
http://mvnrepository.com 사이트에 접속한다.
검색 키워드에 "aspectj"라고 입력한다. (세 가지 검색을 해서 버전을 획득해야 함)
검색 키워드에 "spring"라고 입력한다.
검색 키워드에 "servlet"이라고 입력한다.
두 가지의 Maven 주소를 획득해야 한다.
그림 8. MVNRepository 검색 결과의 예 (1) - AspectJ
그림 9. MVNRepository 검색 결과의 예 (1) - AspectJ
버전을 클릭하면, Maven Pom이 나온다.
이걸 복사 붙여넣기를 해도 된다.
다만 <version>만 복사해서 관리하는 방법도 있으니 자세한 건 POM.xml 소스를 참고하면 좋겠다.
그림 10. pom.xml - 변경 작업 모습(1)
<java-version>14</java-version>으로 변경한다.
<org.springframework-version>5.2.9.RELEASE</org........>로 변경한다.
<org.aspectj-version>1.9.6</org......>으로 변경한다.
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.local</groupId>
<artifactId>example</artifactId>
<name>springAOP-javaConfig</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<!-- 자바 버전 변경함 -->
<java-version>14</java-version>
<!-- 스프링프레임워크 버전 변경함 -->
<org.springframework-version>5.2.9.RELEASE</org.springframework-version>
<!-- AspectJ 변경함 -->
<org.aspectj-version>1.9.6</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ 버전 변경 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- AspectJWeaver 추가 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
파일명: pom.xml
[첨부(Attachments)]
4. Project의 Properties - Build Path, Project Factes 설정하기 (Java Version 변경)
프로젝트의 자바 버전을 변경할 것이다.
그림 11. 프로젝트의 마우스 오른쪽 버튼 메뉴 모습
프로젝트를 선택한 후 마우스 오른쪽 버튼을 누른다.
Properties를 클릭한다.
그림 12. Java Build Path
Java Build Path를 클릭한다.
JRE System Library를 [JavaSE-14]로 변경한다.
그림 13. Project Factes
Project Factes를 클릭한다.
Java의 버전을 14로 바꿔준다.
5. web.xml - 서블릿 스팩 (2.5에서 4.0으로 변경)
서블릿 스팩을 변경할 것이다.
web.xml에서 변경해주면 된다.
그림 14. web.xml (servlet 4.0 스팩으로 변경)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
파일명: web.xml
[첨부(Attachments)]
6. service - Calculator.java
복잡한 설명을 적기보다는 작업화면하고 코드를 소개하겠다.
그림 15. Calculator.java
package com.local.example.service;
public class Calculator {
private long x;
private long y;
private long z;
public Calculator(long x, long y, long z) {
this.x = x;
this.y = y;
this.z = z;
}
// 작업 원본(A 프로그래머 작성함)
public long sum() {
long result = x + y + z;
return result;
}
}
파일명: Calculator.java
[첨부(Attachments)]
Calculator-original.zip
참고로 지금 코드로는 AspectJ가 동작되지 않는 코드이다.
이유는 초기 매개변수가 없는 생성자가 없어서 그렇다.
(다음 페이지에서 소개하겠음.)
7. Controller - HomeController.java
아마 순정 코드에 Calculator의 sum()를 호출한 것을 보면, 다음과 같이 되어있다.
package com.local.example;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.local.example.aop.Calculator;
import com.local.example.aop.RootConfig;
/**
* Handles requests for the application home page.
*/
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@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);
Calculator cal = new Calculator(1, 2, 3);
System.out.printf("result of sum: %d", cal.sum());
model.addAttribute("serverTime", formattedDate );
return "home";
}
}
파일명: HomeController.java
이렇게 하면, 잘 동작할 것이다.
결과가 "result of sum: 6"이 출력될 것이다.
* 2부에서 AOP 사용 코드로 전환하는 방법에 대해서 소개하겠다.
1. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (2), 2020-10-04