1. 핵심기능과 부가기능
- 업무(Biz) 로직은 핵심 기능(Core Concerns)과 부가 기능(Cross-cutting Concerns) 으로 구성
- 핵심 기능 : 사용자가 구현하는 핵심 비즈니스 로직
- 부가 기능 : 핵심 기능을 실행하기 위해서 수행하는 부가적인 기능으로 데이터베이스 연결, 로깅, 파일 입출력 등
2. AOP 개요
OOP는 객체지향 프로그래밍, AOP는 관점지향 프로그래밍(Aspect Oriented Programming)
서로 배타적인 관계 X
OOP 안에 AOP가 포함되는. 객체지향을 좀 더 완전하게 할 수 있는 기법이라고 할 수 있음.
- OOP로 독립적으로 분리하기 어려운 부가기능 로직을 모듈화하는 방식
- 핵심관심과 전체에 적용되는 공통관심(횡단관심)을 분리하여 프로그래밍하는 방식
- AOP는 여러 객체에서 공통으로 사용하는 기능(로직)(= 위의 초록박스)을 분리해서 재사용성을 높이는 프로그래밍 기법으로 관점 지향 프로그래밍
- AOP는 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 Aspect 라는 독특한 모듈형태로 만들어서 (부가기능을 수행) 설계하고 개발하는 방법
- AOP는 부가기능을 Aspect 로 정의하여, 핵심기능에서 부가기능을 분리함으로써 핵심기능을 설계하고(부가기능은 모듈형태로 만들었으므로) 구현할 때 객체 지향적인 가치를 지킬 수 있도록 도와주는 개념
Cross-Cutting Concern이 Primary Concern 밑에서 수행될 수 있도록 Weaving(꼬매기, 짜집기) 하는 것
3. Spring AOP 특징
- Spring AOP는 Proxy 기반
(Proxy : 대리자, 위임자라고 생각하면됨) - Proxy : Advice(부가기능)를 Target 객체에 적용하면서 생성되는 객체
- Target에 대한 호출을 가로채 Advice의 부가기능을 수행한 다음 Target의 핵심기능을 호출
- Target : 핵심기능
- Aspect : 부가기능의 모듈
(원리) Caller에서 호출하는데 Proxy가 가로채서 Aspect 부가기능 수행 후 Target을 호출, 리턴받아서 추가적인 부가기능을 수행할거면 Aspect 요청 후 Caller로 리턴해줌
원래라면 Proxy, Aspect 존재 X, Target이 다 들고있음. Caller와 Target의 호출과 리턴으로만.
4. AOP 주요 용어와 의미
한 번 더
- JointPoint : 실행하는 모든 핵심관심 메서드
- PointCut : 조인포인트 가운데 실행(AOP가 설정)되는 핵심관심 메서드
- Advice : 횡단관심에 해당하는 공통의 부가기능 메서드
- Aspect : 포인트컷과 어드바이스의 결합된 모듈 형태
- Weaving : 포인트컷(핵심관심)이 실행될 때 어드바이스가 포인트컷에 결합되는 과정
< Pointcut 표현식 >
execution(리턴타입 패키지명.클래스명.메서드명(매개변수))
- 리턴타입
- * : 모든 리턴타입 허용
- void : 리턴타입이 void인 메서드
- !void : 리턴타입이 void가 아닌 메서드 - 패키지명
- kr.co.ch04 : 해당 패키지 대상
- kr.co.ch04.. : kr.co.ch04로 시작하는 모든 패키지 대상
- kr.co.ch04..service : kr.co.ch04로 시작해서 마지막 패키지명이 service로 끝나는 패키지 대상 - 클래스명
- BoardService : 해당 클래스 대상
- *Service : 클래스명이 Service로 끝나는 클래스 대상 - 메서드명
- *(..) : 매개변수가 제한이 없는 모든 메서드
- *(*) : 매개변수를 1개 갖는 모든 메서드
- *(*,*) : 매개변수를 2개 갖는 모든 메서드
- get*() : 매개변수가 없고 메서드 이름이 get으로 시작하는 메서드
[실습1] Proxy 기반 AOP
- Impl : 인터페이스 구현체 클래스 뒤에 붙이는 일종의 규칙
- TargetProxy가 AOP 모듈
- before()/after() : core concern을 도와주는 메서드
package ch03.sub1;
public class ProxyTest {
public static void main(String[] args) {
Target target = new TargetImpl();
Target proxy = new TargetProxy(target);
proxy.doBusiness();
}
}
package ch03.sub1;
public class TargetProxy implements Target {
private Target target;
public TargetProxy(Target target) {
this.target = target;
}
public void before() {
System.out.println("cross-cutting before...");
}
public void after() {
System.out.println("cross-cutting after...");
}
@Override
public void doBusiness() {
before();
target.doBusiness();
after();
}
}
package ch03.sub1;
public interface Target {
public void doBusiness();
}
package ch03.sub1;
public class TargetImpl implements Target {
@Override
public void doBusiness() {
System.out.println("core concern...");
}
}
[실습2] Spring XML기반 AOP
AOP를 적용하지 않고 바로 호출하기
LogAdvice : 횡단관심에 해당하는 부가기능 (advice 용어 자체가 부가기능을 갖는 모듈)
- Service.java
사용하기 위해 @Autowired 주입받음.
그리고 insert 위아래로 부가기능 호출
그리고 @Component ← 등록을 했으니 컨테이너에서 등록/관리를 해주어야함
-----
남은 얘네들도 저렇게 출력되게끔 할것임
위에 insert()처럼 호출하는 방식 말고 Weaving을 해줄것임
마치 하나의 로직처럼 Weaving
<Weaving 하는 방법>
설정파일 application.xml에서 Weaving을 설정해주어야 함
- application.xml
(Spring Tools 3 다운 안 받았으면 Spring 홈페이지 - 프레임워크 - Learn - 5.3.30(혹은 다른버젼)의 레퍼런스문서 - core - xmlns:aop 서치해서 넣어주면 된다.)
부가기능을 갖는 aspect 정의
@Component를 통해서 컨테이너가 생성/관리하고 있으니까 logAdvice 참조 가능
select()를 호출하면 비포시점, 애프터시점에 위빙됨
++
그리고 Aspectj 라이브러리 추가해주어야 한다.
https://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.9.20.1
<scope>runtime</scope>는 지워야 라이브러리가 밝게 활성화된다.
==> AOP 설정을 통해 select(), update(), delete() 앞 뒤로 비포, 애프터 부가기능이 실행된다.
package ch03.sub2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class AOPTest {
public static void main(String[] args) {
// 스프링 컨텍스트 객체 생성(컨테이너)
ApplicationContext ctx = new GenericXmlApplicationContext("application.xml");
// Service 객체 가져오기
Service service = (Service) ctx.getBean("service1");
service.insert();
service.select();
service.update(1);
service.delete(1, "홍길동");
}
}
package ch03.sub2;
import org.springframework.stereotype.Component;
@Component
public class LogAdvice {
public void beforeLog() {
System.out.println("----------------------------");
System.out.println("cross-cutting - beforeLog...");
}
public void afterLog() {
System.out.println("cross-cutting - afterLog...");
System.out.println("----------------------------");
}
}
package ch03.sub2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("service1")
public class Service {
@Autowired
private LogAdvice advice;
public void insert() {
advice.beforeLog();
System.out.println("Core consern - insert...");
advice.afterLog();
}
public void select() {
System.out.println("Core consern - select...");
}
public void update(int no) {
System.out.println("Core consern - update...");
}
public void delete(int no, String name) {
System.out.println("Core consern - delete...");
}
}
[실습3] Annotation기반 AOP
- application.xml 설정
Spring XML 기반 AOP와 LogAdvice부분만 차이가 난다.
Annotation 기반에서는 LogAdvice 자체가 Aspect이다.
어노테이션으로 Pointcut해주고
부가기능에 어노테이션으로 또 명시해주면 된다.
package ch03.sub3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class AnnotationAOPTest {
public static void main(String[] args) {
// 스프링 컨텍스트 객체 생성(컨테이너)
ApplicationContext ctx = new GenericXmlApplicationContext("application.xml");
// Service 객체 가져오기
Service service = (Service) ctx.getBean("service2");
service.insert();
service.select();
service.update(1);
service.delete(1, "홍길동");
}
}
package ch03.sub3;
import org.springframework.stereotype.Component;
@Component("service2")
public class Service {
public void insert() {
System.out.println("Core consern - insert...");
}
public void select() {
System.out.println("Core consern - select...");
}
public void update(int no) {
System.out.println("Core consern - update...");
}
public void delete(int no, String name) {
System.out.println("Core consern - delete...");
}
}
package ch03.sub3;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component("logAdvice2")
public class LogAdvice {
@Pointcut("execution(void ch03.sub3.Service.insert(..))")
public void insertPointcut() {} // 내용이 없는 참조 메서드
@Pointcut("execution(void ch03.sub3.Service.select(..))")
public void selectPointcut() {} // 내용이 없는 참조 메서드
@Pointcut("execution(void ch03.sub3.Service.update(*))")
public void updatePointcut() {} // 내용이 없는 참조 메서드
@Pointcut("execution(void ch03.sub3.Service.delete(*,*))")
public void deletePointcut() {} // 내용이 없는 참조 메서드
@Before("insertPointcut() || selectPointcut() || updatePointcut() || deletePointcut()")
public void beforeLog() {
System.out.println("----------------------------");
System.out.println("cross-cutting - beforeLog...");
}
@After("insertPointcut() || selectPointcut() || updatePointcut() || deletePointcut()")
public void afterLog() {
System.out.println("cross-cutting - afterLog...");
System.out.println("----------------------------");
}
}
applicaion.xml 전체코드
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 스프링 컨테이너 객체 탐색 -->
<context:component-scan base-package="ch03"/>
<!-- Annotation기반 AOP 처리를 위한 Auto proxy 설정 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- Aop 설정 -->
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(void ch03.sub2.Service.*(..))"/>
<aop:pointcut id="selectPointcut" expression="execution(void ch03.sub2.Service.select())"/>
<aop:pointcut id="updatePointcut" expression="execution(void ch03.sub2.Service.update(*))"/>
<aop:pointcut id="deletePointcut" expression="execution(void ch03.sub2.Service.delete(*,*))"/>
<!-- 부가기능을 갖는 Aspect 설정 -->
<aop:aspect ref="logAdvice">
<aop:before method="beforeLog" pointcut-ref="selectPointcut"/>
<aop:after method="afterLog" pointcut-ref="selectPointcut"/>
<aop:before method="beforeLog" pointcut-ref="updatePointcut"/>
<aop:after method="afterLog" pointcut-ref="updatePointcut"/>
<aop:before method="beforeLog" pointcut-ref="deletePointcut"/>
<aop:after method="afterLog" pointcut-ref="deletePointcut"/>
</aop:aspect>
</aop:config>
</beans>