1. 학습일지
- 2022년 2월 21일 부터 WebSecurityConfigureAdapter는 Deprecated 처리가 되었다.
- 그 이유에 대한 키워드는 Lambda DSL, IO, Webflux 등이었는데 아직은 학습하기 어려운 부분이었다. 따라서 추후에 공부할 키워드로 !
- WebSecurityConfig에서는 SecurityFilterChain을 @Bean으로 등록하여 lambda를 사용하며 .and()를 제거했다.
- 이 부분은 어떠한 장점을 지니기에, WebSecurityConfigureAdapter를 권장하지 않는다는 공식 발표를 했는 지 꼭 공부해보자!
2) TOP 5 회원 선정하기
서버 사용시간 TOP 5 회원을 선정하기.
- API 사용 시간을 측정한다.
- Scratch 파일 생성하여 테스트해보기.
- 상위 단의 File-new-scratch file-java 선택
- 간단하게 sumFromOneTo()를 정의하여 시작 시점과 끝 시점 사이에서 돌리고 between 값을 출력한다.
class Scratch {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
long output = sumFromOneTo(1_000_000_000);
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
System.out.println("소요시간: " + runTime);
}
private static long sumFromOneTo(long input) {
long output = 0;
for (int i = 1; i < input; ++i) {
output = output + i;
}
return output;
}
}

- 상품 등록 API에 위의 방식과 같이 시작, 끝 시점을 정해주고 between 시간을 구해 저장한다.
- try, finally를 사용하여 return 후에 finally 구문이 실행되도록 해준다.
- 이 때 누적합이 되도록 조건문을 추가해준다.
@PostMapping("/api/products")
public Product createProduct(@RequestBody ProductRequestDto requestDto,
@AuthenticationPrincipal UserDetailsImpl userDetails){
long startTime = System.currentTimeMillis();
try {
Long userId = userDetails.getUser().getId();
Product product = productService.createProduct(requestDto, userId);
return product;
} finally {
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
System.out.println("소요시간: " + runTime);
Users user = userDetails.getUser();
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(user).orElse(null);
if(apiUseTime == null){
apiUseTime = new ApiUseTime(user, runTime);
} else{
apiUseTime.addUseTime(runTime);
}
System.out.println("[API User Time Username: " + user.getUsername() + ", Total Time: " + apiUseTime.getTotalTime());
apiUseTimeRepository.save(apiUseTime);
}
}
- 관리자 권한으로 이를 조회할 수 있도록 한다.
- 이 때 ApiUseTime을 그대로 response에 담는다면, 포함된 User의 정보가 모두 전달된다.
- 따라서 responseDto를 따로 생성하여 전달할 정보만을 담아 보내도록 한다.
2) AOP 란?
- 위와 같이 TOP5 회원 선정 기능이 추가된 것을 회원들이 알아야 할까?

핵심기능
- 각 API 별 수행해야 할 비즈니스 로직
- ex) 상품 키워드 검색, 관심 상품 등록, 회원가입, 관심상품 폴더에 추가 ...
부가기능
- 핵심 기능을 보조하는 기능
- ex) 회원 패턴 분석을 위한 로그 기록, API 수행 시간 저장...
만약 위에서 했던 것과 같이 모든 핵심 기능에 부가 기능을 덧붙인다면?...
- 핵심 기능이 많다면?..
- 핵심 기능이 나중에 추가된다면?..
- 항상 부가기능을 추가해줘야 한다.. 만약 깜빡한다면 부가기능의 신뢰성을 잃게된다.
- 핵심 기능 수정 시 부가 기능이 섞여있다면 두 부분을 온전히 분리하기 위해 모든 코드를 이해해야한다.
- 부가 기능의 변경 및 삭제가 필요하다면 모든 핵심기능 마다 변경사항을 적용해야한다. (유지보수성 최악)
부가 기능을 모듈화 하자!
- AOP(Aspect Oriented Programming)를 통해 부가기능을 모듈화한다.
- 부가기능은 핵심기능과는 관점(Aspect), 관심이 다르다.
- 따라서 부가기능을 핵심기능과 분리하여
부가기능 중심
으로 설계 및 구현이 가능하다.
Spring이 제공하는 AOP

- 어드바이스 = 부가기능
- 포인트 = 부가기능 적용 위치

- 프록시 객체가 생성되고 이는 곧 부가기능의 역할을 수행한다.


