스프링&스프링 부트의 핵심 개념

gimseonjin616·2021년 10월 24일
1

Spring

목록 보기
1/1

Spring의 목표

용이한 테스트와 느스한 결함을 제공하자!

Spring 삼각형

IoC / DI

  • IoC(Inversion Of Control)
    스프링에서 일반적으로 Java 객체를 new로 생성하여 개발자가 관리하는 것이 아닌 Spring Container에게 맡긴다. 즉 개발자에서 프레임워크로 제어의 객체 관리의 권한이 넘어갔고 이를 "제어의 역전"이라 한다.
  • DI(Dependency Injection) : 의존성 주입
    의존성으로 부터 격리시켜 코드테스트에 용이하다.
    DI를 통하여 불가능한 상황을 Mock와 같은 기술을 통하여 안정적인 테스트가 가능하다.
    코드를 확장하거나 변경할 때 영향을 최소화 한다(추상화)
    순환 참조를 막을 수 있다.

DI 예제

main class

import java.io.UnsupportedEncodingException;

public class Main {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String url = "https://velog.io/write?id=8489d4eb-e663-4bb7-b05d-9b12494d99c5";

        // base64 encoder를 사용할 때
        Encoder encoder = new Encoder(new Base64Encoder());
        // URL encoder를 사용할 때
        //Encoder encoder = new Encoder(new URLEncoder());
        String result = encoder.encode(url);
        System.out.println(result);
    }
}

다음 메인 코드를 살펴보자 우리는 주어진 URL을 encode 해야하는 상황이다. 이때 우리에게 선택지는 두 가지로 Base64 encoder 와 url encoder가 있다. 상황에 따라 두 encoder를 유연하게 수정할 수 있어야 할 때, 위와 같이 Encoder의 생성장에 파라미터로 넘겨 주는 방식으로 구현할 수 있다. 이것이 바로 DI이다.

-> 즉 DI는 최상위 레이어에서 생성자, 또는 함수의 파라미터로 필요한 객체를 주입시킴으로써 하위 레이어에서 생기는 코드 수정을 없애고 코드의 재 사용성을 높일 수 있다.

Spring의 IoC Container 예제

main 코드

package com.example.ioc_study_with_spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class IoCStudyWithSpringApplication {

    public static void main(String[] args) {
        SpringApplication.run(IoCStudyWithSpringApplication.class, args);

        ApplicationContext context = ApplicationContextProvider.getContext();


        Encoder encoder = context.getBean(Encoder.class);

        String url = "https://velog.io/write?id=8489d4eb-e663-4bb7-b05d-9b12494d99c5";

        String result = encoder.encode(url);
        System.out.println(result);
    }

}

다음은 스프링에서 IoC Container를 구현한 메인 코드다. 여기서 살펴볼 곳은 바로 ApplicationContext 이다. 이 ApplicationContext는 스프링에 등록된 빈이라는 객체를 쓸 수 있게끔 관리해주는 객체다. 즉 IoCContainer라고 볼 수 있다.

여기서 우리는 Encode라는 객체가 bean으로 등록됐고 getBean으로 가져와지는 걸 볼 수 있다. 하지만 우리는 Base64와 URL Encoder를 가져와야하는데 어떤 것을 가져오는 지 확인할 수 없다. 따라서 우리는 Encoder class도 확인해봐야 한다.

Encoder 객체

package com.example.ioc_study_with_spring;

import org.springframework.stereotype.Component;

@Component
public class Encoder{
    private IEncoder iEncoder;

