Intellij Spring - AOP(Aspect Oriented Programming)

8 minute read

AOP (Aspect Oriented Programming)

  • AOP 란

    애스펙트란 그 자체로 애플리케이션의 핵심기능을 감고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기느에 부가되어 의미를 갖는 특별한 모듈을 가리킨다.
    애스펙트는 부가될 기능을 정의한 어디바이스와, 어드바이스를 어디에 적용할지를 결정하는 포인트컷을 함께 갖고 있다.
    AOP는 핵심기능 코드 사이에 침투한 부가기능을 독릭적인 모듈로 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것이라고 보면 된다.
    AOP는 결국 애플리케이션을 다양한 측면에서 독립적으로 모델링하고, 설계하고, 개발할 수 있도록 만들어 애플리케이션을 다양한 관점에서 바라보며 개발할 수 있게 도와준다.
  • AOP의 용어

    타깃 : 부가기능을 부여할 대상. 핵심기능을 담은 클래스 or 다른 부가기능을 제공하는 프록시 오브젝트.
    어드바이스 : 타깃에게 제공할 부가기능을 담은 모듈.
    조인 포인트 : 어드바이스가 적용될 수 있는 위치. 스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계뿐이다. 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인 포인트가 된다.
    포인트컷 : 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈을 말한다.
    프록시 : 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트. DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메소드 호출을 대신 받아서 타깃에 위임해주면면서, 그 과정에서 부가기능을 부여. 스프링은 프록시를 이용해 AOP를 지원.
    어드바이저 : 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트. 어떤 부가기능(어드바이스)을 어디(포인트컷)에 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈.
    애스펙트 : OOP의 클래스와 마찬가지로 애스펙트는 AOP의 기본 모듈. 한 개 또는 그 이상의 포인트컷과 어드비아스의 조합으로 만들어지며 보통 싱글톤 형태의 오브젝트로 존재.
  • AOP 적용

    UserService.java
      public class UserService {
              ...
    			
              private PlatformTransactionManager transactionManager;
    
              public void setTransactionManager(PlatformTransactionManager transactionManager) {
                      this.transactionManager = transactionManager;
              }
    
              public void upgradeLevels() {
                      TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
    
                      try {
                              List<User> users = userDao.getAll();
                              for (User user : users) {
                                      if (canUpgradeLevel(user))
                                              upgradeLevel(user);
                              }
    
                              this.transactionManager.commit(status);
                      } catch (RuntimeException e) {
                              this.transactionManager.rollback(status);
                              throw e;
                      }
    
              }
    			
              ...
      }
    
    비즈니스 로직을 담당하는 UserService 안에 트랜잭션을 담당하는 기술적인 코드가 있는것을 볼 수 있다.
    • DI를 이용한 데코레이터 패턴

      UserService.java
        public interface UserService {
                void upgradeLevels();
                void add(User user);
        }
      
      UserServiceImpl.java
        public class UserServiceImpl implements UserService {
                ...
      
                @Override
                public void upgradeLevels() {
                        List<User> users = userDao.getAll();
                        for (User user : users) {
                                if (canUpgradeLevel(user))
                                        upgradeLevel(user);
                        }
                }
      
                ...
        }
      
      UserServiceTx.java
        public class UserServiceTx implements UserService {
                private UserService userService;
                private PlatformTransactionManager transactionManager;
      
      
                public void setUserService(UserService userService) {
                        this.userService = userService;
                }
      
                public void setTransactionManager(PlatformTransactionManager transactionManager) {
                        this.transactionManager = transactionManager;
                }
      
                @Override
                public void upgradeLevels() {
                        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
      
                        try {
                                userService.upgradeLevels();
      
                                this.transactionManager.commit(status);
                        } catch (RuntimeException e) {
                                this.transactionManager.rollback(status);
                                throw e;
                        }
                }
      
                @Override
                public void add(User user) {
                        userService.add(user);
                }
        }
      
      데코레이터 패턴은 타깃에 부가적인 기능을 런타임 시 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴을 말한다.
      프록시는 타깃과 같은 메소드를 구현하고 있다가 메소드가 호출되면 타깃 오브젝트로 위임하는 위임 작업과, 지정된 요청에 대해 부가기능을 수행하는 부가 작업. 두 가지로 분류할 수 있다.
      UserService 인터페이스를 구현한 타깃인 UserServiceImpl에 트랜잭션 부가기능을 제공해주는 UserServiceTx를 추가함으로 데코레이터 패턴을 적용한 것 이라고 볼 수 있다.
      이러한 프록시에는 문제점이 있는데 첫 번째는 타깃의 인터페이스를 구현하고 위임하는 코드를 작성하기가 번거롭다는 점이다. 부가기능이 필요 없는 메소드도 구현해서 타깃으로 위임하는 코드를 일일이 만들어줘야 한다. 인터페이스의 메소드가 많아지고 다양해지면 부담스러운 작업이 될 것이다.
      두 번째 문제점은 부가기능 코드가 중복될 가능성이 많다는 점이다. 지금은 upgradeLevels() 메소드에만 트랜잭션 부가기능을 적용했지만 앞으로 트랜잭션 부가기능을 사용하는 메소드가 추가되면 매번 같은 코드가 중복으로 추가될 것이다.
    • 다이내믹 프록시

      TransactionHandler.java
        public class TransactionHandler implements InvocationHandler {
                private Object target;
                private PlatformTransactionManager transactionManager;
                private String pattern;
      
                public void setTarget(Object target) {
                        this.target = target;
                }
      
                public void setTransactionManager(PlatformTransactionManager transactionManager) {
                        this.transactionManager = transactionManager;
                }
      
                public void setPattern(String pattern) {
                        this.pattern = pattern;
                }
      
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName().startsWith(pattern))
                                return invokeInTransaction(method, args);
                        else
                                return method.invoke(target, args);
                }
      
                public Object invokeInTransaction(Method method, Object[] args) throws Throwable {
                        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
      
                        try {
                                Object ret = method.invoke(target,args);
                                this.transactionManager.commit(status);
                                return ret;
                        } catch (InvocationTargetException e) {
                                this.transactionManager.rollback(status);
                                throw e.getTargetException();
                        }
                }
        }		
      


        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao);
        userService.setMailSender(mailSender);
      
        TransactionHandler handler = new TransactionHandler();
        handler.setTarget(userService);
        handler.setTransactionManager(transactionManager);
        handler.setPattern("upgradeLevels");
        UserService txUserService = (UserService) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{UserService.class}, handler);
      
      다이내믹 프록시는 리플랙션 기능을 이용해서 프록시를 만들어 준다. 리플랙션은 자바의 코드 자체를 추상화해서 접근하도록 만든 것이다.
      TransactionHandler 를 보면 기존의 UserServiceTx 와 코드의 양도 비슷하고, 코드 작성은 더 어려워보이지만 다이내믹 프록시 방식이 직접 정의해서 만든 프록시보다 훨씬 유연하고 많은 장점이 있다.
      UserService 인터페이스의 메소드가 50개로 늘어나면 UserServiceTx는 매번 코드를 추가해줘야 한다. 하지만 TransactionHandler와 다이내믹 프록시를 생성해서 사용하는 코드는 전혀 수정이 필요 없다.
      하지만 다이내믹 프록시는 Proxy 클래스의 newProxyInstance() 라는 스태틱 팩토리 메소드를 통해서만 만들 수 있다. 그렇기 때문에 내부적으로 리플랙션 API를 이용해서 빈 정의에 나오는 클래스 이름을 가지고 빈 오브젝트를 생성하는 스프링에서는 DI를 통해 사용할 수가 없다.
    • 팩토리 빈을 이용한 다이내믹 프록시

      TxProxyFactoryBean.java
        public class TxProxyFactoryBean implements FactoryBean<Object> {
                Object target;
                PlatformTransactionManager transactionManager;
                String pattern;
                Class<?> serviceInterface;
      
                public void setTarget(Object target) {
                        this.target = target;
                }
      
                public void setTransactionManager(PlatformTransactionManager transactionManager) {
                        this.transactionManager = transactionManager;
                }
      
                public void setPattern(String pattern) {
                        this.pattern = pattern;
                }
      
                public void setServiceInterface(Class<?> serviceInterface) {
                        this.serviceInterface = serviceInterface;
                }
      
                @Override
                public Object getObject() throws Exception {
                        TransactionHandler txHandler = new TransactionHandler();
                        txHandler.setTransactionManager(transactionManager);
                        txHandler.setTarget(target);
                        txHandler.setPattern(pattern);
      
                        return Proxy.newProxyInstance(
                                        getClass().getClassLoader(),
                                        new Class[]{serviceInterface},
                                        txHandler
                        );
                }
      
                @Override
                public Class<?> getObjectType() {
                        return serviceInterface;
                }
      
                @Override
                public boolean isSingleton() {
                        return false;
                }
        }		
      
      applicationContext.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:util="http://www.springframework.org/schema/util"
                     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
                ...
      
                <bean id="userService" class="ex.spring.user.service.TxProxyFactoryBean">
                        <property name="target" ref="userServiceImpl"/>
                        <property name="transactionManager" ref="transactionManager"/>
                        <property name="pattern" value="upgradeLevels"/>
                        <property name="serviceInterface" value="ex.spring.user.service.UserService"/>
                </bean>
      
                ...
        </beans>
      
      프록시 팩토리 빈 방식은 앞서 데코레이터 패턴의 두 가지 문제를 해결해준다. 첫 번째 문제인 타깃 인터페이스를 구현할 때 마다 클래스를 일일이 만드는 번거로움을 다이내믹 프록시를 이용하면 해결 된다. 두 번째로 같은 부가기능을 가진 메소드가 증가할때 중복된 부가기능 코드가 추가되는 문제점은 핸들러 메소드를 구현해 수많은 메소드에 부가기능을 부여해줄 수 있으니 부가기능 코드의 중복 문제도 사라진다.
      또한, 다이내믹 프록시에 팩토리 빈을 이용한 DI까지 사용할 수 있게되므로 번거로운 생성 코드도 제거할 수 있고, DI 설정만으로 다양한 타깃 오브젝트에 적용도 가능하다.
      지금까지는 메소드 단위로 하나의 클래스 안에 존재하는 여러 개의 메소드에 부가기능을 한 번에 제공하는 방식이었다. 하지만 클래스 단위로 여러개의 클래스에 공통적인 부가기능을 제공하거나, 하나의 타깃에 여러 개의 부가기능을 적용하려고 하면 여러가지 문제들이 발생한다.
    • ProxyFactoryBean

      TransactionAdvice.java
        public class TransactionAdvice implements MethodInterceptor {
                private PlatformTransactionManager transactionManager;
      
                public void setTransactionManager(PlatformTransactionManager transactionManager) {
                        this.transactionManager = transactionManager;
                }
      
                @Override
                public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
      
                        try {
                                Object ret = methodInvocation.proceed();
                                this.transactionManager.commit(status);
                                return ret;
                        } catch (RuntimeException e) {
                                this.transactionManager.rollback(status);
                                throw e;
                        }
                }
        }		
      
      applicationContext.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:util="http://www.springframework.org/schema/util"
                     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
                ...
      				
                <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
                        <property name="target" ref="userServiceImpl"/>
                        <property name="interceptorNames">
                                <list>
                                        <value>transactionAdvisor</value>
                                </list>
                        </property>
                </bean>
      
                <bean id="transactionAdvice" class="ex.spring.user.service.TransactionAdvice">
                        <property name="transactionManager" ref="transactionManager"/>
                </bean>
      
                <bean id="transactionPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
                        <property name="mappedName" value="upgrade*"/>
                </bean>
      
                <bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
                        <property name="advice" ref="transactionAdvice"/>
                        <property name="pointcut" ref="transactionPointcut"/>
                </bean>
      
                ...
        </beans>		
      
      ProxyFactoryBean은 스프링의 DI와 템플릿/콜백 패턴, 서비스 추상화 등의 기법이 모두 적용된 것이다. 그렇기에 독립적이어서 여러 프록시가 공유할 수 있는 어드바이스와 포인트컷으로 확장 기능을 분리할 수 있다.
      ProxyFactoryBean까지 사용함으로 타깃 코드는 깔금한 상태에서 부가기능의 재활용이 가능하면서, 타깃의 적용 메소드를 선정하는 방식도 독립적으로 작성할 수 있도록 분리될 수있다. 하지만 부가기능이 적용될 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정정보를 추가해야 되는 문제는 여전히 남아있다. target 프로퍼티를 제외한 동일한 빈 클래스의 종류, 어드바이스, 포인트컷 설정을 매번 복사해서 붙여넣어줘야 한다.
    • DefaultAdvisorAutoProxyCreator

      applicationContext.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"
                     xsi:schemaLocation="http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans.xsd">
                ...
      				
                <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      				
      				
                <bean id="userService" class="ex.spring.user.service.UserServiceImpl">
                        <property name="mailSender" ref="mailSender"/>
                        <property name="userDao" ref="userDao"/>
                </bean>
      				
                <bean id="transactionAdvice" class="ex.spring.user.service.TransactionAdvice">
                        <property name="transactionManager" ref="transactionManager"/>
                </bean>
      
                <bean id="transactionPointcut" class="ex.spring.proxy.NameMatchClassMethodPointcut">
                        <property name="mappedClassName" value="*ServiceImpl"/>
                        <property name="mappedName" value="upgrade*"/>
                </bean>
      				
                <bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
                        <property name="advice" ref="transactionAdvice"/>
                        <property name="pointcut" ref="transactionPointcut"/>
                </bean>
      
                ...
        </beans>		
      
      DefaultAdvisorAutoProxyCreator는 BeanPostProcessor 인터페이스를 구현해서 만드는 스프링이 제공하는 빈 후처리기 중의 하나이다. 이 빈 후처리기 자체를 빈으로 등록만 하면 빈 오브젝트가 생성될 때 마다 빈 후처리기에 보내서 후처리 작업을 요청한다.
      빈 후처리기가 등록되어 있으면 스프링은 빈 오브젝트를 만들 때마다 후 처리기에게 빈을 보낸다. DefaultAdvisorAutoProxyCreator은 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상이면 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결해준다. 그 후 빈 후처리기가 원래의 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려주고 그 오브젝트가 빈으로 등록된다.
      transactionPointcut은 메소드 뿐만 아니라 클래스까지 필터를 적용시켰다.
    • 포인트컷 표현식과 AOP 네임스페이스 적용

      applicationContext.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 
                     https://www.springframework.org/schema/aop/spring-aop.xsd">
                ...
      				
                <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      				
      			
                <bean id="userService" class="ex.spring.user.service.UserServiceImpl">
                        <property name="mailSender" ref="mailSender"/>
                        <property name="userDao" ref="userDao"/>
                </bean>
      
                <bean id="transactionAdvice" class="ex.spring.user.service.TransactionAdvice">
                        <property name="transactionManager" ref="transactionManager"/>
                </bean>
      
      
                <aop:config>
                        <aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.upgrade*(..))"/>
                        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
                </aop:config>
      
                <!-- 포인트컷을 내장한 어드바이저 태그-->
                <!--    <aop:config>-->
                <!--        <aop:advisor advice-ref="transactionAdvice" pointcut="execution(* *..*ServiceImpl.upgrade*(..))"/>-->
                <!--    </aop:config>-->
      
                ...
        </beans>		
      
      아래의 aop 네임스페이스 선언을 설정파일에 추가해줘야 한다.
        <beans ...
                     xmlns:aop="http://www.springframework.org/schema/aop" 
                     xsi:schemaLocation= "... 
                                    http://www.springframework.org/schema/aop 
                                    https://www.springframework.org/schema/aop/spring-aop.xsd">
                     ...
        </beans>
      
    • TransactionInterceptor

      applicationContext.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 https://www.springframework.org/schema/aop/spring-aop.xsd">
                ...
      				
                <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      
      
                <bean id="userService" class="ex.spring.user.service.UserServiceImpl">
                        <property name="mailSender" ref="mailSender"/>
                        <property name="userDao" ref="userDao"/>
                </bean>
      
                <bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
                        <property name="transactionManager" ref="transactionManager"/>
                        <property name="transactionAttributes">
                                <props>
                                        <prop key="get*">PROPAGATION_REQUIRED,readOnly,timeout_30</prop>
                                        <prop key="upgrade*">PROPAGATION_REQUIRES_NEW,ISOLATION_SERIALIZABLE</prop>
                                        <prop key="**">PROPAGATION_REQUIRED</prop>
                                </props>
                        </property>
                </bean>
      
                <aop:config>
                        <aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.upgrade*(..))"/>
                        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
                </aop:config>
      
                ...
        </beans>		
      
      트랜잭션 경계 설정 코드를 보면 트랜잭션을 가져올때 파라미터로 new DefaultTransactionDefinition를 넘겨주게 된다. DefaultTransactionDefinition이 구현하고 있는 TransactionDefinition 인터페이스는 트랜잭션의 동작방식에 영향을 줄 수 있는 네 가지 속성을 정의하고 있다.
      트랜잭션 전파 : 트랜잭션의 경계에서 이미 진행중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식.
      PROPAGATION_REQUIRED : 진행 중인 트랜잭션이 없으면 새로 시작하고, 이미 시작된 트랜잭션이 있으면 이에 참여한다. DefaultTransactionDefinition의 트랜잭션 전파 속성.
      PROPAGATION_REQUIRES_NEW : 항상 새로운 트랜잭션을 시작한다.
      PROPAGATION_NOT_SUPPORTED : 트랜잭션 없이 동작하도록 한다. 진행 중인 트랜잭션이 있어도 무시한다.
      
      격리 수준 : 서버환경에서는 여러개의 트랜잭션이 동시에 진행될 수 있기 때문에 모든 DB 트랜잭션은 격리수준을 갖고 있어야 한다. DefaultTransactionDefinition에 설정된 격리수준은 ISOLATION_DEFAULT로 DataSource에 설정되어 있는 디폴트 격리수준을 그대로 따른다는 뜻이다.
      제한시간 : 트랜잭션을 수행하는 제한시간을 설정할 수 있다. DefaultTransactionDefinition의 기본 설정은 제한시간이 없는 것이다.
      읽기전용 : 읽기전용 설정시 트랜잭션 내에서 데이터를 조작하는 시도를 막아준다. 데이터 엑세스 기술에 따라서 성능이 향상될 수도 있다.
      스프링에서는 편리하게 트랜잭션 경계설정 어드바이스로 사용할 수 있도록 만들어진 TransactionInterceptor가 존재한다. 이를 이용하면 기존의 TransactionAdvice와 TransactionHandler처럼 새로운 클래스를 만들지 않아도 설정파일을 이용해 쉽게 빈으로 만들수가 있다.
    • tx 네임스페이스 적용

      applicationContext.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"
                     xmlns:tx="http://www.springframework.org/schema/tx"
                     xsi:schemaLocation="http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans.xsd 
                     http://www.springframework.org/schema/aop 
                     https://www.springframework.org/schema/aop/spring-aop.xsd 
                     http://www.springframework.org/schema/tx 
                     http://www.springframework.org/schema/tx/spring-tx.xsd">
                ...
      
                <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      				
      
                <bean id="userService" class="ex.spring.user.service.UserServiceImpl">
                        <property name="mailSender" ref="mailSender"/>
                        <property name="userDao" ref="userDao"/>
                </bean>
      
                <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
                        <tx:attributes>
                                <tx:method name="get*" propagation="REQUIRED" read-only="true" timeout="30"/>
                                <tx:method name="upgrade*" propagation="REQUIRES_NEW" isolation="SERIALIZABLE"/>
                                <tx:method name="*" propagation="REQUIRED"/>
                        </tx:attributes>
                </tx:advice>
      
                <aop:config>
                        <aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.upgrade*(..))"/>
                        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
                </aop:config>
      
                ...
        </beans>		
      
      아래의 tx 네임스페이스 선언을 설정파일에 추가한다.
        <beans ...
                     xmlns:tx="http://www.springframework.org/schema/tx"
                     xsi:schemaLocation="...
                             http://www.springframework.org/schema/tx 
                             http://www.springframework.org/schema/tx/spring-tx.xsd">
                     ...
        </beans>			 
      
      tx 네임스페이스를 이용하면 XML 에디터의 자동완성 기능을 통해 편하게 작성할 수 있다.
    • 트랜잭션 애노테이션

      UserService.java
        @Transactional
        public interface UserService {
                void add(User user);
                void deleteAll();
                void update(User user);
                void upgradeLevels();
                @Transactional(readOnly = true)
                User get(String id);
      
                @Transactional(readOnly = true)
                List<User> getAll();
        }
      
      applicationContext.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:tx="http://www.springframework.org/schema/tx"
                     xsi:schemaLocation="http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans.xsd   
                     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
                ...
      				
                <tx:annotation-driven transaction-manager="transactionManager"/>
      
                <bean id="userService" class="ex.spring.user.service.UserServiceImpl">
                        <property name="mailSender" ref="mailSender"/>
                        <property name="userDao" ref="userDao"/>
                </bean>
      
                ...
        </beans>		
      
      <tx:annotation-driven transaction-manager="transactionManager"/>를 설정파일에 추가한다.
      스프링은 @Transactional을 적용할때 4단계의 대체 정책을 이용하게 해준다. 메소드 속성을 확일할 때 타깃 메소드, 타깃 클래스, 선언 메소드, 선언 타입의 순서에 따라서 @Transactional이 적용 되었는지 차례로 확인하고, 가장 먼저 발견되는 속성정보를 사용한다.
토비의 스프링 3.1

Categories:

Updated:

Leave a comment