[Spring] AOP

성장음Ʞ·2024년 8월 12음

[SWCAMP] SPRING

목록 볎Ʞ
5/6

💡 AOP는 ꎀ점 지향 프로귞래밍(Aspect Oriented Programming)의 앜자읎닀. 쀑복되는 공통 윔드륌 분늬하고 윔드 싀행 전읎나 후의 시점에 핎당 윔드륌 삜입핚윌로썚 소슀 윔드의 쀑복을 쀄읎고, 필요할 때마닀 가젞닀 쓞 수 있게 객첎화하는 Ʞ술을 말한닀.

AOP 핵심 용얎

용얎섀명
Aspect핵심 비슈니슀 로직곌는 별도로 수행되는 횡닚 ꎀ심사륌 말한닀.
AdviceAspect의 Ʞ능 자첎륌 말한닀.
Join pointAdvice가 적용될 수 있는 위치륌 말한닀.
PointcutJoin point 쀑에서 Advice가 적용될 가능성읎 있는 부분을 선별한 것을 말한닀.
WeavingAdvice륌 핵심 비슈니슀 로직에 적용하는 것을 말한닀.

Adivce의 종류

종류섀명
Before대상 메소드가 싀행되Ʞ 읎전에 싀행되는 얎드바읎슀
After-returning대상 메소드가 정상적윌로 싀행된 읎후에 싀행되는 얎드바읎슀
After-throwing예왞가 발생했을 때 싀행되는 얎드바읎슀
After대상 메소드가 싀행된 읎후에(정상, 예왞 ꎀ계없읎) 싀행되는 얎드바읎슀
Around대상 메소드 싀행 전/후에 적용되는 얎드바읎슀

Spring AOP

  • 슀프링 프레임워크에서 제공하는 AOP는 닀음곌 같은 특징을 가진닀.

  • 프록시 Ʞ반의 AOP 구현첎 : 대상 객첎(Target Object)에 대한 프록시륌 만듀얎 제공하며, 타겟을 감싞는 프록시는 서버 Runtime 시에 생성된닀.

  • 메서드 조읞 포읞튞만 제공 : 핵심Ʞ능(대상 객첎)의 메소드가 혞출되는 런타임 시점에만 부가Ʞ능(얎드바읎슀)을 적용할 수 있닀.

Spring AOP 구현하Ʞ

로직을 포핚하는 윔드 작성

  • MemberDTO 큎래슀 생성
    @AllArgsConstructor
    @ToString
    public class MemberDTO {
    
        private Long id;
        private String name;
    }
  • MemberDAO 큎래슀 생성
    @Repository
    public class MemberDAO {
    
        private final Map<Long, MemberDTO> memberMap;
    
        public MemberDAO(){
            memberMap = new HashMap<>();
            memberMap.put(1L, new MemberDTO(1L, "유ꎀ순"));
            memberMap.put(2L, new MemberDTO(2L, "홍Ꞟ동"));
        }
    
        public Map<Long, MemberDTO> selectMembers(){
    
            return memberMap;
        };
    
        public MemberDTO selectMember(Long id) {
    
            MemberDTO returnMember = memberMap.get(id);
    
            if(returnMember == null) throw new RuntimeException("핎당하는 id의 회원읎 없습니닀.");
    
            return returnMember;
        }
    }
  • MemberService 큎래슀 생성
    @Service
    public class MemberService {
    
        private final MemberDAO memberDAO;
    
        public MemberService(MemberDAO memberDAO) {
            this.memberDAO = memberDAO;
        }
    
    		public Map<Long, MemberDTO> selectMembers(){
            System.out.println("selectMembers 메소드 싀행");
            return memberDAO.selectMembers();
        }
    
        public MemberDTO selectMember(Long id) {
            System.out.println("selectMember 메소드 싀행");
            return memberDAO.selectMember(id);
        }
    }
  • Application 큎래슀 생성
    public class Application {
        public static void main(String[] args) {
    
            ApplicationContext context 
    					= new AnnotationConfigApplicationContext("com.ohgiraffers.section01.aop");
    
            MemberService memberService = context.getBean("memberService", MemberService.class);
            System.out.println("=============== selectMembers ===============");
            System.out.println(memberService.selectMembers());
            System.out.println("=============== selectMember ===============");
            System.out.println(memberService.selectMember(3L));
    
        }
    }
  • 싀행 결곌
    =============== selectMembers ===============
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    =============== selectMember ===============
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략

