1일(08.17) Spring Framework - AOP
💻 AOP(Aspect Oriented Programming)
관점 지향 프로그래밍
문제를 핵심사항과 공통사항을 기준으로 프로그래밍하므로써 공통 모듈을 여러코드에 쉽게 적응할 수 있도록 한다
공통관심 사항 구현한 코드를 핵심 로직을 구현한 코드 안에 삽입
- 핵심 로직을 구현한 코드에서 공통 기능을 직접적으로 호출하지 않는다.
- 핵심 로직을 구현한 코드를 컴파일하거나, 컴파일된 클래스를 로딩,
로딩된 클래스 객체를 생성할 때 AOP가 적용되어 핵심 로직 구현 코드 안에 공통 기능이 삽입된다.
- 공통 기능이 변경되더라도 핵심 로직을 구현한 코드를 변경할 필요가 없다.
(공통 기능 코드를 변경한 뒤 핵심 로직 구현 코드에 적용)
-AOP에서는 핵심기능과 공통기능을 분리시켜 핵심 로직에 영향을 끼치지 않게 곹오기능을 끼워 넣는 개발형태이다
이렇게 개발함에 따라 무분별하게 중복되는 코드를 한 곳에 모아 중복되는 코드를 제거 할 수 있게되고
공통기능을 한 곳에 보관함으로써 공통기능 하나의 수정으로 모든 핵심기능들의 공통기능을 수정 할 수 있어 효율적인 유 지보수가 가능하며 재활용성이 극대화 된다.
공통기능(Aspect)
-핵심기능 전에 수행할것.
📌 AOP 용어
Aspect : 공통기능, 여러객체에 공통으로 적용되는 공통 관심 사항 (로그인, 로그아웃)
Advice : Aspect가 수행해야 할 어떤 목록, Aspect를 어떤 사항에 적용시킬 것인지 정의
각각의 pointcut에 삽입되어 동작할 수 있는 코드
Joinpoint : Advice를 적용할 수 있는 모든 요소들을 의미
메소드 호출, 필드 값 수정, 예외 발생 등이 해당된다.
각각의 구체적인 적용 지점을 Pointcut이라고 한다.
Pointcut : Jointpoint를 구성하는 각각의 구체적인 적용 지점(메서드 실행, 예외처리, 속성 변경)
AspectJ Pointcut Expression을 사용하여 Pointcut을 표현한다.
🔴 AOP 설정
- spring에서는 jar파일을 사용하지 않는다. 대신에 아래의 <dependencies><dependency>를 사용한다.
[형식]
<dependencies>
<dependency></dependency>
</dependencies>
* pom.xml 에 아래의 코드를 작성해줍니다.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
메이븐 중앙저장소에서 aop할때 필요한 파일을 가져와서 프로젝트에 넣어서 실행된다.
(mvnrepository)
https://mvnrepository.com/
https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
📂example1
💾 Board.java
package example1;
public class Board {
public void board() {
//생성자 아니다. 클래스와 이름이 다르다
String msg ="게시물 등록";
System.out.println(msg+"을 하기 위한 로그인 수행");//공통기능(Aspect)
System.out.println(msg+"하기"); //핵심기능
System.out.println(msg+"을 DB에 저장");//공통기능
System.out.println(msg+"을 하기 위한 로그아웃 수행");//공통기능
}
}
💾 Order.java
package example1;
public class Order {
public void order() {
String msg ="상품 주문";
System.out.println(msg +"을 하기 위한 로그인 수행");//공통기능
System.out.println(msg +"하기");//핵심기능
System.out.println(msg +"을 DB에 저장");//공통기능
System.out.println(msg +"을 하기 위한 로그아웃 수행");//공통기능
}
}
package example1;
public class AOPMain {
public static void main(String[] args) {
Board b = new Board();
Order o = new Order();
b.board();
System.out.println();
o.order();
}
}
📂 example2
package example2;
public class Board {
public void board() {
//생성자가 아니다. 클래스와 이름이 다르다.
String msg="게시물 등록";
Login.login(msg);
System.out.println(msg+"하기"); //핵심기능
Logout.logout(msg);
}
}
💾 Order.java
package example2;
public class Order {
public void order() {
String msg ="상품 주문";
Login.login(msg);
System.out.println(msg +"하기");//핵심기능
Logout.logout(msg);
}
}
💾 Login.java
package example2;
public class Login {
//공통기능 클래스 하나만 만든다.
public static void login(String msg) {
System.out.println(msg+"하기 전 로그인 수행");
}
}
💾 Logout.java
package example2;
public class Logout {
public static void logout(String msg) {
System.out.println(msg+"한 후 로그아웃 수행");
}
}
💾 AOPMain.java
package example2;
public class AOPMain {
public static void main(String[] args) {
Board b = new Board();
Order o = new Order();
b.board();
System.out.println();
o.order();
}
}
📂example3
💾 Hello.java
package example3;
public class Hello {
public void hello() {
System.out.println("안녕하세요");
}
}
💾 Login.java
package example3;
public class Login {
public void login() {
System.out.println("로그인수행");
}
}
💾 Logout.java
package example3;
public class Logout {
public void logout() {
System.out.println("로그아웃 수행");
}
}
💾 Board.java(interface로 생성)
package example3;
public interface Board {
public void board();
}
💾 BoardImpl.java
package example3;
public class BoardImpl implements Board{
//여기가 핵심 나머지는 공통기능
@Override
public void board() {
String msg="게시물 등록";
System.out.println(msg+" 하기");
}
}
💾 Order.java(interface로 생성)
package example3;
public interface Order {
public void order();
}
💾 OrderImpl.java
package example3;
public class OrderImpl implements Order{
@Override
public void order() {
String msg="상품 주문하기";
System.out.println(msg+" 하기");//핵심 기능
}
}
💾 AOPMain.java
package example3;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class AOPMain {
public static void main(String[] args) {
AbstractApplicationContext context = new GenericXmlApplicationContext("aopExam.xml");
System.out.println("---게시물 등록 하기---");
Board myboard = (Board)context.getBean("myboard");
myboard.board();
System.out.println();
System.out.println("---상품 주문 하기---");
Order myorder = (Order)context.getBean("myorder");
myorder.order();
}
}
AOPMain에 아래와 같이 출력되게끔 해보았다.
로그인수행
안녕하세요
DB 작업 수행함
게시물등록하기:핵심기능
로그아웃수행
로그인수행
상품하기:핵심기능
DB 작업 수행함
로그아웃수행
- GenericXmlApplicationContext 클래스는 xml파일에 정의된 설정 정보를 읽어와서 객체를 생성하고, 각각의 객체를 연결 한 뒤에 내부적으로 보관한다.
GenericXmlApplicationContext 에서 생성할 객체가 무엇인지 설정, 객체를 어떻게 연결할지, 정보는 xml파일에 설정한다.
📌 spring 컨테이너 종류
- BeanFactory 인터페이스 : Bean 객체를 관리, Bean 객체간의 의존관계를 설정해주는 기능을 제공
XmlBeanFactory 클래스가 있다.
- AbstractApplicationContext : 컨테이너 종료(close)와 같은 기능을 제공해 주는 객체.
- GenericXmlApplicationContext : xml파일에 정의된 설정 정보를 읽어와서 객체를 생성하고, 각각의 객체를 연결 한 뒤에 내부적으로 보관한다.
- GenericXmlApplicationContext 에서 생성할 객체가 무엇인지 설정, 객체를 어떻게 연결할지, 정보는 xml파일에 설정한다.
- AbstractApplicationContext 객체를 상속 받아 만드는 클래스,
- xml 파일에서 스프링 빈 설정 정보를 읽어오는 역할.
- 객체를 생성할 때 파라미터 값으로 xml의 경로를 전달하여 설정 파일로 사용
- 객체는 전달받은 xml파일에서 설정 정보를 읽어와서 Bean 객체를 생성하고 property 값을 설정한다.
💾 aopExam.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<!-- example3.BoardImpl myboard = example3.BoardImpl() -->
<bean class="example3.BoardImpl" id="myboard"/>
<bean class="example3.Hello" id="myhello"/>
<bean class="example3.Login" id="mylogin"/>
<bean class="example3.Logout" id="mylogout"/>
<bean class="example3.Dao" id="mydao"/>
<bean class="example3.OrderImpl" id="myorder"/>
<!-- 핵심기능을 실행하기 전에 실행할 것이다. -->
<aop:config>
<aop:aspect ref="mydao" order="3">
<aop:before method="dao" pointcut="execution(* example3.BoardImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="mylogin" order="1">
<aop:before method="login"
pointcut="execution(* example3.BoardImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="myhello" order="2">
<aop:before method="hello" pointcut="execution(* example3.BoardImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="mylogout">
<aop:after method="logout" pointcut="execution(* example3.BoardImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="mylogin">
<aop:before method="login" pointcut="execution(* example3.OrderImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="mydao" order="2">
<aop:after method="dao" pointcut="execution(* example3.OrderImpl.*())"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspect ref="mylogout" order="1">
<aop:after method="logout" pointcut="execution(* example3.OrderImpl.*())"/>
</aop:aspect>
</aop:config>
📌 xml Schima를 이용한 AOP설정
<aop:aspect ref="mylogin" order="1">
: order를 지정하여 순서대로 출력하게 할 수 있다. 순서는 상대적임.
<aop:config> : aop 설정하겠다는 의미
<aop:aspect ref="mylogout" order="1"> : aspect(공통기능)을 지정, ref속성: 공통기능을 구현하고 있는 Bean
<aop:after method="logout" pointcut="execution(* example3.OrderImpl.*())"/>
-<aop:pointcut> : pointcut을 설정, expression속성: Advice를 적용할 pointcut에 대한 정보 지정
</aop:aspect>
</aop:config>
- after: 숫자 큰것이 우선순위로 실행되고
- before: 숫자 작은것이 우선순위로 실행된다.
📌 AspectJ Pointcut Expression 예시
<aop:before method="login" pointcut="execution(* example3.BoardImpl.*())"/>
: 메서드()괄호 안에 매개변수(파라미터가)가 없는, 아무 메서드 실행 전에 로그인 클래스 안에 로그인 메서드가 있냐
로그인메서드 먼저 실행해라
execution(* example3.BoardImpl.*()) : example3.BoardImpl의 어떤 메서드 상관 안한다.
mylogin 변수로 관리하는 example3.Login 클래스 안에 메서드를 실행해라
- execution(public void set*(..)) : 리턴타입이 void, 패키지명 없음, 메서드 이름이 set으로 시작, 파라미터 0개 이상인 메서드 호출
- execution(* 패키지이름.클래스이름.*()) : 괄호앞에 있는것은 메서드 이름(이름을 *), 파라미터가 없는 메서드 호출, 앞에 있는 *은 리턴타입 아무 타입으로 리턴해라
- execution(*.패키지이름..*.*(..)) : 패키지의 하위 패키지에 있는 파라미터가 0개 이상인 메소드 호출
- execution(* read*(Integer), ..) : 메서드 이름이 read로 시작, 앞에 정수가 꼭 와야한다. 1개 이상 파라미터 갖는 메소드 호출
- execution(* get*(*), ..) : 이름이 get으로 시작, 파라미터 1개 와야 한다
- execution(* get*(*,*), ..) : 이름이 get으로 시작, 파라미터 2개 와야 한다
📂 example4
💾