wasup

Spring) AOP( Aspect Oriented Programming ) / 관점지향 프로그래밍 본문

IT/Java

Spring) AOP( Aspect Oriented Programming ) / 관점지향 프로그래밍

wasupup 2021. 8. 9. 19:55
반응형

AOP

: 모든 메소드 호출 시간을 측정하고 싶을 때

: 회원 가입 시간, 회원 조회 시간을 측정하고 싶을 때

Aspect Oriented Programming : 관점지향 프로그래밍 : 공동관심사항과 핵심관심사항을 분리

시간 측정의 로직을 별도의 공통 로직으로 만든 결과

: 핵심 관심 사항을 깔끔하게 유지

: 변경이 필요한 경우 해당 로직만 변경 가능

: 원하는 적용 대상을 선택 가능

//패키지명 아래있는 클래스 모두
@Around("execution(* com.was.waspj..*(..))")

 

//서비스 아래있는 클래스 모두
@Around("execution(* com.was.waspj.service..*(..))")

Controller -> Service -> Repository

프록시 Controller -> 실제 Controller -> 프록시 Service -> 실제 Service -> 프록시 Repository -> 실제 Repository

 

* AOP 적용 전에는 컨트롤러가 서비스를 바로 의존하는 관계였다.

* AOP 적용 후에는 컨트롤러는 서비스를 의존하기 전에 가짜 서비스(프록시)를 만들어낸다

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
        System.out.println("memberService = " + memberService.getClass());
    }

 

Service = class com.was.waspj.service.Service$$EnhancerBySpringCGLIB$$8639ea25

* 프록시가 끝나면 joinPoint.proceed(); 으로 다음 메소드를 실행한다.


Controller

package com.was.waspj.controller;

import com.was.waspj.domain.Member;
import com.was.waspj.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;

@Controller
public class MemberController {
    //private final MemberService memberService = new MemberService();
    //->MemberService를 가져다 써야하는데
    //->다른 소스블럭에서 매번 가져다 쓰면 문제가 있음?
    private MemberService memberService;

    //필드 주입
    //@Autowired private MemberService memberService;

    //setter 주입 : 단점이라면 public으로 노출됨.
//    @Autowired
//    public void setMemberService(MemberService memberService) {
//        this.memberService = memberService;
//    }

    //생성자 주입
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping("/members/new")
    public String createForm(){
        return "members/createMemberForm";
    }

    @PostMapping("/members/new")
    public String create(MemberForm form){
        Member member = new Member();
        member.setName(form.getName());

        //System.out.println("member : " + member.getName());

        memberService.join(member);

        return "redirect:/";
    }

    @GetMapping("/members")
    public String list(Model model){
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
}

Service

package com.was.waspj.service;

import com.was.waspj.domain.Member;
import com.was.waspj.repository.MemberRepository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /*
    * 회원가입
    * */
    public Long join(Member member){
        //같은 이름이 있는 중복 회원은 안됨.
        //ifPresent : result가 null이 아니고 어떤 값이 있는경우 동작함.
        //값을 바로 꺼내고싶다면 .get을 사용하면 되지만 권장되지 않음.
        //.orElseGet : 값이 있으면 꺼내고 없으면 어떤 메서드를 실행.
        
        //Optional<Member> result = memberRepository.findByName(member.getName());
        //Oprional로 꺼내는 것 보다 아래처럼
        validateDuplicateMember(member);//중복 회원 검증
        //통과하면
        memberRepository.save(member);//가입
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                        .ifPresent(m ->{
                            throw new IllegalStateException("이미 존재하는 회원입니다.");
                        });
    }

    /*전체회원 조회*/
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    /*회원id 반환*/
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }

}

Repository

package com.was.waspj.repository;

import com.was.waspj.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    //Optional : 자바8부터 생겼음.
    //값이 없을 때 null을 Optional로 감싸서 반환하는 방법이 선호되고있음
    List<Member> findAll();
}

 

package com.was.waspj.repository;

import com.was.waspj.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository{
    @Override
    Optional<Member> findByName(String name);
}

Aop

package com.was.waspj.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component//->SpringConfig에 직접등록하는것이 더 좋음.
public class TimeTraceAop {
    //                      패키지명 아래있는 클래스 모두
    @Around("execution(* com.was.waspj..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        //@Around()
        long start = System.currentTimeMillis();
        System.out.println("시작 : " + joinPoint.toString());
        try{
            //.proceed(); -> 다음 메소드로 진행
            return joinPoint.proceed();
        }finally {
            //로직이 끝날 때 그리고 예외 발생 유무와 상관 없이 System.currentTimeMillis();를 확인 -> finally
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("끝 : " + joinPoint.toString() + " " + timeMs + "ms");
        }


    }
}

SpringConfig

package com.was.waspj;

import com.was.waspj.aop.TimeTraceAop;
import com.was.waspj.repository.MemberRepository;
import com.was.waspj.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.Time;

@Configuration
public class SpringConfig {
    private final MemberRepository memberRepository;

    //생성자가 하나인경우 @Autowired 생략가능
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository);
    }

//    @Bean
//    public TimeTraceAop timeTraceAop(){
//        return new TimeTraceAop();
//    }

}

인프런 강의 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

반응형
Comments