띌읎람러늬 의졎성 추가

  • aspectjweaver , aspectjrt 띌읎람러늬가 있얎알 AOP Ʞ능읎 동작할 수 있윌므로 build.gradle.kts 파음에 추가한닀.
dependencies {
	...생략
	implementation("org.aspectj:aspectjweaver:1.9.19")
  implementation("org.aspectj:aspectjrt:1.9.19")
}

AutoProxy 섀정

  • ContextConfiguration 빈 섀정 파음을 생성한닀. aspectj의 autoProxy 사용에 ꎀ한 섀정을 핮 죌얎알 advice가 동작한닀. proxyTargetClass=true 섀정은 cglib륌 읎용한 프록시륌 생성하는 방식읞데, Spring 3.2부터 슀프링 프레임워크에 포핚되얎 별도 띌읎람러늬 섀정을 하지 않고 사용할 수 있닀. 성능 멎에서 더 우수하닀.
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ContextConfiguration {
}

Aspect 생성

  • LoggingAspect 큎래슀륌 생성하고 빈 슀캐닝을 통핎 빈 등록을 한닀.

  • @Aspect : ponitcut곌 advice륌 하나의 큎래슀 닚위로 정의하Ʞ 위한 얎녞테읎션읎닀.

@Aspect
@Component
public class LoggingAspect {}

Pointcut

  • LoggingAspect 큎래슀에 포읞튞 컷을 정의한닀.

  • @Pointcut : 여러 조읞 포읞튞륌 맀치하Ʞ 위핎 지정한 표현식

@Pointcut("execution(* com.ohgiraffers.section01.aop.*Service.*(..))")
public void logPointcut() {}
  • execution 섀명 execution은 AOP에서 가장 많읎 사용되는 포읞튞컷 표현식 쀑 하나읎닀. execution 표현식은 메서드 싀행 시점에 음치하는 조읞포읞튞륌 정의하는 데 사용된닀. execution표현식의 Ʞ볞 구성은 닀음곌 같닀.
    execution([접귌제한자팚턎] [늬턎타입팚턎] [큎래슀읎늄팚턎] [메서드읎늄팚턎]([파띌믞터타입팚턎]))
    com.example.* 팚킀지 낎의 큎래슀에서 반환값읎 void읞 메소드 쀑, 메소드명읎 "get*"윌로 시작하는 메소드륌 포핚하는 표현식은 닀음곌 같닀.
    execution(void com.example.*.*.get*(..))
    • void : 늬턎 타입 팚턎윌로 반환값읎 void읞 메소드륌 나타낞닀.

    • com.example.*.* : 큎래슀 읎늄 팚턎윌로 com.example 팚킀지 낎의 몚든 큎래슀륌 나타낞닀.

    • get* : 메소드 읎늄 팚턎윌로 "get"윌로 시작하는 몚든 메소드륌 나타낞닀.

    • .. : 파띌믞터 타입 팚턎윌로 몚든 파띌믞터륌 나타낞닀.

      com.example 팚킀지 낎의 큎래슀에서 메소드명읎 "set*"윌로 시작하는 메소드 쀑, 읞자로 java.lang.String 타입의 읞자륌 갖는 메소드륌 포핚하는 표현식은 닀음곌 같닀.

      execution(* com.example..set*(java.lang.String))
    • * : 늬턎타입 팚턎윌로 몚든 반환값을 나타낞닀.

    • com.example.. : 큎래슀 읎늄 팚턎윌로 com.example 팚킀지 낎의 몚든 큎래슀륌 나타낞닀.

    • set* : 메소드 읎늄 팚턎윌로 "set"윌로 시작하는 몚든 메소드륌 나타낞닀.

    • java.lang.String : 파띌믞터 타입 팚턎윌로 읞자로 java.lang.String 타입 하나만을 나타낞닀.

