ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Board - Interceptor, AOP, Transaction
    Projects/Problem & Solution 2021. 11. 6. 19:50

    Interceptor 적용

    인터셉터(Intterceptor)란?

    인터셉터는 컨트롤러의 URI에 접근하는 과정에서 무언가를 제어할 필요가 있을 때 사용된다. 예를 들어, 회원제로 이루어지는 시스템이 있다고 가정했을 때, 로그인이나 계정의 권한과 관련된 처리 등을 인터셉터를 이용해서 더욱 효율적으로 처리가 가능하다.

     

    1. LoggerInterceptor 클래스 생성
    @Slf4j
    public class LoggerInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.debug("");
            log.debug("==================== BEGIN ====================");
            log.debug("Request URI ===> " + request.getRequestURI());
    
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.debug("==================== END ======================");
            log.debug("");
        }
    }

    interceptor 패키지에 LoggerInterceptor 클래스를 추가하고, 위 코드를 작성한다.

    스프링에서 인터셉터는 HandlerInterceptor 인터페이스를 구현하는 방식으로 사용할 수 있다.

    해당 인터페이스는 preHandle(), postHandle(), afterCompletion(), afterConcurrentHandlingStarted() 총 네 개의 메서드를 포함하고 있으며, 이번 프로젝트에서는 preHandel과 postHandle 메서드만 사용된다.

    preHandle() 메서드는 컨트롤러의 메서드에 매핑된 특정 URI를 호출했을 때 컨트롤러에 접근하기 전에 실행되는 메서드이며, postHandle() 메서드는 컨트롤러를 경유한 후 화면으로 결과를 전달하기 전에 실행되는 메서드이다.

     

    2. 빈(Bean)으로 등록
    @Configuration
    public class MvcConfiguration implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoggerInterceptor())
                    .excludePathPatterns("/css/**", "/fonts/**", "/plugin/**", "/scripts/**");
        }
    }

    LoggerInterceptor 클래스를 빈으로 등록하기 위해 configuration 패키지에 MvcConfigutaion 클래스를 생성한다.

    해당 클래스는 WebMvcConfigurer 인터페이스를 상속받고, addInterceptors() 메서드를 오버라이딩 함으로써 인터셉터 관련 클래스를 빈으로 등록할 수 있다.

     

    3. 인터셉터 실행

     

    게시글 리스트 페이지로 이동한 후 콘솔 창을 확인해보면, LoggerInterceptor 클래스의 preHandle(), postHandle() 메서드에 작성한 로그가 출력되고 있음을 확인할 수 있다.

    이번 프로젝트에서는 로그인 처리를 구현하지 않았기 때문에, 특정 URI를 호출했을 때 로그를 출력하는 용도로 사용된다.


    AOP 설정

    AOP란?

    AOP는 관점 지향 프로그래밍이며, 자바와 같은 객체 지향 프로그래밍을 효율적으로 사용할 수 있도록 도와주는 역할을 한다.

    여러 개의 핵심 비즈니스 로직 외에 공통으로 처리되어야 하는 로그 출력, 보안 처리, 예외 처리와 같은 코드를 별도로 분리해서 하나의 단위로 묶는 모듈화의 개념으로 생각할 수 있다.

    AOP에서 관점은 핵심적인 관점과 부가적인 관점으로 나눌 수 있으며, 핵심적인 관점은 핵심 비즈니스 로직을 의미하고, 부가적인 관점은 공통으로 처리되어야 하는 코드를 의미한다.

     

     

    위 사진에서 각각의 화살표는 하나의 기능을 구현하기 위한 작업을 의미한다.

    필수적으로 처리되어야 하는 로깅, 트랜잭션과 같은 부가적인 기능들이 각각의 기능들에 하나씩 추가된다고 하면 코드가 길어지는 것뿐만 아니라, 후에 유지보수 또한 힘들게 될 것이다. AOP는 이러한 문제를 관점이라는 개념을 통해 해결할 수 있다.

     

     

    AOP에서는 부가적인 관점이 핵심 비즈니스 로직의 바깥에 포함되어 있다. AOP를 적용하면 로깅, 트랜잭션과 같은 부가적인 기능들을 핵심 비즈니스 로직에 일일이 추가하지 않아도 된다.

     

    1. LoggerAspect 클래스 생성
    @Component
    @Aspect
    public class LoggerAspect {
    
    	private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    	@Around("execution(* com.board..controller.*Controller.*(..)) or execution(* com.board..service.*Impl.*(..)) or execution(* com.board..mapper.*Mapper.*(..))")
    	public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
    
    		String type = "";
    		String name = joinPoint.getSignature().getDeclaringTypeName();
    
    		if (name.contains("Controller") == true) {
    			type = "Controller ===> ";
    
    		} else if (name.contains("Service") == true) {
    			type = "ServiceImpl ===> ";
    
    		} else if (name.contains("Mapper") == true) {
    			type = "Mapper ===> ";
    		}
    
    		logger.debug(type + name + "." + joinPoint.getSignature().getName() + "()");
    		return joinPoint.proceed();
    	}
    }

    aop 패키지 내에 LoggerAspect 클래스를 추가하고, 위 코드를 작성한다.

    "@Around" 애너테이션은 타겟 메서드의 호출 이전과 이후에 모두 적용이 가능하도록 설정하는 애너테이션이다.

    excution 구문을 통해서 AOP를 적용할 범위를 설정한다.

    위 코드는 특정 클래스의 어떤 메서드가 호출되는지 로그에 출력해 주는 부가적인 역할을 하는 클래스이다.

     

    2. AOP 실행

     

    게시글 리스트 페이지로 이동한 후 콘솔 창을 확인해보면, LoggerAspect 클래스에서 정의한 로직에 알맞은 로그가 출력되고 있음을 알 수 있다.


    Transaction 설정

    트랜잭션(Transaction)이란?

    트랜잭션은 쉽게 이야기해서 하나의 작업에 여러 개의 작업이 같이 묶여 있는 것으로 생각할 수 있다.

    하나의 작업으로 이루어진 여러 개의 작업 중 어느 하나를 실패한다면, 하나의 작업 안에 포함되어 있는 모든 작업들이 취소된다. 이렇게 함으로써 데이터의 무결성을 보장할 수 있다.

     

     트랜잭션의 기본 원칙

     

    특성 설명
    원자성(Atomicity) 하나의 트랜잭션 내에서 실행한 작업들은 하나로 간주한다.
    일관성(Consistency) 트랜잭션은 일관성있는 데이터베이스 상태를 유지한다.
    고립성(Isolation) 트랜잭션은 독립적으로 처리되어야 하며, 처리되는 중간에 외부의 간섭이 없어야 한다,
    지속성(Durability) 트랜잭션을 성공적으로 마치면 그 결과는 지속적으로 유지되어야 한다.

     

    1. 트랜잭션 매니저 등록
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    configuration 패키지의 DBConfiguration 클래스에 위 코드를 추가한다.

    추가된 메서드는 스프링에서 제공해주는 트랜잭션 매니저는 빈(Bean)으로 등록하기 위한 메서드이다.

     

    2. TransactionAspect 클래스 생성
    @Configuration
    public class TransactionAspect {
    
    	@Autowired
    	private PlatformTransactionManager transactionManager;
    
    	private static final String EXPRESSION = "execution(* com.board..service.*Impl.*(..))";
    
    	@Bean
    	public TransactionInterceptor transactionAdvice() {
    
    		List<RollbackRuleAttribute> rollbackRules = Collections.singletonList(new RollbackRuleAttribute(Exception.class));
    
    		RuleBasedTransactionAttribute transactionAttribute = new RuleBasedTransactionAttribute();
    		transactionAttribute.setRollbackRules(rollbackRules);
    		transactionAttribute.setName("*");
    
    		MatchAlwaysTransactionAttributeSource attributeSource = new MatchAlwaysTransactionAttributeSource();
    		attributeSource.setTransactionAttribute(transactionAttribute);
    
    		return new TransactionInterceptor(transactionManager, attributeSource);
    	}
    
    	@Bean
    	public Advisor transactionAdvisor() {
    
    		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    		pointcut.setExpression(EXPRESSION);
    
    		return new DefaultPointcutAdvisor(pointcut, transactionAdvice());
    	}
    
    }

    스프링에서 트랜잭션을 설정하는 방법은 XML 설정, 애너테이션 설정, AOP 설정으로 나눌 수 있으며, 이번 프로젝트에서는 AOP 설정을 사용한다.

    aop 패키지에 TransactionAspect 클래스를 추가하고, 위 코드를 작성한다. transactionManager는 DBConfiguration 클래스에서 빈(Bean)으로 등록한 PlatformTransactionManager 객체이다.

    EXPRESSION을 통해 AOP를 적용할 범위를 설정해준다. 위 코드 같은 경우 비즈니스 로직을 수행하는 ServiceImpl 클래스의 모든 메서드를 지정해주었다.

    RollbackRuleAttribute 생성자의 인자로 Exception 클래스를 지정함으로써, 어떠한 예외가 발생하면 무조건 롤백이 수행되도록 설정해주었다.


     

    GitHub - qlsdud0604/board: 스프링 부트를 이용한 게시판의 제작

    :clipboard: 스프링 부트를 이용한 게시판의 제작. Contribute to qlsdud0604/board development by creating an account on GitHub.

    github.com

     

    728x90

    댓글

Designed by Tistory.