ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링(Spring) 개발 - (16) AOP 설정하기 (부제: Controller에도 AOP 적용하기)
    Spring 2015. 8. 20. 18:39

    이번 글에서는 AOP라는 것에 대해서 이야기를 하려고 합니다.


    여태까지 글에서는 "이게 스프링의 특성이다."라는 것이 없는, 일반적인 개발에 대한 이야기 였습니다.


    스프링 프레임워크를 사용한다고 이야기를 하기 어려웠었죠. 


    여태까지 나왔던 내용들은 사실 스프링이 아닌 다른 프레임워크들 예를들어 스트럿츠2와 같은 프레임워크를 사용하는거랑 차이가 별로 없습니다. 


    그래서 이번글에서는 "스프링이기에 가능한 기능"에 대해서 이야기를 하려고 합니다. 


    이번글에서는 소스를 작성할 것은 별로 없지만, 이론적으로 복잡한 그렇지만 중요한 내용이 나오게 됩니다. 


    일단 기본적인 개념을 설명한 후, 소스를 통해서 그 개념이 어떻게 구현되는지 살펴보겠습니다. 


    AOP 개발방식에는 몇가지가 있고 AOP를 설정하는 방법에는 @AspectJ 어노테이션을 이용한 방법과 XML을 이용한 방법 2가지가 있습니다.


    여기서는 AspectJ를 이용한 어노테이션을 이용한 방법을 이용합니다.


    그리고 인터넷에 AOP에 대해 나와있는 내용 중 상당수가 설명이 부족한 경우가 있습니다.


    특히 Controller에 AOP가 적용이 안된다거나 (사실 보통 Controller에는 AOP를 적용하지 않습니다. Interceptor가 있기 때문이죠. 그렇지만 어쩔수없이 Controller에도 AOP를 적용해야 할 수도 있기 때문에 여기서는 같이 설명을 하겠습니다.) 스프링의 다른 기능과 충돌이 나는 경우가 있습니다.


    글 하단에서는 이러한 부분과 함께 지금까지의 글에서 잘못 설정한 부분을 같이 설명 하려고 합니다. 


    ------------------------------------------------------------------------------------


    1. AOP란?

    스프링의 가장 큰 특징을 이야기하라고 하면 여러가지가 있겠지만, 가장 중요한 것은 IoC, DI 그리고 AOP가 아닐까 싶다.

    각각 Inversion of Controller(제어 역전), Dependency Injection(의존관계 주입), Aspect Orient Programming(관점 지향 프로그래밍)의 약자이다. 

    이 중에서 AOP에 대해서 조금 더 알아보자. 

    관점 지향 프로그래밍이라는 단어를 처음 들어보는 경우도 있겠지만, 우리는 비슷한 단어를 많이 들어봤었다.

    바로 Java의 OOP(Object Oriented Programming) - 객체 지향 프로그래밍이라는 단어와 앞 단어만 다르다는것을 알 수 있다. 


    그럼 AOP에 대해서 이야기하기에 앞서 OOP가 무엇인지 간단히 살펴보자.

    OOP라는 개념은 기존 C언어 등에서 사용되던 구조적 프로그래밍의 개념과 완전히 달랐다. 

    구조적 프로그래밍에서 OOP로 넘어가면서 개발자들은 큰 혼란을 겪기도 했다고 한다. 

    그렇미나 객체의 개념이나 재사용등과 같은 어려가지 개념들은 기존의 구조적 프로그래밍을 하던 사람들에게 쉽게 받아들여지지 않기도 했다고 한다.

    그렇지만 OOP의 뛰어난 효율성으로 자바는 지금과 같이 널리 사용되게 되었다. (물론 효율성 하나만 가지고 널리 사용되게 된건 아니다.)


    잠깐 상식으로 보통 OOP라고 하면 Java를 이야기하기 때문에 OOP하면 Java, Java하면 OOP로 생각을 하고, 자바가 최초의 OOP 언어인걸로 생각하는 사람도 가끔 있는데, 최초로 OOP의 개념을 구현한것은 스몰토크 라는 언어이다. 

    스몰토크는 정말 100% OOP 언어였는데, 그렇기 때문에 불편한 점도 적지 않았다. 

    자바에서 변수를 선언할 때, int num, String str; 이런식으로 변수를 선언할 수가 있는데 스몰토크는 무조건 new를 통해서 생성해야 했다고 한다.

    모든 변수를 선언할 때 new를 통해서 객체를 만들어야지만 사용할 수 있다고 생각을 하면 사실 좀 끔찍하지 않은가?-_-;

    자바는 그런 불편한 점을 개선한 OOP 언어다. 


    구조적 프로그래밍에서 객체 지향 프로그래밍으로 넘어오면서 많은 혼란이 있었는데, 이번에는 관점 지향 프로그래밍이라는 새로운 개념이 등장하면서

    많은 개발자들은 "또 새로운걸 공부해야해?" 라면서 엄청난 압박을 받았다.

    새로운 기술이 나왔을 때 프로그래머의 반응.jpg


    그렇지만 AOP는 OOP를 대신하는 새로운 개념이 아니라, OOP를 더욱 OOP답게 사용할 수 있도록 도와주는 개념이다. 

    객체를 재사용함으로써 개발자들은 반복되는 코드의 양을 굉장히 많이 줄일수가 있었다. 

    그렇지만 객체의 재사용에도 불구하고 반복되는 코드를 없앨수는 없었는데, 예를 들어 로그, 권한 체크, 인증, 예외 처리 등 필수적으로 해야하기 때문에 소스에서 반복될 수 밖에 없는 부분도 있다. 

    AOP는 이러한 부분을 해결해준다. 

    기능을 비지니스 로직과 공통 모듈로 구분한 후에 개발자의 코드 밖에서 필요한 시점에 비지니스 로직에 삽입하여 실행되도록 한다. 

    즉, OOP에서는 공통적인 기능을 각 객체의 횡단으로 입력했다면, AOP는 공통적인 기능을 종단간으로 삽입할 수 있도록 한 것이다.


    위의 이미지는 OOP에서 로직흐름을 보여준다. 

    계정, 게시판, 계좌이체 라는 로직을 처리할 때, 모두 똑같이 권한,로깅, 트랜잭션을 처리해줘야한다. 따라서 모든 로직에 똑같은 코드가 반복적으로 삽입될 수 밖에 없다. 

    즉, 권한, 로깅, 트랜잭션이라는 관심(Aspect)이 횡단으로 삽입이 되는 것이다. 


    그렇지만 AOP에서는 이러한 관심을 종단으로 삽입할 수 있도록 해준다. 

    AOP에서는 모든 관심을 종단으로 삽입시켜준다. 

    위의 이미지와 비교했을 때, 권한, 로깅, 트랜잭션과 같은 관심이 각 계층의 바깥쪽에서 종으로 연결이 된것을 볼 수 있다. 

    기존의 프로그래밍에서는 각 객체별로 처리했던 것을 각 관점별로 외부에서 접근을 하는것이 AOP의 핵심이다. 

    즉 개발자는 계정, 게시판, 계좌이체와 같은 기능을 만들고, 공통적인 관심을 처리하는 모듈을 분리해서 개발한 후, 필요한 시점에 자동으로 소스코드가 삽입되도록 하는것이다.

    이렇게만 설명하면 이게 대체 무슨 소리인가 싶을테니까, 소스를 통해서 살펴보자.

    AOP의 개념에 대해서는 소스를 살펴본 후 좀 더 자세히 알아보도록 하겠다.


    2. AOP 설정하기

    AOP를 이용해서 여러가지를 할 수 있지만, 여기서는 가장 많이 쓰이는(이라고 쓰고 만만한이라고 읽는) 로깅을 적용시켜보자.


    1. pom.xml

    pom.xml에 다음의 properties와 dependency를 추가하자. (기존 first 프로젝트에는 이미 추가되어 있다.)

    properties

    <org.aspectj-version>1.7.3</org.aspectj-version>
    


    dependency

    <!-- AspectJ -->
    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjrt</artifactId>
    	<version>${org.aspectj-version}</version>
    </dependency>
    
    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjweaver</artifactId>
    	<version>${org.aspectj-version}</version>
    </dependency>
    
    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjtools</artifactId>
    	<version>${org.aspectj-version}</version>
    </dependency>
    


    2. Java

    first > common > logger 패키지 밑에 LoggerAspect.java 파일을 만들고, 다음의 소스코드를 작성한다.

    package first.common.logger;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    @Aspect
    public class LoggerAspect {
    	protected Log log = LogFactory.getLog(LoggerAspect.class);
    	static String name = "";
    	static String type = "";
    	
    	@Around("execution(* first..controller.*Controller.*(..)) or execution(* first..service.*Impl.*(..)) or execution(* first..dao.*DAO.*(..))")
    	public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
    		type = joinPoint.getSignature().getDeclaringTypeName();
    		
    		if (type.indexOf("Controller") > -1) {
    			name = "Controller  \t:  ";
    		}
    		else if (type.indexOf("Service") > -1) {
    			name = "ServiceImpl  \t:  ";
    		}
    		else if (type.indexOf("DAO") > -1) {
    			name = "DAO  \t\t:  ";
    		}
    		log.debug(name + type + "." + joinPoint.getSignature().getName() + "()");
    		return joinPoint.proceed();
    	}
    }

    LoggerAspect라는 클래스는 Controller, Service, DAO가 실행될 때, 어떤 계층의 어떤 메서드가 실행되었는지를 보여주는 역할을 한다.

    해당 소스에 대한 내용은 일단 설정을 마무리하고 결과를 확인한 다음에 설명을 하도록 하겠다.


    3. XML

    1) action-servlet.xml 

    action-servlet.xml을 다음과 같이 수정한다. 

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:context="http://www.springframework.org/schema/context"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    	<context:component-scan base-package="first" use-default-filters="false">
    		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    	</context:component-scan>
    	
    	<mvc:annotation-driven>
    		<mvc:argument-resolvers>
    			<bean class="first.common.resolver.CustomMapArgumentResolver"></bean>		
    		</mvc:argument-resolvers>
    	</mvc:annotation-driven>
    	
    	<mvc:interceptors>
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/>
    			<bean id="loggerInterceptor" class="first.common.logger.LoggerInterceptor"></bean>
    		</mvc:interceptor>
    	</mvc:interceptors>
    	
    	<aop:aspectj-autoproxy/>
    	<bean id="loggerAspect" class="first.common.logger.LoggerAspect" />
    	
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
        
        <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0" />
        <bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />    
        
        <bean
        	class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:order="1" 
        	p:viewClass="org.springframework.web.servlet.view.JstlView" 
        	p:prefix="/WEB-INF/jsp/" p:suffix=".jsp">
       	</bean>
    </beans>
    

    기존과 비교하여 몇가지가 바뀌었다. 

    먼저 aop라는 태그를 사용하기위해서 7, 11번째줄에 aop 태그를 쓸 수 있도록 선언을 해주었다. 

    다음으로 13~15번째줄이 바뀌었다. 기존에는 <context:component-scan base-package="first></context:component-scan>만 되어 있었는데, 

    component-scan에 use-default-filters 라는 옵션이 들어가고 <context:include-filter>라는 부분이 추가되었다. 

    component-scan의 역할은 스프링에서 자동적으로 스테레오 타입의 어노테이션을 등록해주는 역할은 한다. 

    우리는 @Controller, @Service, @Repository, @Component 라는 어노테이션을 사용했었다. 

    단순히 이러한 어노테이션을 붙이기만 해도 각각 Controller, Service, DAO의 역할을 할 수 있었던 이유가 component-scan을 통해서 스프링이 자동적으로 bean을 등록시켜줌으로써 그 기능을 했던 것이다. 

    이것이 우리가 Controller, Service, DAO 또는 FileUtils와 같은 Component를 생성을 하지 않고서도 사용할 수 있었던 이유이다. 

    만약 component-scan을 하지 않았다면, Controller, Service, DAO를 생성할 때 마다 xml 파일에 작성해야지만 사용할 수 있다. 굉장히 비효율적인 일이 아닐수가 없다. 

    그런데 여기서 use-default-filters="false"라는 값을 주게되면 이와 같은 어노테이션을 자동으로 검색하지 않겠다는 뜻이 된다. 

    그리고 <context:include-filter> 태그에 해당하는 어노테이션만 검색하고 bean을 등록시켜 준다. 여기서는 Controller 어노테이션만 검색을 한 것이다. 

    나머지 @Service, @Repository, @Component 어노테이션은 다른곳에서 component-scan을 하게 할 것이다.

    왜 이렇게 설정하는지 의문이 들 것이다. 

    이는 root context와 servlet context의 차이때문에 그렇다. 이 둘의 차이점은 나중에 설명을 하도록 하겠다. 


    마지막으로 30~31번째 줄에 방금 만든 LoggerAspect를 등록하였다. 


    2) context-common.xml

    이번에는 context-common.xml을 다음과 같이 수정한다.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:context="http://www.springframework.org/schema/context"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:cache="http://www.springframework.org/schema/cache"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    						http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    						http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
           					http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
        
        <context:component-scan base-package="first">
    		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    	</context:component-scan>
           					
        <!-- MultipartResolver 설정 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        	<property name="maxUploadSize" value="10000000" />
            <property name="maxInMemorySize" value="100000000" />
        </bean>
    </beans>
    

    14~16번째 줄 component-scan 부분이 추가되었다. 

    아까 action-servlet.xml에서 Controller만 추가했는데, context-common.xml에서 @Controller를 제외한 나머지를 등록시켜 주는 역할을 한다. 

    여기서 볼것은 <context:exclude-filter> 태그다. 앞선 action-servlet.xml에서는 include였고, 여기서는 exclude인것을 명확히 확인하자. 


    3) context-aspect.xml

    src/main/resources 밑의 config > spring 폴더 밑에 context-aspect.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: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/context http://www.springframework.org/schema/context/spring-context.xsd 
    							http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    	
    	<aop:aspectj-autoproxy/>
    	<bean id="loggerAspect" class="first.common.logger.LoggerAspect" />
    </beans>
    

    이 파일의 내용은 별로 없다. 

    사실 이 내용은 context-common.xml 등 기존의 xml 파일에 작성을 해도 되지만, 앞으로 다른 AOP를 작성할 상황을 대비해서 따로 xml파일을 생성하였다. 

    여기서 실제로 봐야할 내용은 10~11번째 줄이다. 여기서 잘 살펴보면 action-servlet.xml에서 AOP를 설정한 것과 동일한 것을 알 수 있다. 

    왜 똑같은 것을 두번 설정을 했냐면 위에서 잠시 이야기한 root context와 servlet context의 차이 때문이다. 나중에 한꺼번에 설명을 하도록 하겠다.


    여기까지 작성을 하고나면 다음과 같은 구조를 가진다.


    이것으로 관련 설정은 완료가 되었다. 


    4. 실행

    그럼 이제 앞에서 설정한 AOP가 어떻게 동작하는지 살펴보자.

    first 프로젝트를 실행시킨 후 이클립스의 로그를 확인해보자.


    로그의 빨간색 박스 부분을 잘 살펴보면, 기존에는 없었던 로그가 찍히는것을 볼 수 있다.

    RequestURI를 보면 /first/sample/openBoardList.do 이기 때문에 게시글 목록을 가져오는 주소라는 것을 알 수 있는데, 기존에는 볼수 없었던 Controller, ServiceImpl, DAO 라는 로그가 찍히는것을 알 수 있다. 

    이를 잘 살펴보면 게시글을 가져오기 위해서 각각의 계층에서 호출한 메스드의 이름인 것을 깨달을 수 있다. 

    위에서 AOP의 개념을 이야기 할 때 사용한 이미지에서 각 계층별로 로그를 출력하는 것을 구현한 것이다. 

    즉, 기존의 소스를 건드리지 않고 위와 같은 로그를 "삽입"하여 실행시킨 것이다. 

    다시말해, 새로운 Controller, Service, DAO를 만들더라도 AOP를 통해서 로그가 출력이 되는 것이다. 

    앞에서 각 관점별로 외부에서 접근한다고 이야기를 했던 것이 바로 이러한 것이다. 


    이제, 앞에서 설명하지 않았던 LoggerAspect.java에 대해서 간단히 살펴보자.

    먼저 9번째 줄에서 새로운 어노테이션인 @Aspect를 볼 수 있다. 

    action-servlet.xml과 context-aspect.xml에서 <aop:aspectj-autoproxy/>를 사용했었는데 @Aspect 어노테이션을 통해서 bean을 등록시켜주는 역할을 한다.


    이제 AOP의 주요 개념에 대해서 알아보자.

    1) 관점(Aspect)

    구현하고자 하는 횡단 관심사의 기능을 의미한다. 한개 이상의 포인트컷과 어드바이스의 조합으로 만들어진다.


    2) 조인포인트(Join point)

    관점(Aspect)를 삽입하여 어드바이스가 적용될 수 있는 위치를 말한다. 


    3) 어드바이스(Advice)

    관점(Aspect)의 구현체로 조인 포인트에 삽입되어 동작하는 코드이다. 

    어드바이스는 조인포인트와 결합하여 동작하는 시점에 따라 5개로 구분된다.

    • Before Advice : 조인포인트 전에 실행되는 advice
    • After returning advice : 조인포인트에서 성공적으로 리턴 된 후 실행되는 advice
    • After throwing advice : 예외가 발생하였을 경우 실행되는 advice
    • After advice : 조인포인트에서 메서드의 실행결과에 상관없이 무조건 실행되는 advice, 자바의 finally와 비슷한 역할을 한다.
    • Around advice : 조인포인트의 전 과정(전, 후)에 수행되는 advice

    4) 포인트컷(PointCut)

    어드바이스를 적용할 조인 포인트를 선별하는 과정이나 그 기능을 정의한 모듈을 의미한다. 패턴매칭을 이용하여 어떤 조인포인트를 사용할 것인지 결정한다.


    5) 타겟(Target)

    어드바이스를 받을 대상, 즉 객체를 의미한다. 비지니스로직을 수행하는 클래스일수도 있지만, 프록시 객체(Object)가 될 수도 있다.


    이 외에도 위빙(Weaving), 프록시(Proxy) 등의 여러가지 용어가 있지만, 지금 단계에서는 모두 알 필요는 없다. 

    (사실 필자도 다 모른다.)




    이제 위의 내용을 바탕으로 15번째 줄을 잘 살펴보자.

    @Around("execution(* first..controller.*Controller.*(..)) or execution(* first..service.*Impl.*(..)) or execution(* first..dao.*DAO.*(..))")


    먼저 @Around다. 

    어드바이스는 어노테이션이 붙은 메서드를 이용해 정의한다.

    AspectJ에서는 다섯 가지 종류의 Advice를 이용할 수 있는데 각각 @Before, @AfterReturning, @AfterThrowing, @After, @Around 어노테이션으로 표현된다.

    @Around로 시작하기 때문에, Around Advice임을 알수 있다.


    그 다음으로 "execution"은 포인트컷 표현식 부분이다. 

    포인트컷 표현식은 위에서 사용된 execution()을 포함하여 여러가지 포인트컷 지시자(PointCut Designator)를 사용할 수 있다. 

    그 종류는 다음과 같다.

    • execution() : 가장 대표적이고 강력한 지시자로, 접근제어자, 리턴 타입, 타입 패턴, 메서드, 파라미터 타입, 예외 타입 등을 조합해서 메서드까지 선택가능한 가장 정교한 포인트컷을 만들수 있다.
    • within() : 타입 패턴만을 이용하여 조인포인트를 정의한다.
    • this : 빈 오브텍트의 타입의 조인토인트를 정의한다.
    • target : 대상객체의 타입 비교를 이용한 조인포인트를 정의한다.
    • args : 메서드의 파라미터 타입만을 이용하여 조인포인트를 정의한다.
    • @target : 특정 어노테이션이 정의된 객체를 찾는 조인포인트를 정의한다.
    • @args : 특정 어노테이션을 파라미터로 받는 오브젝트를 찾는 조인포인트를 정의한다. 
    • @within : @target과 유사하게 특정 어노테이션이 정의된 객체를 찾는데, 선택될 조인포인트 메서드는 타겟 클래스에서 선언이 되어있어야 한다.
    • @annotation : 조인 포인트 메서드에 특정 어노테이션을 찾는 조인포인트를 정의한다.

    이렇게 종류를 써 놨지만, 필자도 다 사용을 해본것은 아니다. 

    보통 execution 하나로 다 처리를 해버린다....ㅡ_ㅡ;;;


    그 다음으로 * first..controller.*Controller.*(..) 부분을 살펴보자.

    처음에 first.. 라고 되어있는것을 볼 수 있다. 이는 first 패키지 밑의 모든 서브패키지를 의미한다.

    그 다음 controller. 는 controller 패키지 밑의 클래스와 인터페이스만을 지정한다. 

    그 다음 *Controller.는 Controller라는 이름으로 끝나는 것을 의미한다.

    마지막으로 *(..)은 모든 메서드를 의미한다.


    그 다음으로 or는 포인트컷 표현식을 조합할 수 있다. 

    포인트컷의 조합식에는 or, and, not 3가지를 사용할 수 있으며 각각 ||, &&, !으로 표현할 수도 있다. 

    즉 @(..)) || execution 이런식으로 표현해도 무방하다는 의미이다. 


    여기서 AspectJ를 이용한 장점 한가지를 이야기하려고 한다. 

    AOP를 설정할 때는 하나 이상의 포인트컷과 어드바이스를 가져야하는데 AspectJ를 사용하면 위에서 보는것과 같이 어노테이션을 이용하여 어드바이스에 포인트컷을 직접 지정해 줄 수 있다.

    만약 이러한 방식이 되지 않는다면, 포인트컷과 어드바이스를 따로따로 정의해야한다. 굳이 불편한 방법을 선택할 이유는 없으니, 이 부분은 넘어가도록 한다.


    그 외 소스 부분은 따로 설명하지 않아도 될것으로 생각한다. 


    이제 마지막으로 설명할 부분은 왜 action-servlet.xml과 context-aspect.xml 두 곳에서 AOP를 설정했는지에 대한 것이다. 

    이는 Application Context의 계층구조와 연관이 되어 있다. 

    지금 우리의 프로젝트는 2개의 컨텍스트가 설정이 되어있다. 

    하나는 action-servlet.xml이고 다른 하나는 context-*.xml 파일이 그것인데, 이는 각각 Root Application Context, Servlet Context의 설정파일이다.


    두개의 차이점은 다음과 같다.

    Root Application Context

    - 최상단 컨텍스트 

    - 서로 다른 서블릿 컨텍스트에서 공유하는 bean을 등록 

    - 웹에서 사용되는 컨트롤러 등을 등록

    - 서블릿 컨텍스트에서 등록된 bean을 사용할 수 없으며, 서블릿 컨텍스트와 루트 컨텍스트에서 동일한 bean을 설정할 경우, 서블릿 컨텍스트에서 설정한 bean만 동작


    Servlet Context

    - 서블릿에서 이용되는 컨텍스트

    - 여기서 정의된 bean은 다른 서블릿 컨텍스트와 공유할 수 없음


    따라서 Controller와 관련된 bean은 action-servlet.xml에 설정하고, Service, DAO, Component등은 context-*에 설정하게 된다.

    SpringMVC 개발에서는 이렇게 설정하는것이 원칙이다. 


    우리가 설정한 AOP를 보면 Controller, Service, DAO의 3개 영역에서 모두 사용이 되어야 하는데, 한쪽의 컨텍스트에서만 설정하게 되면 다른 컨텍스트에서는 동작하지 않게 된다. 

    예를 들어 action-servlet.xml에만 설정을 하면 Controller의 로그만 출력될 것이고, context-aspect.xml에서만 설정하면 Service, DAO에서만 로그가 출력이 될 것이다.


    사실 이 글 전에 action-servlet.xml에서만 Component-scan을 했었는데, 이는 잘못된 방법이다. 

    그렇지만 이번에 AOP를 설정하면서 같이 이야기를 하기 위해서 놔뒀었다. 

    이번글에서 두가지 컨텍스트에 대해서 이야기를 하면서 왜 잘못되었고, 어떻게 해야하는지도 같이 살펴보게 되었다. 


    ------------------------------------------------------------------------------------


    이번글에서는 이론적인 내용이 굉장히 많았습니다. 


    AOP에 대해서 이야기를 하면서 Context에 대한 이야기도 조금 했는데요. 


    저도 처음에 이 부분에 대한 개념이 부족해서 몇날 몇일을 삽질했었던 기억이 있습니다. 


    원래 이번글에서 많은 분들이 원하셨던 페이징에 대해서 이야기를 하려고 했었는데, 너무 개발위주의 글이 되는것 같아서 


    스프링을 스프링답게 쓸 수 있는, 또 꼭 알아야할 이론적인 내용으로 작성해봤습니다.


    처음에는 일단 동작하는 소스를 기준으로 설명하는 것이 필요했기 때문에 기초적인 부분을 설명을 했는데, 


    이제는 이론적인 내용이 하나 둘 필요할것 같네요. 


    다음글은 페이징에 대해서 작성을 하겠습니다.


    first.zip


    댓글

Designed by Tistory.