    public Encoder(IEncoder iEncoder){
        this.iEncoder = iEncoder;
    }

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

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

여기서 우리는 @Component 어노테이션을 확인할 수 있다. Component 어노테이션은 이 객체를 IoC Container에 빈으로 등록하겠다는 뜻이다. 하지만 우리는 여기에서도 어떤 Encoder를 쓰는 지 확인할 수 없다. 왜냐하면 IoC Container가 자동으로 IEncoder bean 객체를 지정해주기 때문이다. 따라서 우리는 IEncoder 구현 객체를 살펴봐야 한다.

package com.example.ioc_study_with_spring;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.Base64;

@Component
public class Base64Encoder implements IEncoder {
    @Override
    public String encode(String message) {
        return Base64.getEncoder().encodeToString(message.getBytes());

    }
}

다음은 Base64Encoder 객체이다. 여기서도 @Component 어노테이션이 사용된 것을 확인할 수 있다. 따라서 IoC 컨테이너에 등록된 IEncoder 빈이 Base64라는 것을 확인할 수 있고 Encoder 객체에 자동으로 등록되는 객체가 Base64라는 것을 알 수 있다. 그런데 우리에게는 URL Encoder도 있고 이를 bean으로 등록할 것이다. 이때 IoC Container는 혼란스러워지는데 왜냐하면 IEncoder 객체가 2개나 등록됐기 때문이다. 따라서 이에 대한 에러코드를 띄울 것이다. 이에 대한 해결책 또한 스프링에서 제공한다.

URL encoder 코드

package com.example.ioc_study_with_spring;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

@Primary
@Component
public class UrlEncoder implements IEncoder {

    @Override
    public String encode(String message) {
        try {
            return URLEncoder.encode(message,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

URL Encoder를 살펴보면 @Primary라는 어노테이션이 붙어있다. 이 어노테이션은 여러 Bean이 등록됐을 때, 가장 우선적으로 매칭시켜주는 Bean이라는 뜻이다. 따라서 우리가 프로그램을 실행하면 우선적으로 URL Encoder가 주입될 것이다.

AOP 관점지향 프로그램

  • 스프링 어플리케이션은 대부분의 경우 MVC에서 Layered 설계를 사용한다. 주로 3 계층을 사용하는데 Application Layer, Business Layer, Infra Layer로 정의된다.

  • Application Layer : Client 중심의 로직을 적용하고 Client와 통신하기 위한 계층이다.

  • Business Layer : 내부 정책에 따른 Logic을 개발한다.

  • Infra Layer : 데이터베이스 및 외부와의 연동을 처리한다.

횡단 관심

=> AOP는 여러 계층에서 공통적으로 사용하는 부분을 구현할 때, 코드 반복을 막기 위해서 사용하는 방법이다.

주요 어노테이션

Annotation설명
@Aspect자바에서 널리 사용하는 AOP 프레임워크며 AOP를 정의하는 Class에 할당
@Pointcut기능을 어디다 적용시킬 지 설정
@Befor메소드 실행하기 이전
@After메소드가 성공적으로 실행된 후, 예외가 발생되더라도 실행
@AfterReturing메소드 호출 성공 시
@AfterThrowing메소드 실패 예와 발생
@AroundBefor After 모두 제어

AOP 사용 예제 1

ParameterAop 코드

package com.example.demo.Aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class ParameterAop {
    @Pointcut("execution(* com.example.demo.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 = "obj")
    public void afterReturn(JoinPoint joinPoint, Object obj){
        System.out.println("return obj : " + obj);
    }
}

다음 코드를 보면 @Aspect 어노테이션으로 시작하는 것을 볼 수 있다. @Aspect 어노테이션은 이 클래스가 AOP에 속하는 클래스라는 것을 나타낸다. @Pointcut 어노테이션은 이 Aop 클래스가 어느 범위내에서 활동할 지 제한한다. 이 부분에서 여러 공식이 있는데 어려우니 다음에 차후 Research 하도록 하겠다.

@Before @AfterReturning 어노테이션을 통해 메소드 실행 전후로 AOP 클래스가 실행되도록 할 수 있다.

AOP 사용 예제 2

TimerAop 코드

package com.example.demo.Aop;

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

@Aspect
@Component
public class TimerAop {
    @Pointcut("execution(* com.example.demo.Controller..*.*(..))")
    private void cut(){}

    @Pointcut("@annotation(com.example.demo.Annotation.Timer)")
    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());
    }
}

이 클래스에서는 @Around 어노테이션을 확인할 수 있다. 이 @Around 어노테이션은 메소드 실행 전 후 모두 컨드롤 할 수 있는 메서드로 주로 실행 시간을 측정할 때 많이 사용된다.

마무리

스프링 & 스프링 부트의 핵심 개념인 IoC와 AOP에 대해서 간단하게 알아봤다. 이 외에도 PSA와 POJO가 있으나 이는 차후 다시 포스팅할 계획이다.

profile
to be data engineer

0개의 댓글