실제 개발시 AOP 는 XML 설정을 더 좋아함 왜일까?
어노테이션으로 수정하려면 다시 컴파일해야되기 때문에. . .
어차피 뭐로 할 지 회사가면 PM이 정해준다고(?)한다.
그러니 편한 어노테이션 설정뿐만 아니라 앞전에 실습했던 XML 설정도 계속해서 익혀두자
@Annotation
Spring 시리즈 AOP(1) 을 참고하면, 각 어노테이션의 동작 방식을 자세하게 알 수 있다.
@Pointcut
// @Pointcut( 룰 설정 ( 적용시킬 주소 ) )
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
@Before
// cut()이 실행되는 지점의 @Before 에서 해당 메소드(before()) 를 실행
@Before("cut()")
public void before(JoinPoint joinPoint) { . . . } // JoinPoint 지점 정보
@After
@AfterReturing
// cut()이라는 지점의 반환 값
@AfterReturning(value = "cut()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj) { . . . } // JoinPoint 지점 정보, Object 반환 값
@AfterThrowing
@Around
@Aspect
// AOP 사용 + Component 로 Spring 에서 관리
@Aspect
@Component
public class ParameterAop {
// @Pointcut( 룰 설정 ( 적용시킬 주소 ) )
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
// cut()이 실행되는 지점의 @Before 에서 해당 메소드(before()) 를 실행
@Before("cut()")
public void before(JoinPoint joinPoint) { // JoinPoint 지점 정보
// method 이름(get,post) JointPoint 에서 가져오기
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println(method.getName());
// mehtod 에 담긴 정보 가져오기
Object[] args = joinPoint.getArgs(); // method 의 매개변수의 배열
for(Object obj : args) {
System.out.println("type : " + obj.getClass().getSimpleName()); // method 타입
System.out.println("value : " + obj); // method 값
}
}
// cut()이라는 지점의 반환 값
@AfterReturning(value = "cut()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj) { // JoinPoint 지점 정보, Object 반환 값
System.out.println("return obj");
System.out.println(returnObj);
}
}
@Aspect
, @Component
@Pointcut
룰을 설정하는 어노테이션
적용시킬 주소를 속성으로 넣음
연산자의 종류가 매우 다양함
cut 지점을 설정하는 주소가 들어감
cut 지점의 실행 전은 @Before
, 실행 후는 @After
어노테이션으로 설정
JointPoint
MethodSignature
joinPoint.getArgs()
메서드에 담겨 있는 정보를 가져옴@AfterReturning
Object returnObj
RestApiController.java
전 실습에서 했던 것과 유사
return 값으로 정보를 반환
GET 방식인 경우 → id 는 100, name 은 yeppi
POST 방식인 경우 → User{key=’value’, . . .} 즉 User 객체의 전체를 반환
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
**return id + " " + name;** // input 들어올 때, 값 찍히면 return
}
@PostMapping("/post")
public User post(@RequestBody User user) {
**return user;** // 값 반환할 때, 값 찍히면 return
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
. . .
}
cut()
과, enableTimer()
둘 다 사용하여 실행 시간을 구하면 됨StopWatch
를 사용하여 프로세스 실행 전과 후의 start, stop을 끊어줌joinPoint.process()
TimerAop.java
@Aspect
@Component
public class TimerAop { // Timer 동작 AOP
// controller 하위의 method 에 **제약 걸기**
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
// annotation 패키지 하위의 Timer method 는 **로깅하기** -> 실행 시간이 필요함
@Pointcut("@annotation(com.example.aop.annotation.Timer)")
private void enableTimer(){}
// **Before, After Method 는 Timer 를 공유할 수 없음**
// cut() 과 enableTimer() 를 같이 사용
@Around("cut() && enableTimer()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
// 실행 전
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// joinPoint.proceed() -> 실질적인 method 실행
Object result = joinPoint.proceed();
// 실행 후
stopWatch.stop();
// 총 걸린 시간 (초단위)
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
}
}
RestApiController.java
@RestController
@RequestMapping("/api")
public class RestApiController {
// 서비스 로직에만 집중하도록 작성
// 반복되는 기능(현. StopWatch)들은 AOP로 처리
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
return id + " " + name; // input 들어올 때, 값 찍히면 return
}
@PostMapping("/post")
public User post(@RequestBody User user) {
return user; // 값 반환할 때, 값 찍히면 return
}
// 직접 만든 annotation @Timer
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db logic
**Thread.sleep**(1000 * 2); // 2초 후에 종료
}
}
@Target
, @Retention
으로 decode 어노테이션을 직접 만들기@Pointcut
, @Before
, @AfterReturning
를 사용하여 AOP 를 생성.
.
.
@Decode
@PutMapping("/put")
public User put(@RequestBody User user) {
System.out.println("put");
System.out.println(user);
return user; // 값 반환할 때, 값 찍히면 return
}
// annotation 직접 만들기
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {}
@Aspect
@Component
public class DecodeAop {
// controller 하위의 method 에 제약 걸기
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
// annotation 패키지 하위의 Timer method 는 로깅하기 -> 실행 시간이 필요함
@Pointcut("@annotation(com.example.aop.annotation.Decode)")
private void enabledecode(){}
// Before 는 Encode
@Before("cut() && enabledecode()")
public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
Object[] args = joinPoint.getArgs(); // method 의 파라미터(args) 값 중에 원하는 값 뽑기
for (Object arg : args) {
if(arg instanceof User) { // 그 중 '원하는 객체 User' 를 찾았을 때만 값 변경
User user = User.class.cast(arg); // User 클래스로 형변환
String base64Email = user.getEmail(); // 기존 encoding 되어있던 email 꺼내기
String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8"); // base64Email 반환형 byte -> new String 으로 decoding
user.setEmail(email);
}
}
}
// After 는 Decode
@AfterReturning(value = "cut() && enabledecode()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj) {
if(returnObj instanceof User) {
User user = User.class.cast(returnObj); // decoding
String email = user.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes()); // encoding
user.setEmail(base64Email);
}
}
}
@SpringBootApplication
public class AopApplication {
public static void main(String[] args) {
SpringApplication.run(AopApplication.class, args);
// 코드 추가
System.out.println(Base64.getEncoder().encodeToString("yeppi@gmail.com".getBytes()));
}
}
이체() 메서드 { }
→ 다른 은행에 출금 + 입금XML 로 진행한다
<!-- Transaction 설정 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<tx>
가 자동으로 만들어줌<tx:method name="*" rollback-for="Exception"/>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
</aop:config>
insertBoard
를 클라이언트가 호출하면txmanager
를 이용하여 rollback👉 트랜잭션 설정은 AOP 설정을 내부적으로 사용한다