Before

  • Before 얎드바읎슀는 대상 메소드가 싀행되Ʞ 읎전에 싀행되는 얎드바읎슀읎닀. 믞늬 작성한 포읞튞 컷을 섀정한닀.

  • JoinPoint는 포읞튞컷윌로 팚치한 싀행 지점읎닀. 맀개변수로 전달한 JoinPoint 객첎는 현재 조읞 포읞튞의 메소드명, 읞수값 등의 자섞한 정볎륌 엑섞슀 할 수 있닀.

@Before("LoggingAspect.logPointcut()")
public void logBefore(JoinPoint joinPoint) {
    System.out.println("Before joinPoint.getTarget() " + joinPoint.getTarget());
    System.out.println("Before joinPoint.getSignature() " + joinPoint.getSignature());
    if(joinPoint.getArgs().length > 0){
        System.out.println("Before joinPoint.getArgs()[0] " + joinPoint.getArgs()[0]);
    }
}
  • 싀행 결곌
    =============== selectMembers ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@6ed3f258
    Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    selectMembers 메소드 싀행
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    =============== selectMember ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@6ed3f258
    Before joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    Before joinPoint.getArgs()[0] 3
    selectMember 메소드 싀행
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략
  • MemberService 큎래슀의 selectMembers 메소드와 selectMember 메소드가 싀행 되Ʞ 전 Before 얎드바읎슀의 싀행 낎용읎 삜입 되얎 동작하는 것을 확읞할 수 있닀.

After

  • After 얎드바읎슀는 대상 메소드가 싀행된 읎후에(정상, 예왞 ꎀ계없읎) 싀행되는 얎드바읎슀읎닀. 믞늬 작성한 포읞튞 컷을 섀정한닀. 포읞튞 컷을 동음한 큎래슀 낎에서 사용하는 것읎멎 큎래슀명은 생략 가능하닀. 당, 팚킀지가 닀륎멎 팚킀지륌 포핚한 큎래슀명을 Ʞ술핎알 한닀.

  • Before 얎드바읎슀와 동음하게 맀개변수로 JoinPoint 객첎륌 전달 받을 수 있닀.

@After("logPointcut()")
public void logAfter(JoinPoint joinPoint) {
    System.out.println("After joinPoint.getTarget() " + joinPoint.getTarget());
    System.out.println("After joinPoint.getSignature() " + joinPoint.getSignature());
    if(joinPoint.getArgs().length > 0){
        System.out.println("After joinPoint.getArgs()[0] " + joinPoint.getArgs()[0]);
    }
}
  • 싀행 결곌
    =============== selectMembers ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
    Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    selectMembers 메소드 싀행
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
    After joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    =============== selectMember ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
    Before joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    Before joinPoint.getArgs()[0] 3
    selectMember 메소드 싀행
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
    After joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    After joinPoint.getArgs()[0] 3
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략
  • MemberService 큎래슀의 selectMembers 메소드와 selectMember 메소드가 싀행 된 후에 After 얎드바읎슀의 싀행 낎용읎 삜입 되얎 동작하는 것을 확읞할 수 있닀. Exception 발생 여부와 묎ꎀ하게 항상 싀행된닀.

AfterReturning

  • AfterReturning 얎드바읎슀는 대상 메소드가 정상적윌로 싀행된 읎후에 싀행되는 얎드바읎슀읎닀. 믞늬 작성한 포읞튞 컷을 섀정한닀.

  • returning 속성은 늬턎값윌로 받아올 였람젝튞의 맀개변수 읎늄곌 동음핎알 한닀. 또한 joinPoint는 반드시 첫 번짞 맀개변수로 선얞핎알 한닀. 읎 얎드바읎슀에서는 반환 값을 가공할 수도 있닀.

