본문 바로가기
프로그래밍/SPRING

[SPRING] IOC, DI, COMPONENT, BEAN, AOP

by 두둠칫 2021. 10. 25.

 

1. IOC : Inversion Of Control

- 스프링에서는 일반적인 JAVA 객체를 생성하여 개발자가 관리하는 것이 아닌 Spring Container에게 모두 맡긴다

- 객체 관리의 제어 권한이 개발자 -> Spring Framework(의 Spring Container)에게 넘어감 : 제어의 역전

 

2. DI : Dependency Injection

- 외부로부터 사용할 객체를 주입 받는다. 즉, Spring Container가 주입한다.

- 의존 코드를 따로 두어, 의존성으로부터 격시킴으로써 코드 테스트에 용이

- 이에따라 안정적으로 테스트 가능(기대값 활용)
- 추상화, 순환참조방지 가능

 

- IOC, DI 예제 : Base64, URL Encoder

1) 적용전

public class Base64Encoder {
    public String encode(String msg){
        return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
    }
}

public class urlEncoder { ... }
public class SpringIocApplication {

    public static void main(String[] args) {

        String url = "...";
        
        // base64, url Encoding code
        Base64Encoder base64Encoder = new Base64Encoder();
        // UrlEncoder urlEncoder = new UrlEncoder();
        
        String result = base64Encoder.encode(url);
        System.out.println(result);
    }

}

 : 원하는 동작에 따라 encoder를 생성해서 encoding

 

2) DI 적용

public class Encoder {

    private IEncoder iEncoder;

    // DI 주입(param 설정)
    public Encoder(IEncoder iEncoder){
//        this.iEncoder = new Base64Encoder();
//        this.iEncoder = new UrlEncoder();

        this.iEncoder = iEncoder;
    }

    public void setiEncoder(IEncoder iEncoder){
        this.iEncoder = iEncoder;
    }

    public String encode(String msg){
        return iEncoder.encode(msg);
    }
}
public interface IEncoder {
    String encode(String msg);
}
public class Base64Encoder implement IEncdoer {
    public String encode(String msg){
        return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
    }
}

public class urlEncoder implement IEncdoer { ... }
public class SpringIocApplication {

    public static void main(String[] args) {
    
        String url = "...";
        
        // still created by programmer(new)
        Encoder encoder = new Encoder(new Base64Encoder());
        //Encoder encoder = new Encoder(new urlEncoder());
        
        Strubg result = encoder.encode(url);
        System.out.println(result);
    }

}

 : 코드 수정은 간결해졌지만 여전히 programmer에 의해 관리(new 생성)

 

3) IOC 적용

public interface IEncoder {
    String encode(String msg);
}
@Component // spring container에게 "이 클래스를 bean으로 관리해라"라는 어노테이션 즉 IOC 발생
public class Base64Encoder implement IEncdoer {
    public String encode(String msg){
        return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
    }
}

@Component
public class urlEncoder implement IEncdoer { ... }
@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException{
        context = applicationContext;
    }

    public static ApplicationContext getContext(){
        return context;
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringIocApplication.class, args);
        ApplicationContext context = ApplicationContextProvider.getContext();
        
        String url = "...";
        
        Encoder encoder = context.getBean("base64Encode", Encoder.class); // Encoder class에 qualifier로 지정된 것으로 실행
        
        String result = encoder.encode(url);
        System.out.println(result);
    }

}


@Configuration // 여러 개의 Bean 생성
class AppConfig{

     @Bean("base64Encode")
     public Encoder encoder(Base64Encoder base64Encoder){
         return new Encoder(base64Encoder);
     }

    @Bean("urlEncode")
    public Encoder encoder(UrlEncoder urlEncoder){
        return new Encoder(urlEncoder);
     }
}

 : programmer가 new로 생성하는 것 없이 모두 Bean으로 Spring Container가 관리

 

cf) AppConfig 대신 Encoder에 직접 Qualifier 지정하는 것으로도 가능

@Component
public class Encoder {

    private IEncoder iEncoder;

