관점 지향 프로그래밍 AOP (Aspect Oriented Programming)
소스 코드의 여기저기에 흩어져있는 횡단 관심사를 중심으로 설계와 구현을 하는 프로그래밍 기법
횡단 관심사의 분리를 실현하는 방법
(어떤 메서드가 실행되고 파라미터 타입이 어떤 지 로그를 띄울 수 있음)
- 횡단 관심사(Cross-Cutting Concern)
핵심 비즈니스 로직과 다소 거리가 있지만, 여러 모듈에서 공통적이고 반복적인 처리를 요구하는 내용 - 횡단 관심사의 분리(Separation Of Cross_Cutting Concerns)
애플리케이션 개발할 때 횡단 관심사에 해당하는 부분을 분리해서 한 곳으로 모으는 것을 의미
AOP 사용 예
- 로깅
- 보안 적용
- 트랜잭션 관리
- 예외 처리
AOP 관련 용어
- 애스팩트(Aspect)
AOP의 단위가 되는 횡단 관심사에 해당한다 - 조인포인트(Join Point)
횡단 관심사가 실행될 지점이나 시점(메서드 실행이나 예외 발생 등)을 말한다 - 어드바이스(Advice)
특정 조인 포인트에서 실행되는 코드로, 횡단 관심사를 실제로 구현해서 처리하는 부분- Before : 조인포인트(createPost()) 전에 실행. (삼겹살을 굽기 직전에)
- After : 조인 포인트(createPost())에서 처리가 완료된 후 실행(삽겹살을 굽고 먹은 직후 실행)
- Around : 조인 포인트(createPost()) 전후에 실행(삽겹살을 굽기 직전과 먹은 직후 실행)
- After Returning : 조인 포인트(createPost())가 정상적으로 종료 후 실행
- After Throwing : 조인 포인트(createPost())에서 예외 발생 시 실행. 예외가 발생안되면 실행 안함
- 포인트 컷(Pointcut)
수많은 조인 포인트 중에서 실제로 어드바이스를 적용할 곳을 선별하기 위한 표현식(expression) - 위빙(Weaving)
애플리케이션 코드의 적절한 지점에 애스펙트를 적용 - 타깃(Target)
AOP 처리에 의해 처리 흐름에 변화가 생긴 객체를 말함
csrf처리
스프링 시큐리티에서 submit할 때
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
- <form 태그 사용시>
<sec:csrfInput />
</form> - ajax사용시
beforeSend:function(xhr){
xhr.setRequestHeader("${_csrf.headerName}","${_csrf.token}");
},
success앞에 꼭쓰기 - 파일 업로드 시
"action", "/item/updatePost?${_csrf.parameterName}=${_csrf.token}"
"enctype" , "multupart/form-data" - 만약 어쩔수없이csrf 비활성 처리가 필요하다면
security-context.xml에서 <security:csrf disabled="true" /> 추가하기
예제 - 로그
pom.xml
<!-- AOP(Aspect Oriented Programming : 관점 지향 프로그래밍) 시작
1) aspectjrt => 이미 있으므로 생략
2) aspectjweaver => 없으므로 의존 관계를 정의
-->
<!-- https://mvnrepository.com/artifact/aspectj/aspectjweaver
1.5.4 -->
<!-- AspectJ RunTime -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- AspectJ Weaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- AspectJ Tools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- AOP(Aspect Oriented Programming : 관점 지향 프로그래밍) 끝 -->
root-context.xml
<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:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
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-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 스프링 AOP 활성화 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- kr.or.ddit.aop 패키지를 컴포넌트 스캔 대상으로 등록 -->
<context:component-scan base-package="kr.or.ddit.aop"></context:component-scan>
더보기
<?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:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
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-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<!--
root-context.xml : 스프링 설정 파일
서블릿과 필터가 공유할 수 있는 루트 스프링 컨테이너 설정으로,
공통 빈(Service, Repository(DAO), DB, Log 등)을 설정함.
공통빈을 설정하는 곳으로 주로 View 지원을 제외한 bean을 설정함
스프링 설정?
view와 관련되지 않은 객체를 정의
Service(기능), DAO(Repository : 저장소), DB등 비즈니스 로직과 관련된 설정
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName() = "oracle.jdbc.driver.OracleDriver";
-->
<!-- dataSource : 데이터베이스와 관련된 정보를 설정 -->
<!--
db : database(개념. 공유/저장/통합/운영). RDB(Relational DB.관계형DB)
dbms : database management system(DB관리시스템.오라클)
localhost=127.0.0.1=내ip주소
xe : express(OracleXE11g.r2) => SID(sequence ID)
-->
<bean id="uploadFolder" class="java.lang.String">
<constructor-arg value="C:\\Users\\PC-13\\git\\repository4\\springProj\\src\\main\\webapp\\resources\\upload"></constructor-arg>
</bean>
<bean id="uploadFolderDirect" class="java.lang.String">
<constructor-arg value="C:\\eGovFrameDev-3.10.0-64bit\\workspace\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp0\\wtpwebapps\\springProj\\resources\\upload"></constructor-arg>
</bean>
<!--
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
-->
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver" />
<property name="url"
value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="jsptest" />
<property name="password" value="java" />
</bean>
<!-- 데이터베이스와 연결을 맺고 끊어질 때까지의
라이프 사이클을 관리해주는 sqlSession 객체를 생성
1) dataSource
2) 매퍼 xml의 위치 지정. / : src/main/resources/
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
-->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations"
value="classpath:/sqlmap/**/*_SQL.xml" />
<property name="configLocation"
value="/WEB-INF/mybatisAlias/mybatisAlias.xml" />
</bean>
<!-- 데이터베이스에 개별적으로 쿼리를 실행시키는 객체.
이 객체를 통해 query를 실행함
-->
<bean id="sqlSessionTemplate"
class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!-- 파일업로드 설정
CommonsMultipartResolver multipartResolver = new multipartResolver();
multipartResolver.setMaxUploadSize(10485760);
multipartResolver.setDefaultEncoding("UTF-8");
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 파일업로드 용량 (10MB)-->
<property name="maxUploadSize" value="10485760"/>
<property name="defaultEncoding" value="UTF-8" />
</bean>
<!-- 파일업로드 디렉토리 설정 -->
<bean id="uploadPath" class="java.lang.String">
<constructor-arg value="c:\\upload"/>
</bean>
<!-- Mapper 인터페이스 설정
개발자가 직접 DAO를 설정하지 않아도
자동으로 Mapper 인터페이스를 활용하는 객체를 생성하게 됨
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="kr.or.ddit.**.mapper" />
</bean>
<!-- 트랜잭션 관리자의 빈을 정의 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 애너테이션 기반의 트랜잭션 제어를 활성화함 -->
<tx:annotation-driven/>
<!-- 스프링 AOP 활성화 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- kr.or.ddit.aop 패키지를 컴포넌트 스캔 대상으로 등록 -->
<context:component-scan base-package="kr.or.ddit.aop"></context:component-scan>
</beans>
ServiceLoggerAdvice.java
root-context.xml에서 지정한 component-scan에 지정한 위치의 패키지 kr.or.ddit.aop 안에 있는 클래스
지시자 | 임의의 1개의 리턴타입 | 패키지 밑의 각각의 패키지 | 그 하위에 모든 파일/패키지 | ||
@Before("execution | (* | kr.or.ddit. | *..* | (..) | )") |
package kr.or.ddit.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/*
Aspect(애스팩트) : AOP(Aspect Oriented Programming)의 단위가 되는 횡단 관심사
- 횡단 관심사(Cross-Cutting Concern) : 핵심(core) 비즈니스 로직(삼겹살구어먹기, 빵또아의 아이스크림)과
다소 거리가 있지만,
여러 모듈에서 공통적이고 반복적인 처리를 요구하는 내용(불판닦기, 불판교체, 빵또아의 빵)
- 횡단 관심사 분리(Separation Of Cross-Cutting Concern) : 횡단 관심사에 해당하는
부분(불판닦기, 불판교체, 빵또아의 빵)을 분리해서 한 곳으로 모으는 것을 의미
- Component : 골뱅이Aspect와 짝궁. component-scan시 "여기 봐주세요"라는 의미
- JoinPoint : 어드바이스가 적용될 수 있는 위치
- Advice(로그 출력 한다) : 어떤 부가기능(불판닦기)을 언제(삼겹살을 굽기 전(Before)에) 사용할지 정의
* 언제?
- Before : 조인포인트(createPost()) 전에 실행. (삼겹살을 굽기 직전에)
- After : 조인 포인트(createPost())에서 처리가 완료된 후 실행(삽겹살을 굽고 먹은 직후 실행)
- Around : 조인 포인트(createPost()) 전후에 실행(삽겹살을 굽기 직전과 먹은 직후 실행)
- After Returning : 조인 포인트(createPost())가 정상적으로 종료 후 실행
- After Throwing : 조인 포인트(createPost())에서 예외 발생 시 실행. 예외가 발생안되면 실행 안함
*/
@Component
@Aspect
@Slf4j
public class ServiceLoggerAdvice {
//로보트에 : AOP대상(로그, 보안, 트랜잭션, 에러)
//포인트컷 표현식. *..*(..)
//excution : 포인트컷(대상(메소드)을 선별하는 것) 지정자
//(* : 임의의 1개의 리턴타입
//.. : 임의의 0개 이상
//kr.or.ddit.*..*(..) :
// 패키지 밑의 각각의 패키지가 있고
// 그 하위에 모든 파일/패키지
// 각각의 메소드가 있고
// (..) : 모든 파라미터
//결론 : 포인트컷에 포함된 메서드를 대상으로 그 메서드가 실행되기 전에 로그를 출력해보자
//Before어드바이스 : 조인 포인트 전에 실행됨. 예외가 발생하는 경우만 제외하고 항상 실행됨
// execution : 지시자
@Before("execution(* kr.or.ddit.*..*(..))")
public void startLog(JoinPoint jp) {
log.info("--- startLog ---");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardService.register(BoardVO)
log.info("startLog : " + jp.getSignature());
//.getArgs() : 전달 된 파라미터 정보를 보여줌
// [BoardVO [boardNo=127,title=개똥이]]
log.info("startLog : " + Arrays.toString(jp.getArgs()));
}
//AfterReturning 어드바이스
// 조인 포인트가 정상적으로 종료한 후에 실행됨. 예외 발생 시 실행 안됨
@AfterReturning("execution(* kr.or.ddit.*..*(..))")
public void logReturning(JoinPoint jp) {
log.info("--- logReturning ---");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌.
//파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardService.register(BoardVO)
log.info("logReturning : " + jp.getSignature());
}
//After Throwing 어드바이스
// 조인 포인트에서 예외 발생 시 실행. 예외가 발생 안 되면 실행 안 됨
@AfterThrowing(pointcut="execution(* kr.or.ddit.*..*(..))", throwing="e")
public void logException(JoinPoint jp, Exception e) {
log.info("--- logException ---");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌.
//파라미터 타입은 무엇인지 보여줌
log.info("logException : " + jp.getSignature());
//예외 메시지를 보여줌
log.info("logException : " + e);
}
//After어드바이스
// 조인 포인트 완료 후 실행. 예외 발생이 되더라도 항상 실행 됨
@After("execution(* kr.or.ddit.*..*(..))")
public void endLog(JoinPoint jp) {
log.info("endLog");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardService.register(BoardVO)
log.info("endLog : " + jp.getSignature());
//.getArgs() : 전달 된 파라미터 정보를 보여줌
// [BoardVO [boardNo=127,title=개똥이]]
log.info("endLog : " + Arrays.toString(jp.getArgs()));
}
//ProceedingJoinPoint : around 어드바이스에서 사용함
// 횡단관심사 - 포인트컷 대상 core메소드 - 횡단관심사
// 스프링프레임워크가 컨트롤 하고 있는 비즈니스로직 호출을 가로챔. 책임이 around 어드바이스로 전가됨
// 그래서 비즈니스 메소드에 대한 정보를 around 어드바이스 메소드가 가지고 있어야 하고
// 그 정보를 스프링 컨테이너가 around 어드바이스 메소드로 넘겨주면
// ProceedingJoingPoint 객체로 받아서 around 어드바이스가 컨트롤 시 활용함
@Around("execution(* kr.or.ddit.*..*(..))")
public Object timeLog(ProceedingJoinPoint pjp) throws Throwable{
//메소드 실행 직전 시간 체킹
long startTime = System.currentTimeMillis();
log.info("pjpStart : " + Arrays.toString(pjp.getArgs()));
//메소드 실행
Object result = pjp.proceed();
//메소드 실행 직후 시간 체킹
long endTime = System.currentTimeMillis();
log.info("pjpEnd : " + Arrays.toString(pjp.getArgs()));
//직후 시간 - 직전 시간 => 메소드 실행 시간
log.info(pjp.getSignature().getName() + " : " + (endTime - startTime));
return result;
}
}
'Spring' 카테고리의 다른 글
[Spring] 예제 (0) | 2024.08.19 |
---|---|
[Spring] 예제 - 환경 설정 (0) | 2024.08.19 |
[Spring] 오류 페이지 (0) | 2024.08.13 |
[Spring] 페이지 별 권한 주기 @PreAuthorize (0) | 2024.08.13 |
[Spring] passwordEncoder, 로그인/로그아웃/자동로그인 (0) | 2024.08.12 |