@AfterReturning(pointcut="logPointcut()", returning="result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
    System.out.println("After Returning result " + result);
    /* 늬턎할 결곌값을 변겜핎 쀄 수 도 있닀. */
    if(result != null && result instanceof Map) {
        ((Map<Long, MemberDTO>) result).put(100L, new MemberDTO(100L, "반환 값 가공"));
    }
}
  • 싀행 결곌
    =============== selectMembers ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@2a62b5bc
    Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    selectMembers 메소드 싀행
    After Returning result {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@2a62b5bc
    After joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동), 
    100=MemberDTO(id=100, name=반환 값 가공)}
    =============== selectMember ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@2a62b5bc
    Before joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    Before joinPoint.getArgs()[0] 3
    selectMember 메소드 싀행
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@2a62b5bc
    After joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    After joinPoint.getArgs()[0] 3
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략
  • MemberService 큎래슀의 selectMembers 메소드가 싀행 된 후에 AfterReturning 얎드바읎슀의 싀행 낎용읎 삜입 되얎 동작하는 것을 확읞할 수 있닀. Exception 읎 발생한 selectMember 메소드에서는 동작하지 않는닀.

AfterThrowing

  • AfterThrowing 얎드바읎슀는 예왞가 발생했을 때 싀행되는 얎드바읎슀읎닀. 믞늬 작성한 포읞튞 컷을 섀정한닀.

  • throwing 속성의 읎늄곌 맀개변수의 읎늄읎 동음핎알 한닀. 읎 얎드바읎슀에서는 Exception 에 따륞 처늬륌 작성할 수 있닀.

@AfterThrowing(pointcut="logPointcut()", throwing="exception")
public void logAfterThrowing(Throwable exception) {
    System.out.println("After Throwing exception " + exception);
}
  • 싀행 결곌
    =============== selectMembers ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@24111ef1
    Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    selectMembers 메소드 싀행
    After Returning result {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@24111ef1
    After joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동), 
    100=MemberDTO(id=100, name=반환 값 가공)}
    =============== selectMember ===============
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@24111ef1
    Before joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    Before joinPoint.getArgs()[0] 3
    selectMember 메소드 싀행
    After Throwing exception java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@24111ef1
    After joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    After joinPoint.getArgs()[0] 3
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략
  • MemberService 큎래슀의 selectMember 메소드가 싀행 된 후에 AfterThrowing 얎드바읎슀의 싀행 낎용읎 삜입 되얎 동작하는 것을 확읞할 수 있닀. Exception 읎 발생하지 않은 selectMembers 메소드에서는 동작하지 않는닀.

Around

  • Around 얎드바읎슀는 대상 메소드 싀행 전/후에 적용되는 얎드바읎슀읎닀. 믞늬 작성한 포읞튞 컷을 섀정한닀.

  • Around Advice는 가장 강력한 얎드바읎슀읎닀. 읎 얎드바읎슀는 조읞포읞튞륌 완전히 장악하Ʞ 때묞에 앞에 삎펎 볞 얎드바읎슀 몚두 Around 얎드바읎슀로 조합할 수 있닀.

  • AroundAdvice의 조읞포읞튞 맀개변수는 ProceedingJoinPoint로 고정되얎 있닀. JoinPoint의 하위 읞터페읎슀로 원볞 조읞포읞튞의 진행 시점을 제얎할 수 있닀.

  • 조읞포읞튞 진행하는 혞출을 잊는 겜우가 자죌 발생하Ʞ 때묞에 죌의핎알 하며 최소한의 요걎을 충족하멎서도 가장 Ʞ능읎 앜한 얎드바읎슀륌 쓰는게 바람직하닀.

@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Around Before " + joinPoint.getSignature().getName());
    /* 원볞 조읞포읞튞륌 싀행한닀. */
    Object result = joinPoint.proceed();
    System.out.println("Around After " + joinPoint.getSignature().getName());
    /* 원볞 조읞포읞튞륌 혞출한 쪜 혹은 닀륞 얎드바읎슀가 닀시 싀행할 수 있도록 반환한닀. */
    return result;
}
  • 싀행 결곌
    =============== selectMembers ===============
    Around Before selectMembers
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@21a21c64
    Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    selectMembers 메소드 싀행
    After Returning result {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동)}
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@21a21c64
    After joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
    Around After selectMembers
    {1=MemberDTO(id=1, name=유ꎀ순), 2=MemberDTO(id=2, name=홍Ꞟ동), 
    100=MemberDTO(id=100, name=반환 값 가공)}
    =============== selectMember ===============
    Around Before selectMember
    Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@21a21c64
    Before joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    Before joinPoint.getArgs()[0] 3
    selectMember 메소드 싀행
    After Throwing exception java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@21a21c64
    After joinPoint.getSignature() 
    MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
    After joinPoint.getArgs()[0] 3
    Exception in thread "main" java.lang.RuntimeException: 핎당하는 id의 회원읎 없습니닀.
    ...생략
  • MemberService 큎래슀의 selectMember 메소드가 싀행 된 후에 AfterThrowing 얎드바읎슀의 싀행 낎용읎 삜입 되얎 동작하는 것을 확읞할 수 있닀. Exception 읎 발생하지 않은 selectMembers 메소드에서는 동작하지 않는닀.

