
복습
프록시 패턴이 어려우면 디자인 패턴을 공부해볼 것 (GoF)
구상 클래스(Concrete Class)
런타임 때 위빙 → 실행이 됐을 때
Enhancer
CGLIB에서 제공하는 클래스로 프록시 객체를 동적으로 생성할 때 사용.
Reflection 방식 → 클래스 파일로 된 것을 꺼내다가 사용
@Test
@DisplayName("언어차원(JDK, Reflection)에서 제공하는 프록시 패턴 테스트")
void T1() throws Exception {
MockServiceImpl targetObject = new MockServiceImpl();
SubLogicInvocationHandler handler = new SubLogicInvocationHandler(targetObject);
MockService proxy = (MockService) Proxy.newProxyInstance(
MockService.class.getClassLoader()
, new Class[]{MockService.class}
, handler
);
proxy.logic1();
proxy.logic2();
}
MockService - 인터페이스
MockServiceImple - 구현 클래스
첫번째 인자 MockService.class.getClassLoader() → 클래스 로더가 필요
두번째 인자 new Class[]{MockService.class} → 구현할 인터페이스 목록(목록인 이유는 다중 구현일 수도 있어서)
실제로 AOP를 사용할 때 MethodInterceptor를 사용
advice
ProxyFactory → 프록시 객체를 생성해줌 (SpringBoot에서는 어노테이션으로 쉽게 생성해줌)
new 키워드를 사용해서 직접 객체 생성
상속받은 객체는 CGLIB가 동적으로 만들어주기 때문에 우리는 몰라도 됨
@Test
@DisplayName("CGLIB 적용!")
void test1() throws Exception {
UserService userService = new UserService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new UserServiceInterceptor(userService));
UserService proxy = (UserService) enhancer.create();
proxy.createUser();
}
❗️SpringAOP의 문제❗️
Advice 종류
| @Before | 대상 메서드 실행 전에 Advice 실행 |
|---|---|
| @After | 대상 메서드 실행 후에 Advice 실행 |
| @AfterReturning | 정상적으로 실행 완료 후 Advice 실행 |
| @AfterThrowing | 예외 발생 시 Advice 실행 |
| @Around | 대상 메서드 실행 전/후 모두 Advice 실행 |
Aspect 적용 코드
@Slf4j
@Aspect //내부적으로 프록시를 만들어서 적용시켜줌
@Component
public class LoggingAspect {
//AopService 의 logic()
//메서드를 구분하기 위해 패키지를 지정해주면 됨
@Around("execution(public void io.shs0160.greppaop.spring_proxy.app.AopService.logic())")
//point Cut을 표현식으로 지정 -> execution(접근제어자 반환타입 [선언타입]메서드이름(파라미터) [예외])
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//around -> 전 후 다 적용
//join point 를 실행되는 시점으로 잡을 수 있음(언제)
log.info("[LoggingAspect] 횡단 관심사 로깅 시작!");
Object result = joinPoint.proceed();
log.info("[LoggingAspect] 횡단 관심사 로깅 종료!");
//point cut 으로 어디에 적용시킬건지 결정
return result;
}
}
💡Point Cut 표현식
- 어떤 메서드에 Advice를 적용할지 결정하는 표현식
execution(접근제어자 리턴타입 패키지.클래스.메서드(매개변수))
| * | 모든 값 (와일드카드) |
|---|---|
| .. | 하위 패키지, 여러 개의 인자를 받음 |
| + | 해당 클래스와 모든 자식 클래스 포함 (상속 포함) |
Pointcut 표현식
| execution( com.example.service..*(..)) | com.example.service 패키지의 모든 클래스, 모든 메서드에 적용 |
|---|---|
| execution( com.example.service.MyService.(..)) | MyService 클래스의 모든 메서드에 적용 |
| execution( com.example.service.MyService.get(..)) | MyService 클래스의 get으로 시작하는 모든 메서드에 적용 |
| execution(public * *(..)) | 모든 public 메서드에 적용 |
| execution(* *(..)) | 모든 메서드에 적용 |
| execution( com.example...*(..)) | com.example 패키지와 그 하위 패키지 전체에 적용 |
| execution( com.example...*(String, ..)) | 첫 번째 인자가 String인 모든 메서드에 적용 |
특정 조건을 추가한 표현식
| within(com.example.service..*) | com.example.service 패키지 내부의 모든 클래스에 적용 |
|---|---|
| within(com.example.service.MyService) | 특정 클래스(MyService) 내부의 메서드에 적용 |
| this(com.example.service.MyService) | MyService의 인스턴스(프록시 포함)에 적용 |
| target(com.example.service.MyService) | MyService의 원본 객체에 적용 |
| args(java.lang.String) | 인자가 String 타입인 메서드에 적용 |
| @within(org.springframework.stereotype.Service) | @Service 애노테이션이 붙은 클래스 내부의 메서드에 적용 |
| @annotation(org.springframework.transaction.annotation.Transactional) | @Transactional이 붙은 메서드에 적용 |
Test 코드
Test 클래스 만드는 방법 → Shift + Cmd + T
//통합 테스트
@Slf4j
@SpringBootTest
public class AopServiceTests {
//TDD -> 테스트 주도 개발
//DDD -> 도메인 주도 개발
@Autowired
AopService aopService;
@Autowired
NonAopService nonAopService;
@Test
@DisplayName("주입 여부 확인")
void 주입_확인() throws Exception {
//log.info("AopService = {}", aopService);
aopService.logic();
log.info("============");
//point cut 때문에 non은 적용이 안돼있는걸 볼 수 있음
nonAopService.logic1();
}
}
결과 확인