    public Encoder(@Qualifier("urlEncoder") IEncoder iEncoder){ // qualifier로 설정(Component name)
        this.iEncoder = iEncoder;
    }

    public void setiEncoder(IEncoder iEncoder){
        this.iEncoder = iEncoder;
    }

    public String encode(String msg){
        return iEncoder.encode(msg);
    }
}

 

 

 

3. Bean

- Spring Container가 관리하는 단위

- @Component 어노테이션을 통해 Class를 Bean으로 등록 가능

cf) method는 @Bean으로 등록. 즉, 하나의 @Compnent 내에 여러 @Bean

- 어노테이션 등록한 이후 해당 객체는 Spring Container가 관리 : IOC

 

 

 

4. AOP : Aspect Oriented Programming : 관점지향 프로그래밍

- 대부분 Web, Business, Data Layer로 정의

Web Business Data
REST API 제공, Client 중심 로직 적용 내부 정책에 따른 로직 개발 DB 및 외부 연동 처리

- Spring에서 어노테이션으로 실현 가능 ex) @Before, @After, @AfterReturning

 

예시)

// create custom annotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
@Aspect // aop로 동작하게 하는 어노테이션
@Component
public class ParameterAop {

    @Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
    private void cut(){

    }

    @Before("cut()")
    public void before(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println(method.getName());


        Object[] args = joinPoint.getArgs(); // 매개변수배열

        for(Object obj : args){
            System.out.println("type : "+obj.getClass().getSimpleName());
            System.out.println("value : "+obj);
        }
    }

    @AfterReturning(value = "cut()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj){
        System.out.println("return obj : " + returnObj);
    }

}
@Aspect
@Component
public class TimerAop {

    @Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
    private void cut(){

    }

    @Pointcut("@annotation(com.example.aop.annotation.Timer)") // Timer annotation이 붙은 것
    private void enableTimer(){

    }

    @Around("cut() && enableTimer()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object result = joinPoint.proceed();

        stopWatch.stop();

        System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
    }
}
@Aspect
@Component
public class DecodeAop {

    @Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
    private void cut(){

    }

    @Pointcut("@annotation(com.example.aop.annotation.Decode)") // Decode annotation이 붙은 것
    private void enableDecode(){

    }

    @Before("cut() && enableDecode()")
    public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {

        Object[] args = joinPoint.getArgs();

        for(Object arg : args){
            if(arg instanceof User){
                User user = User.class.cast(arg);
                String base64Email = user.getEmail();
                String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");

                user.setEmail(email);
            }
        }
    }

    @AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
    public void afterReturning(JoinPoint joinPoint, Object returnObj){

        if(returnObj instanceof User){
            User user = User.class.cast(returnObj);
            String email = user.getEmail();
            String base64Email = Base64.getEncoder().encodeToString(email.getBytes());

            user.setEmail(base64Email);
        }
    }
}
@RestController
@RequestMapping("/api")
public class RestApiController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable Long id, @RequestParam String name){
        // before aop applied
//        System.out.println("get method");
//        System.out.println("get method : " + id + " " + name);
        return id + " " + name;
    }

    @PostMapping("/post")
    public User post(@RequestBody User user){
        // before aop applied
//        System.out.println("post method : "+user);
        return user;
    }

    @Timer // Timer aop만 반응할 수 있도록
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {

        // db logic...
        Thread.sleep(1000*2);
    }

    @Decode // Decode aop만 반응할 수 있도록
    @PutMapping("/put")
    public User put(@RequestBody User user){
        System.out.println("put : " + user);

        return user;
    }
}

 : 요청 -> Controller -> Annotation & Mapping -> AOP -> 설정된 관점에서 동작

'프로그래밍 > SPRING' 카테고리의 다른 글

한끝완2  (0) 2022.05.23
한끝완1  (0) 2022.02.20
[SPRING] Response, ObjectMapper  (0) 2021.09.09
[SPRING] SPRING BOOT 특징, REST API, JSON  (0) 2021.09.08
[SPRING] 웹 개발 개론  (0) 2021.08.16