- AOP 후 과정
- DispatcherServlet이 요청을 받아서 Controller로 보내는 것은 동일하다
- 그러나 Controller로 가기 전에 AOP Proxy가 이 요청을 가로챈다.
- (정확히는 핵심 기능이 DI 될 때 프록시 객체를 중간에 삽입한다.)
- 가로 챈 후 @Around가 붙은 메서드가 실행 된다.
- 메서드 내 ProceedingJoinPoint.proceed()가 실행될 때 Controller로 해당 요청을 보내준다.
- 이 후 컨트롤러가 Response를 보내면 다시 AOP Proxy가 이를 받아와 ProceedingJoinPoint.proceed() 이후의 기능을 수행한다.
- proceed()에 의해 원래 호출하려고 했던 함수, 인자가 전달된다.
- (이 때 AOP Proxy는 Object output = joinPoint.proceed();와 같이 Java의 모든 객체를 받을 수 있는 Object 타입으로 결과를 받아오고 이를 리턴한다.)
- 메서드가 종료되면 이를 다시 Dispatcher Servlet으로 보낸다.
+ 스프링 AOP 어노테이션
- @Aspect: 스프링 빈 (Bean) 클래스에만 적용 가능하며, 해당 클래스를 AOP로 사용하겠다는 뜻이다.
- 어드바이스(Advice) 종류
- @Around: 핵심기능 수행 전과 후 모두 (@Before + @After)
- @Before: 핵심기능 호출 전에만 (ex. Client 의 입력값 Validation 수행)
- @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
- @AfterReturning: '핵심기능' 호출 성공 시에만 (함수의 Return 값 사용 가능)
- @AfterThrowing: '핵심기능' 호출 실패 시에만. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)
- 포인트 컷(PointCut)
execution(modifiers-pattern? `return-type-pattern` declaring-type-pattern? `method-name-pattern(param-pattern)` throws-pattern?)
@Around("execution(public * com.spring.springcore.controller..*(..))")
- modifiers-pattern(접근제어자): public, private, *.....
- return-type-pattern(반환 타입): void, String, List<//String>, *....
- declaring-type-pattern(패키지 포함한 클래스명): com.sparta.springcore.controller.*(..) ->
.별
은 해당 패키지 모두, ..
은 하위패키지까지 모두
- method-name-pattern(param-pattern) (함수명(파라미터 패턴)):
- 함수명: 그대로 쓰거나, xxx*과 같이 해당 문자열을 포함한 모든 함수를 지정할 수도 있음 혹은
..
으로 모든 함수에 사용 가능
- 파라미터 패턴: 패키지 포함하여 인수 명 그대로 쓰거나 / () 인수없음 / (*) 타입 관계없이 1개 / (..) 타입 관계없이 0~N개
- @PointCut: 메서드 위에 정의하여 범위를 지정하고 재사용, 결합사용 가능하다.
@Component
@Aspect
public class Aspect {
@Pointcut("execution(* com.sparta.springcore.controller.*.*(..))")
private void forAllController() {}
@Pointcut("execution(String com.sparta.springcore.controller.*.*())")
private void forAllViewController() {}
@Around("forAllContorller() && !forAllViewController")
public void saveRestApiLog() {
...
}
@Around("forAllContorller()")
public void saveAllApiLog() {
...
}
}
Controller - Service - Repository 3계층에 맞춰 구현을 해야하는 다른 이유
- Controller에 비즈니스 로직을 추가한다면 AOP가 있을 경우 훨씬 수월하다. (부가기능이 분리되어있기 때문)
- Service영역에 AOP를 적용시켰는데, Controller에서 Repository를 바로 호출한다면? 부가기능이 적용되지 않는 메서드가 생긴다.
3) 트랜잭션 (@Transactional)
트랜잭션이란?
- 데이터베이스에서 데이터에 대한 하나의 논리적 실행단계
- ACID (원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어
- 쉽게 말해, DB 관련 최소 작업 단위라고 생각하면 된다.
- @Transactional이 붙은 메서드에서
- 모두 정상적인 값을 배출하면 Commit!
- 하나라도 오류를 뱉어내면 Rollback! -> 이 때 이미 DB에 변동 사항이 생겼더라도 모두 롤백 처리된다.
- 아래 코드는 트랜잭션 어노테이션의 실제 구현 코드이다.
public List<Folder> addFolders(List<String> folderNames, User user) {
TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<Folder> existFolderList = folderRepository.findAllByUserAndNameIn(user, folderNames);
List<Folder> savedFolderList = new ArrayList<>();
for (String folderName : folderNames) {
if (isExistFolderName(folderName, existFolderList)) {
throw new IllegalArgumentException("중복된 폴더명을 제거해 주세요! 폴더명: " + folderName);
} else {
Folder folder = new Folder(folderName, user);
folder = folderRepository.save(folder);
savedFolderList.add(folder);
}
}
transactionManager.commit(status);
return savedFolderList;
} catch (Exception ex) {
transactionManager.rollback(status);
throw ex;
}
}

- 어디서 많이 본 것 같은데?...(바로 위에서 배움ㅋ)
➕ 사실 트랜잭션은 AOP
이다. 😲
- 위에 코드를 보면 트랜잭션의 시작 + Commit / Rollback (try-catch) 로 구현되는데,
- 트랜잭션이 시작되고, 해당 메서드 내부 기능이 수행되고, 결과에 따라 try-catch로 Commit과 Rollback이 결정되는 것이다.
2. 코멘트
- 오늘은 3주차 과제를 마무리했다.
- WebSecurityConfigureAdapter가 Deprecated되어 권장사항으로 코드 변경
- 회원가입 validation check 부분 테스트 프레임워크 및 모키토 (Mock Object 생성 자동화) 를 사용하여 구현했다.