Controller로 테스트
@Controller
@RequiredArgsConstructor
public class AopController {
private final AopService aopService;
@ResponseBody
@GetMapping("/aop")
public String aop() {
aopService.logic();
return "OK";
}
}
결과 확인

요청을 받고 처리
종속성
@Controller → Controller 역할을 하는 Bean 생성 / ServletContext에 올라감
@RequestMapping → path와 method를 기재해 줘야함
@GetMapping
→ 동작원리
@RequestMapping 이 들어있음@PostMapping 도 똑같다@ResponseBody
view가 없어서 직접 html을 보낼 수 있음
직렬화 → 객체를 통신 가능한 단위로 바꾸는 것
AOP는 김영한 강의 들을 때 SpringBoot로 써본적이 있었는데, 이번에 Spring으로 직접 만들어 사용해보니까 생각했던거 보다 훨씬 어려웠다.. 그래도 이제 더 이상 이렇게 깊게 공부할 일 없을테니까 괜찮겠지ㅜ
(AOP 안녕,,~ 한동안은 보지 말자)와~! 데브코스를 시작한 지 벌써 한 달이 지났다.
처음에는 너무 시간이 안 가서 5개월을 어떻게 버티지 막막했는데 나름 적응된 것 같아 다행이다.
솔직히 초반에는 배우는 내용이 나한테 어렵기도 하고, 뭔가 나보다 전부 앞서 나가있는 것 같다는 느낌이 들어서 우울한 생각을 많이 하기도 했다.😢 앞으로 5개월이나 남았는데 계속 이런 식이면 어떡하지? 하는 걱정도 했고, ‘내가 이 길을 가는 게 맞나’하는 의구심도 들었다. 물론 그 고민은 여전히 ing이다…ㅎ그래도 이제는 최대한 공부에만 집중해 보려고 노력하는 중이다. 부트캠프를 하면서 정말 열심히 하는 사람들의 모습에 자극을 받기도 했고, 일단 지금 내가 놓여진 이 상황에 최선을 다 해야 나중에 덜 후회할거 같아서.. 물론 이제 Spring을 하기 시작하니까 나름 재미있어서 그런 것도 사실이다..ㅎ
아직 4개월 이라는 긴 시간이 남았지만 후회없게 (제발..)최선을 다 해보자! 화이팅~