ㅁ AOP
- 지금까지 작성한 컨트롤러, 서비스, dao의 코드는 이 프로그램이 제대로 작동하기 위해 필수로 필요한 핵심 로직(비즈니스 로직)이다.
- 그런데 핵심로직 내에서 매번 공통적으로 처리해야 하는 로직(공통 로직)이 있을 수 있다.
대표적인게 로깅 처리와 트랜잭션 처리다.
- 컨트롤러의 각 메소드에서 요청시 전달값을 받고 있다.
요청시 전달값, 조회된 결과값을 로그로 확인해보는 것이 좋다.
- 각각의 url 요청 때마다 요청시 전달값, 어떤 클래스의 어떤 메소드가 동작하는지 등의 정보를 알기 위해 로그를 출력한다면 컨트롤러의 모든 메소드에 로그 출력문을 일일이 작성해야 한다.
- 그러지 않고 url 요청 때마다 컨트롤러 측의 메소드가 동작될 때 무조건 로그가 출력되게끔 공통 로직을 핵심 로직과 분리해서 작성해두고, 내가 원하는 시점에 반복 적용시킬 수 있는 기술이 AOP다.
- 핵심 로직은 최대한 건들지 않고 공통 로직만 따로 작성한 다음에, 내가 원하는 시점에 작동되게 주입할 수 있다.
- AOP 기술을 안 써도 됨. 핵심 로직가서 일일이 다 쓰기만 하면 된다.
근데 불편하다. 개발이 끝나고 공통 로직을 수정할 때도 일일이 다 수정해야 함.
ㅁ AOP 사용
- AOP 개념이 어렵고 세팅할 것도 많아서 안쓰는 곳도 있다.
- 스프링을 사용한다면 트랜잭션 관리를 위한 AOP 설정을 기본적으로 많이 해두고 쓴다.
ㅁ 트랜잭션 처리
- 원래 트랜잭션 관리는 개발자가 직접 해야되는게 개발원칙중에 하나다.
- 그런데 스프링에서는 한 기능에 하나의 dml문 같은경우 AOP 설정을 안해도
sqlsession이 자동으로 close 되면서 commit 까지 자동으로 진행된다.
- 한 기능에 여러 dml문일 때는 개발자가 직접 핸들링을 해야되고 이를 편하게 하기 위해 AOP 기술을 이용한다.
ㅁ AOP
- Aspect Oriented Programming. 관점 지향 프로그래밍
- 핵심 로직(비즈니스 로직)마다 매번 반복해서 공통적으로 처리해야하는 로직(로깅, 트랜잭션 등)을 한번만 정의해두고 내가 원하는 시점에 반복 적용시키는 프로그래밍 방식.
- 핵심 로직과 공통 로직을 분리해서 개발할 수 있다.
- 개발이 다 끝난 후에 핵심 로직을 건들지 않고도 공통로직만을 따로 작성해서 원하는 시점에 실행시킬 수 있다.
ㅁ AOP 관련 용어
(1) JoinPoint
- AOP를 적용시킬 대상의 전체 메소드
- Controller 또는 ServiceImpl의 모든 메소드
(2) PointCut
- AOP를 적용시킬 특정 메소드(핵심로직)
- Controller 또는 ServiceImpl의 모든 메소드 중 로깅 또는 트랜잭션이 필요한 메소드
(3) Advice
- 공통로직(공통적으로 처리해야하는 구문)이다.
- AOP 동작하는 부분이다. AOP 동작 자체다.
- ex) 로깅, 트랜잭션 처리
- Advice(공통로직)을 어떤 핵심 로직에 반영시킬건지 그 대상을 지정해줘야 한다.
그때 JoinPoint와 PointCut이 나온다.
- 중요한건 PointCut과 Advice다.
PointCut이 핵심로직이고 Advice가 공통 로직이다.
- 공통 로직을 어떤 핵심 로직에 반영시킬건지, PointCut 실행 전 후 전과 후 모두 언제 Advice를 수행할지 순서 정하기.
- 예를들어 서비스에는 삽입, 수정, 삭제, 조회 메소드가 있지만 트랜잭션이 필요한 메소드는 사입, 수정, 삭제 메소드 뿐이다.
ㅁ Advice 동작 시점
(1) @Before : PointCut(핵심로직) 동작 이전
(2) @After : PointCut(핵심로직) 동작 이후
(3) @Around : PointCut(핵심로직) 동작 이전과 이후
(4) @AfterReturning : PointCut(핵심로직) 정상 완료 이후
(5) @AfterThrowing : PointCut(핵심로직) 예외 발생 이후
ㅁ Advice 동작 순서
(1) Before Advice : Advice -> PointCut
(2) After Advice : PointCut -> Advice
(3) Around Advice : Advice -> PointCut -> Advice
ㅁ Pointcut을 결정하는 표현식 작성법
(1) 형식
- execution (반환타입 패키지.클래스.메소드(매개변수))
- 클래스와 메소드에도 *을 쓸 수 있다. 모든을 의미한다.
(2) 반환타입
- * : 모든 반환타입
- void : void 타입
- !void : void 제외한 타입
(3) 매개변수
- * : 1개의 모든 매개변수
- .. : 모든 매개변수
ㅁ 로직 : 하나의 기능을 처리하기 위한 코드 묶음.
===================================================================================
ㅁ 이전 마이바티스 프로젝트 계속 씀.
ㅁ pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion>
<groupId>com.br</groupId>
<artifactId>sbatis</artifactId>
<name>04_Spring_MyBatis_AOP</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>11</java-version>
<org.springframework-version>5.3.27</org.springframework-version>
<org.aspectj-version>1.9.19</org.aspectj-version>
<org.slf4j-version>2.0.7</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ (AOP를 위한 라이브러리)-->
<dependency> <!-- 원래 있던 것. 이게 있어야 "Advice 동작 시점"의 어노테이션들을 사용할 수 있다. -->
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- 위빙 : Advice(공통로직)를 PointCut(핵심로직)에 로딩되도록 함 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
<scope>runtime</scope>
</dependency>
<!-- Logging (logback) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
<!-- <scope>test</scope> 이건 지워야 함 -->
</dependency>
<!-- (2) slf4j -->
<dependency> <!-- 기존 dependency 중 이것만 남기고 다 지운다.-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<!-- (3) log4jdbc -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Lombok 라이브러리 추가 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!-- Jackson 라이브러리 추가 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<!-- db관련 라이브러리 -->
<!-- (1) ojdbc8 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>23.2.0.0</version>
</dependency>
<!-- (2) spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- (3) mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.14</version>
</dependency>
<!-- (4) mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- (5) commons-dbcp -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
- AspectJ가 AOP 관련 라이브러리다.
- AspectJ 주석이 붙은 dependency 태그가 이미 있다.
- 홈피에서 가져와도 되는데 귀찮아서 어차피 버전도 같은걸 쓸거라 이미 있던 dependency 태그를 복붙한다.
2번째 AspectJ의 dependency 태그의 artifactId를 바꾸고 scope를 추가하면 홈피에 있는거랑 동일하게 된다.
ㅁ 패키지와 클래스 생성
- com.br.sbatis.aop.BeforeLoggingAdvice
- com.br.sbatis.config.SpringBeanConfig
ㅁ BeforeLoggingAdvice
package com.br.sbatis.aop;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BeforeLoggingAdvice { // controller측 모든 메소드(PointCut)가 동작되기 전에 로그 출력되게끔(Advice)
@Pointcut("execution (* com.br.sbatis.controller.*Controller.*(..))") /* 포인트컷 표현식 */
public void setPointCut() {} /* 포인트컷 서명 */
// Before Advice 메소드 작성법 (즉, Pointcut 메소드 전에 실행시키고자 하는 메소드)
// 1. 반환타입은 void로 해야 함.
// 2. 메소드명은 마음대로.
// 3. 매개변수 : JoingPoint 타입 객체 (생략 가능, 핵심 로직 정보를 알아내고자 할 경우 작성)
// "setPointCut()"이라는 메소드가 실행되기 "전(@Before)"에 "loggingBeforeAdvice()"를 실행한다는 뜻.
@Before("setPointCut()")
public void loggingBeforeAdvice() {
log.info("loggingBeforeAdvice 메소드 동작 ***************");
}
}
- *Controller를 작성하면 ~Controller로 끝나는 모든 클래스가 다 선택된다.
- *(..)로 모든 메소드를 선택했다. 매개변수는 ..이 모든 것을 뜻한다.
- setPointCut() 메소드를 만들었다. 메소드 명은 아무거나 상관없다.
- 포인트컷 서명은 포인트컷 표현식을 지칭하는 이름이다.
- BeforeLoggingAdvice에서는 포인트컷 표현식과 포인트컷 서명을 분리해서 작성하고 있다.
- Logger 객체가 필요하다. 클래스 상단에 @Slf4j 어노테이션을 작성하면 내가 Logger 객체를 생성할 필요 없이 log.메소드()를 사용할 수 있다.
- PointCut과 Advice를 만들었다고 끝이 아니고 빈으로 등록해야 한다.
클래스 상단에 @Aspect 어노테이션을 작성한다. 이게 AOP를 등록하는 구문이다.
다음과 같은 포인트컷에 어던 어드바이스를 작동시킬건지, 실행 시점은 언젠지 ~ 클래스를 ~ @Aspect 어노테이션
- 그리고 이 클래스도 빈으로 등록해야 한다.
내가 만든 클래스여서 상단에 어노테이션을 붙여서 간단하게 빈등록할 수 있지만, 스프링 부트 프로젝트할 때는 xml 파일이 다 사라지고 자바 방식으로 빈을 등록해야해서 자바 방식으로 자바 파일만들어서 빈을 등록해본다.
- @Aspect는 빈등록이 아니다. aop ~
ㅁ SpringBeanConfig
package com.br.sbatis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.br.sbatis.aop.BeforeLoggingAdvice;
@EnableAspectJAutoProxy
@Configuration
public class SpringBeanConfig {
@Bean
public BeforeLoggingAdvice beforeLoggingAdvice() {
return new BeforeLoggingAdvice();
}
}
- 클래스 상단에 @Configuration ~
- @EnableAspectJAutoProxy ~
ㅁ NoticeController를 보면,
package com.br.sbatis.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.sbatis.dto.NoticeDto;
import com.br.sbatis.service.NoticeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequestMapping("/notice")
@RequiredArgsConstructor
@Controller
public class NoticeController {
private final NoticeService noticeService;
@GetMapping("/list.do") // /notice/list.do
public void noticeList(Model model) {
List<NoticeDto> list = noticeService.selectNoticeList();
log.debug("list: {}", list);
model.addAttribute("list", list);
// return "notice/list"; void여도 포워딩하려 한다. url mapping("/notice/list")를 가지고 포워딩한다.
}
@GetMapping("/detail.do") // /notice/detail.do
public void noticeDetail(int no, Model model) {
NoticeDto n = noticeService.selectNoticeByNo(no);
model.addAttribute("n",n);
}
@GetMapping("/enroll.do") // /notice/enroll.do
public void noticeEnroll() {}
// 이렇게만 작성해도 페이지 이동이 된다.
@PostMapping("/insert.do")
public String noticeInsert(NoticeDto n) {
int result = noticeService.insertNotice(n);
if(result > 0) { // 성공시 다시 목록페이지
return "redirect:/notice/list.do";
}else { // 실패시 메인페이지
return "redirect:/";
}
}
@GetMapping("/modify.do") // /notice/modify.do
public void noticeModify(int no, Model model) {
model.addAttribute("n", noticeService.selectNoticeByNo(no));
}
@PostMapping("/update.do")
public String noticeUpdate(NoticeDto n) {
int result = noticeService.updateNotice(n);
if(result > 0){ // 성공시 상세페이지
return "redirect:/notice/detail.do?no=" + n.getNo();
}else { // 실패시 목록페이지
return "redirect:/notice/list.do";
}
}
@PostMapping("/delete.do")
public String noticeDelete(String[] deleteNo) {
// String[] arr = request.getParameterValues("deleteNo") <- request 객체가 있었다면
// 체크박스 선택 안 한 경우 예외 처리
if (deleteNo == null || deleteNo.length == 0) {
// 선택 항목이 없을 경우 에러 메시지와 함께 목록 페이지로 이동
return "redirect:/notice/list.do?error=noSelection";
}
// 실행할 sql문 : delete from notice where no in (xx, xx, xx) <- 동적 쿼리
int result = noticeService.deleteNotice(deleteNo);
if(result == deleteNo.length){ // 성공시 목록페이지
return "redirect:/notice/list.do";
}else { // 실패시 메인페이지
return "redirect:/";
}
}
}
- noticeList 메소드에는 이미 로그 출력문이 있다. 비교해본다.
- 메인페이지요청시, 그리고 목록페이지 요청시 loggingBeforeAdvice 메소드 동작 ************** 로그가 찍힌다.
- DEBUG는 NoticeController의 noticeList 메소드의 로그 출력문에서 출력되는 로그다.
ㅁ 뭔가 잘 안될 땐 메이븐 - 프로젝트 업데이트.
================================================================================
ㅁ logback.xml 수정
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender class="ch.qos.logback.core.ConsoleAppender" name="consoleLog">
<encoder>
<pattern>%-5level: [%date{yyyy-MM-dd HH:mm:ss}] [%logger:%line] - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="INFO" />
<logger name="com.br.sbatis" level="DEBUG" />
<logger name="jdbc.sqlonly" level="off" /> <!-- 쿼리문만 -->
<logger name="jdbc.sqltiming" level="off" /> <!-- 쿼리문 + 실행시간 -->
<logger name="jdbc.audit" level="off" /> <!-- JDBC 호출 정보 -->
<logger name="jdbc.resultset" level="off" /> <!-- ResultSet 호출 정보 -->
<logger name="jdbc.resultsettable" level="off" /> <!-- ResultSet 결과 (조회결과 테이블) -->
<logger name="jdbc.conntection" level="off" /> <!-- Connection 호출 정보 -->
<root level="WARN">
<appender-ref ref="consoleLog" />
</root>
</configuration>
- 쿼리 로그 헷갈려서 sqliming을 off했다.
ㅁ BeforeLoggingAdvice 수정
package com.br.sbatis.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import lombok.extern.slf4j.Slf4j;
@Aspect
@Slf4j
public class BeforeLoggingAdvice { // controller측 모든 메소드(PointCut)가 동작되기 전에 로그 출력되게끔(Advice)
@Pointcut("execution (* com.br.sbatis.controller.*Controller.*(..))") /* 포인트컷 표현식 */
public void setPointCut() {} /* 포인트컷 서명 */
// Before Advice 메소드 작성법 (즉, Pointcut 메소드 전에 실행시키고자 하는 메소드)
// 1. 반환타입은 void로 해야 함.
// 2. 메소드명은 마음대로.
// 3. 매개변수 : JoingPoint 타입 객체 (생략 가능, 핵심 로직 정보를 알아내고자 할 경우 작성)
// "setPointCut()"이라는 메소드가 실행되기 "전(@Before)"에 "loggingBeforeAdvice()"를 실행한다는 뜻.
@Before("setPointCut()")
public void loggingBeforeAdvice(JoinPoint joinPoint) {
// log.info("loggingBeforeAdvice 메소드 동작 ***************");
Object[] args = joinPoint.getArgs(); // 핵심로직이 실행될 때 요청시 전달값 (배열로 반환하는 메소드가 getArgs다.)
for(Object arg : args) {
log.info("arg: {}", arg);
}
}
}
- 모든 컨트롤러의 메소드 실행 전에 로그가 출력되게 되어있었다.
- loggingBeforeAdvice 메소드에 JoinPoint 타입의 매개변수를 추가한다.
- 기존 로그 출력문은 잘 작동하는지 확인하기 위한 것었다. 주석처리한다.
- 메인페이지 - 목록페이지 - 2번 게시글 상세 페이지 - 2번 게시글 수정 페이지 - 수정하고 다시 2번 게시글 상세 페이지
ㅁ
- NoticeController에 가보면 메소드 옆에 표시가 되어 있다.
================================================================
ㅁ BeforeLoggingAdvice 수정
package com.br.sbatis.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import lombok.extern.slf4j.Slf4j;
@Aspect
@Slf4j
public class BeforeLoggingAdvice { // controller측 모든 메소드(PointCut)가 동작되기 전에 로그 출력되게끔(Advice)
@Pointcut("execution (* com.br.sbatis.controller.*Controller.*(..))") /* 포인트컷 표현식 */
public void setPointCut() {} /* 포인트컷 서명 */
// Before Advice 메소드 작성법 (즉, Pointcut 메소드 전에 실행시키고자 하는 메소드)
// 1. 반환타입은 void로 해야 함.
// 2. 메소드명은 마음대로.
// 3. 매개변수 : JoingPoint 타입 객체 (생략 가능, 핵심 로직 정보를 알아내고자 할 경우 작성)
// "setPointCut()"이라는 메소드가 실행되기 "전(@Before)"에 "loggingBeforeAdvice()"를 실행한다는 뜻.
@Before("setPointCut()")
public void loggingBeforeAdvice(JoinPoint joinPoint) {
// log.info("loggingBeforeAdvice 메소드 동작 ***************");
Object[] args = joinPoint.getArgs(); // 핵심로직이 실행될 때 요청시 전달값 (배열로 반환하는 메소드가 getArgs다.)
for(Object arg : args) {
// log.info("arg: {}", arg);
}
// 현재 실행되는 핵심로직 메소드 정보
Signature signature = joinPoint.getSignature();
log.info("method: {}", signature.getName()); // 메소드명만
log.info("short: {}", signature.toShortString()); // 클래스명 + 메소드명
log.info("long: {}", signature.toLongString()); // 반환형 + 클래스명 + 메소드명
}
}
- org.aspectj.lang.Signature를 임포트한다.
- 요청시 전달값 출력하는 로그("log.info("arg: {}", arg)")는 헷갈리니 주석처리 했다.
- 메인페이지 - 목록페이지 - 2번 게시글 상세 페이지 - 2번 게시글 수정 페이지 - 수정하고 다시 2번 게시글 상세 페이지
- 컨트롤러 수행 시점 이전에 실행되었다.
===================================================================================
ㅁ 이번엔 before에서 after 로 바꿔본다.
ㅁ before 했던거는 끈다.
- BeforeLoggingAdvice 클래스 상단의 @Aspect를 주석처리만 하면 된다. 등록을 아예 없앴다.
ㅁ After Advice 메소드 작성법
- 반환타입 : void
- 메소드명 : 마음대로
- 매개변수 : JoinPoint (생략 가능)
ㅁ com.br.sbatis.aop 패키지에 AfterLoggingAdvice를 생성한다.
package com.br.sbatis.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Aspect
public class AfterLoggingAdvice {
@After("execution(* com.br.sbatis.controller.*Controller.*(..))")
public void afterLoggingAdvice() {
log.info("afterLoggingAdvice 메소드 동작 ************************");
}
}
- before 때는 포인트컷 표현식과 포인트컷 서명을 따로 작성했었다.
ㅁ SpringBeanConfig 수정
package com.br.sbatis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.br.sbatis.aop.AfterLoggingAdvice;
import com.br.sbatis.aop.BeforeLoggingAdvice;
@EnableAspectJAutoProxy
@Configuration
public class SpringBeanConfig {
@Bean
public BeforeLoggingAdvice beforeLoggingAdvice() {
return new BeforeLoggingAdvice();
}
@Bean
public AfterLoggingAdvice afterLoggingAdvice() {
return new AfterLoggingAdvice();
}
}
- 메인페이지 - 목록페이지.
- 첫번재 줄은 메인페이지 요청시 출력되는 로그문이다.
- sql문은 지금 껐다.
- NoticeController의 noticeList 메소드 실행 다음에 실행되었다.
ㅁ 폴더 구조