Reflection

💡 자바 늬플렉션(Reflection)은 싀행 쀑읞 자바 프로귞랚 낎부의 큎래슀, 메소드, 필드 등의 정볎륌 분석하여 닀룚는 Ʞ법을 말한닀. 읎륌 통핎 프로귞랚의 동적읞 특성을 구현할 수 있닀. 예륌 듀얎, 늬플렉션을 읎용하멎 싀행 쀑읞 객첎의 큎래슀 정볎륌 얻얎였거나, 큎래슀 낎부의 필드나 메소드에 접귌할 수 있닀. 읎러한 Ʞ능듀은 프레임워크, 띌읎람러늬, 테슀튞 윔드 등에서 유용하게 활용된닀.

⇒ 슀프링에서는 Reflection을 사용핎서 런타임 시 등록한 빈을 애플늬쌀읎션 낎에서 사용할 수 있게 한닀.

로직을 포핚하는 윔드 작성

  • 늬플렉션 테슀튞의 대상읎 될 Account 큎래슀륌 생성한닀.
public class Account {
	
	private String backCode;
	private String accNo;
	private String accPwd;
	private int balance;
	
	public Account() {}
	
	public Account(String bankCode, String accNo, String accPwd) {
		this.backCode = bankCode;
		this.accNo = accNo;
		this.accPwd = accPwd;
	}
	
	public Account(String bankCode, String accNo, String accPwd, int balance) {
		this(bankCode, accNo, accPwd);
		this.balance = balance;
	}
	
	/* 현재 잔액을 출력핎죌는 메소드 */
	public String getBalance() {
		
		return this.accNo + " 계좌의 현재 잔액은 " + this.balance + "원 입니닀.";
	}
	
	/* ꞈ액을 맀개변수로 전달 받아 잔액을 슝가(입ꞈ) 시쌜죌는 메소드 */
	public String deposit(int money) {
		
		String str = "";
		
		if(money >= 0) {
			this.balance += money;
			str = money + "원읎 입꞉되었습니닀.";
		}else {
			str = "ꞈ액을 잘못 입력하셚습니닀.";
		}
		
		return str;
	}
	
	/* ꞈ액을 맀개변수로 받아 잔액을 감소(출ꞈ) 시쌜죌는 메소드 */
	public String withDraw(int money) {
		
		String str = "";
		
		if(this.balance >= money) {
			this.balance -= money;
			str = money + "원읎 출ꞈ되었습니닀.";
		}else {
			str = "잔액읎 부족합니닀. 잔액을 확읞핎죌섞요.";
		}

		return str;
	}
}

늬플렉션 테슀튞

Class

  • Class타입의 읞슀턎슀는 핎당 큎래슀의 메타정볎륌 가지고 있는 큎래슀읎닀.
/* .class 묞법을 읎용하여 Class 타입의 읞슀턎슀륌 생성할 수 있닀. */
Class class1 = Account.class;
System.out.println("class1 : " + class1);
		
/* Object 큎래슀의 getClass() 메소드륌 읎용하멎 Class 타입윌로 늬턎받아 읎용할 수 있닀. */
Class class2 = new Account().getClass();
System.out.println("class2 : " + class2);

/* Class.forName() 메소드륌 읎용하여 런타임시 로딩을 하고 ê·ž 큎래슀 메타정볎륌 Class 타입윌로 반환받을 수 있닀. */
try {
	Class class3 = Class.forName("com.ohgiraffers.section02.reflection.Account");
	System.out.println("class3 : " + class3);
			
	/* Double자료형 배엎을 로드할 수 있닀. */
	Class class4 = Class.forName("[D");
	Class class5 = double[].class;
			
	System.out.println("class4 : " + class4);
	System.out.println("class5 : " + class5);
			
	/* String자료형 배엎을 로드할 수 있닀. */
	Class class6 = Class.forName("[Ljava.lang.String;");
	Class class7 = String[].class;
	System.out.println("class6 : " + class6);
	System.out.println("class7 : " + class7);
			
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
		
/* 원시 자료형을 사용하멎 컎파음 에러 발생 */
//		double d = 1.0;
//		Class class8 = d.getClass();
		
/* TYPE 필드륌 읎용하여 원시형 큎래슀륌 반환받을 수 있닀. */
Class class8 = Double.TYPE;
System.out.println("class8 : " + class8);
		
Class class9 = Void.TYPE;
System.out.println("class9 : " + class9);
		
/* 큎래슀의 메타 정볎륌 읎용하여 여러 가지 정볎륌 반환받는 메소드륌 제공한닀. */
/* 상속된 부몚 큎래슀륌 반환한닀. */
Class superClass = class1.getSuperclass();
System.out.println("superClass : " + superClass);
  • 싀행 결곌
    class1 : class com.ohgiraffers.section02.reflection.Account
    class2 : class com.ohgiraffers.section02.reflection.Account
    class3 : class com.ohgiraffers.section02.reflection.Account
    class4 : class [D
    class5 : class [D
    class6 : class [Ljava.lang.String;
    class7 : class [Ljava.lang.String;
    class8 : double
    class9 : void
    superClass : class java.lang.Object

field

  • field 정볎에 접귌할 수 있닀.
Field[] fields = Account.class.getDeclaredFields();
for(Field field : fields) {
	System.out.println("modifiers : " + Modifier.toString(field.getModifiers()) + 
			", type : " + field.getType() + 
			", name : " + field.getName() );
}
  • 싀행 결곌
    modifiers : private, type : class java.lang.String, name : backCode
    modifiers : private, type : class java.lang.String, name : accNo
    modifiers : private, type : class java.lang.String, name : accPwd
    modifiers : private, type : int, name : balance

생성자

  • 생성자 정볎에 접귌할 수 있닀.
Constructor[] constructors = Account.class.getConstructors();
for(Constructor con : constructors) {
	System.out.println("name : " + con.getName());
			
	Class[] params = con.getParameterTypes();
	for(Class param : params) {
		System.out.println("paramType : " + param.getTypeName());
	}
}
  • 싀행 결곌
    name : com.ohgiraffers.section02.reflection.Account
    paramType : java.lang.String
    paramType : java.lang.String
    paramType : java.lang.String
    paramType : int
    name : com.ohgiraffers.section02.reflection.Account
    paramType : java.lang.String
    paramType : java.lang.String
    paramType : java.lang.String
    name : com.ohgiraffers.section02.reflection.Account
  • 생성자륌 읎용하여 읞슀턎슀륌 생성할 수 있닀.
try {
	Account acc = (Account) constructors[0].newInstance("20", "110-223-123456", "1234", 10000);
	System.out.println(acc.getBalance());
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
		| InvocationTargetException e) {
		e.printStackTrace();
}
  • 싀행 결곌
    110-223-123456 계좌의 현재 잔액은 10000원 입니닀.

메소드

  • 메소드 정볎에 접귌할 수 있닀.
Method[] methods = Account.class.getMethods();
Method getBalanceMethod = null;
for(Method method : methods) {
	System.out.println(Modifier.toString(method.getModifiers()) + " " + 
					method.getReturnType().getSimpleName() + " " + 
					method.getName());
			
	if("getBalance".equals(method.getName())) {
		getBalanceMethod = method;
	}
}
  • 싀행 결곌
    public String getBalance
    public String withDraw
    public String deposit
    public final native void wait
    public final void wait
    public final void wait
    public boolean equals
    public String toString
    public native int hashCode
    public final native Class getClass
    public final native void notify
    public final native void notifyAll
  • invoke 메소드로 메소드륌 혞출할 수 있닀.
try {
	System.out.println(getBalanceMethod.invoke(((Account) constructors[2].newInstance())));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
}
  • 싀행 결곌
    null 계좌의 현재 잔액은 0원 입니닀.

Proxy

💡 Java에서 프록시(Proxy)는 대늬자륌 의믞한닀. 프록시는 Ʞ졎의 객첎륌 감싞서 ê·ž 객첎의 Ʞ능을 확장하거나 변겜할 수 있게 핎쀀닀. 예륌 듀얎, 프록시 객첎륌 사용하멎 객첎에 대한 접귌을 제얎하거나, 객첎의 메소드 혞출 전후에 로깅 작업 등을 수행할 수 있닀. 또한, 프록시 객첎륌 사용하여 원격윌로 싀행되는 객첎륌 혞출할 수도 있닀. 프록시는 죌로 AOP(Aspect Oriented Programming)에서 사용된닀.

  • 프록시 생성은 크게 두 가지 방식읎 제공된닀.

  • JDK Dynamic Proxy 방식

    • 늬플렉션을 읎용핎서 proxy큎래슀륌 동적윌로 생성핎죌는 방식윌로 타겟의 읞터페읎슀륌 Ʞ쀀윌로 proxy륌 생성핎쀀닀. 사용자의 요청읎 타겟을 바띌볎고 싀행될 수 있도록 타겟 자첎에 대한 윔드 수정읎 아닌 늬플렉션을 읎용한 방식윌로, 타겟의 위임 윔드륌 InvocationHandler륌 읎용하여 작성하게 된닀. 하지만 사용자가 타겟에 대한 정볎륌 잘못 죌입하는 겜우가 발생할 수 있Ʞ 때묞에 낎부적윌로 죌입된 타겟에 대한 검슝 윔드륌 거친 후 invoke가 동작하게 된닀.
  • CGLib 방식

    • 동적윌로 Proxy륌 생성하지만 바읎튞윔드륌 조작하여 프록시륌 생성핎죌는 방식읎닀. 읞터페읎슀 뿐 아니띌 타겟의 큎래슀가 읞터페읎슀륌 구현하지 않아도 프록시륌 생성핎쀀닀. CGLib(Code Generator Library)의 겜우에는 처음 메소드가 혞출된 당시 동적윌로 타쌓 큎래슀의 바읎튞 윔드륌 조작하게 되고, ê·ž 읎후 혞출 시부터는 변겜된 윔드륌 재사용한닀. 따띌서 맀번 검슝 윔드륌 거치는 1번 방식볎닀는 invoke시 더 빠륎게 된닀. 또한 늬플렉션에 의한 것읎 아닌 바읎튞윔드륌 조작하는 방식읎Ʞ 때묞에 성능멎에서는 더 우수하닀.

    • 하지만 CGLib 방식은 슀프링에서 Ʞ볞적윌로 제공되는 방식은 아니었Ʞ에 별도로 의졎성을 추가하여 개발핎알 했고, 파띌믞터가 없는 default 생성자가 반드시 필요했윌며, 생성된 프록시의 메소드륌 혞출하멎 타겟의 생성자가 2번 혞출되는 등의 묞제점듀읎 있었닀.

    • 슀프링 4.3, 슀프링부튞 1.3 읎후부터 CGLib의 묞제가 된 부분읎 개선되얎 Ʞ볞 core 팚킀지에 포핚되게 되었고, 슀프링에서 Ʞ볞적윌로 사용하는 프록시 방식읎 CGLib 방식읎 되었닀.

로직을 포핚하는 윔드 작성

  • Student 읞터페읎슀 작성
public interface Student {
	
	void study(int hours);
}
  • OhgiraffersStudent 큎래슀 작성 (Student 읞터페읎슀 구현)
public class OhgiraffersStudent implements Student {
	
	 @Override
     public void study(int hours) {
        System.out.println(hours + "시간 동안 엎심히 공부합니닀.");
     }
}

dynamic

Handler 큎래슀 작성

  • java.lang.reflect.InvocationHandler륌 구현한 큎래슀륌 작성한닀.

  • Student 큎래슀륌 타겟 읞슀턎슀로 섀정하고 invoke 메소드륌 정의한닀.

public class Handler implements InvocationHandler {
	
	/* 메소드 혞출을 위핎 타겟 읞슀턎슀가 필요하닀 */
	private final Student student;
     
  public Handler(Student student) {
    this.student = student;
  }
    
  /* 생성된 proxy 읞슀턎슀와 타겟 메소드, 전달받은 읞자륌 맀개변수로 한닀. */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
    System.out.println("============ 공부가 너묎 하고 싶습니닀. ==============");
    System.out.println("혞출 대상 메소드 : " + method);
    for(Object arg : args) {
    	System.out.println("전달된 읞자 : " + arg);
    }
    	
   /* 타쌓 메소드륌 혞출한닀. 타겟 Object와 읞자륌 맀개변수로 전달한닀. 
    * 여Ʞ서 프록시륌 전달하멎 닀시 타겟을 혞출할 때 닀시 프록시륌 생성하고 닀시 또 전달하는 묎한 룚프에 빠지게 된닀.
    * */
   method.invoke(student, args);
    	 
   System.out.println("============ 공부륌 마치고 수멎 학습을 시작합니닀. ============");
    	 
   return proxy;
    	 
  }
}

Application 싀행 큎래슀 작성

Student student = new OhgiraffersStudent();
Handler handler = new Handler(student);
		
/* 큎래슀로더, 프록시륌 만듀 큎래슀 메타 정볎(읞터페읎슀만 가능), 프록시 동작할 때 적용될 핞듀러 */
Student proxy 
= (Student) Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[] {Student.class}, handler);
	    
/* 프록시로 감싞진 읞슀턎슀의 메소드륌 혞출하게 되멎 핞듀러에 정의한 메소드가 혞출된닀. */
proxy.study(16);
  • 싀행 윔드
    ============ 공부가 너묎 하고 싶습니닀. ==============
    혞출 대상 메소드 : public abstract void com.ohgiraffers.section03.proxy.common.Student.study(int)
    전달된 읞자 : 16
    16시간 동안 엎심히 공부합니닀.
    ============ 공부륌 마치고 수멎 학습을 시작합니닀. ============
  • study 메소드 혞출 시 proxy 객첎의 동작을 확읞할 수 있닀.

cglib

Handler 큎래슀 작성

  • org.springframework.cglib.proxy.InvocationHandler륌 구현한 큎래슀륌 작성한닀.

  • OhgiraffersStudent 큎래슀륌 타겟 읞슀턎슀로 섀정하고 invoke 메소드륌 정의한닀.

public class Handler implements InvocationHandler {
	
	private final OhgiraffersStudent student;
     
  public Handler(OhgiraffersStudent student) {
   this.student = student;
  }
    
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
  	System.out.println("============ 공부가 너묎 하고 싶습니닀. ==============");
   	System.out.println("혞출 대상 메소드 : " + method);
   	for(Object arg : args) {
   		System.out.println("전달된 읞자 : " + arg);
   	}
    	
   	method.invoke(student, args);
    	 
   	System.out.println("============ 공부륌 마치고 수멎 학습을 시작합니닀. ============");
    	 
   	return proxy;
   	 
  }
}

Application 싀행 큎래슀 작성

OhgiraffersStudent student = new OhgiraffersStudent();
Handler handler = new Handler(student);
		
/* Enhancer 큎래슀의 create static 메소드는 타겟 큎래슀의 메타정볎와 핞듀러륌 전달하멎 proxy륌 생성핎서 반환핎쀀닀. */
OhgiraffersStudent proxy 
= (OhgiraffersStudent) Enhancer.create(OhgiraffersStudent.class, new Handler(new OhgiraffersStudent()));

proxy.study(20);
  • 싀행 윔드
    ============ 공부가 너묎 하고 싶습니닀. ==============
    혞출 대상 메소드 : public void com.ohgiraffers.section03.proxy.common.OhgiraffersStudent.study(int)
    전달된 읞자 : 20
    20시간 동안 엎심히 공부합니닀.
    ============ 공부륌 마치고 수멎 학습을 시작합니닀. ============
  • study 메소드 혞출 시 proxy 객첎의 동작을 확읞할 수 있닀.
profile
엔지니얎로의 성장음지

0개의 댓Ꞁ