1. AOP(Aspect Oriented Programming)

  • 횡단 관심사(Cross-cutting Concerns)

    • 어플리케이션 전반에 걸쳐 공통적으로 나타나는 로직
    • 동일한 기능을 하는 코드가 여러 모듈에서 반복적으로 나타나기에 흩어진 관심사라고도 함
    • 횡단 관심사를 통합하는 것으로 중복 코드를 제거하고 각 모듈이 핵심 로직에 집중 가능
      => 단일 책임 원칙(Single Responsibility Principle) 을 자연스럽게 적용 가능
  • 횡단 관심사를 상속 등의 기본적인 객체 지향 기법을 사용하여 분리하는 데는 한계가 있음

  • AOP는 보다 효율적으로 공통 로직을 여러 모듈에 적용하기 위한 프로그래밍 기법

 

 

2. Spring AOP의 특성

  • 대상 객체나 메소드를 감싸는 동적 프록시를 사용하여 구현
  • 프록시의 특성상 프록시 내부에서 메소드를 직접 호출할 경우 부가기능이 동작하지 않음
  • AOP는 Bean으로 등록된 객체에만 사용 가능
  • @Transactional, @Cacheable 등이 Spring이 지원하는 대표적인 AOP 서비스
  • 기본적으로 지원하는 AOP 서비스 외에도 사용자가 직접 AOP를 구현할 수도 있음

 

 

3. @Aspect

  • Spring에서 직접 AOP를 구현하기 위해 사용하는 어노테이션

  • 대상 클래스를 AOP 모듈로 사용할 것임을 명시

  • AOP 모듈로 사용하기 위해서는 Bean으로 등록되어야함(@Component 등 사용)

  • 공통 로직을 구현한 AOP 모듈 내의 메소드는 Advice라 함

     

  • Advice 실행 시점 지정

    • @Before : 대상 메소드의 실행 이전

    • @After : 대상 메소드의 실행 이후

    • @AfterReturning : 대상 메소드가 정상적으로 실행되어 반환된 이후

    • @AfterThrowing : 대상 메소드 실행중 Exception이 발생한 시점

    • @Around : 메소드 호출을 intercept하여 로직 실행 전후로 공통 로직을 수행 가능

       

  • AOP 적용 대상 지정

    • @Pointcut

      • @Pointcut("")의 형태로 AOP 적용 대상을 지정할 수 있음
        => Pointcut에서 사용되는 표현식을 PCD(Point Cut Designator)라 함

       

      • execution

        • 메소드의 반환 타입, 이름, 파라미터 등을 기준으로 대상을 지정

        • example

          • execution(pubilc * *(..)) : 모든 public 메소드
          • execution(int *(..)) : 반환 타입이 int인 모든 메소드
          • execution(* * (Long, int)) : 파라미터가 Long, int 인 모든 메소드

       

      • within

        • 특정 패키지, 클래스 내의 모든 메소드를 대상으로 지정

        • example

          • within(com.example.SampleProject.service.TestService)

       

      • this / target

        • 타입명을 사용하여 해당 클래스 또는 그 하위 클래스의 메소드를 대상으로 지정

        • this는 프록시 객체의 타입과, target은 실제 객체의 타입과 매칭

        • example

          • this(com.example.SampleProject.service.TestInterface)
          • target(com.example.SampleProject.service.TestInterfaceImpl)

       

      • args

        • 특정 이름의 매개변수를 받는 메소드를 대상으로 지정

        • Pointcut 메소드 또는 Advice 메소드에서 해당 변수를 매개변수로 받아야함

        • example

          • args(name)
          • public Object advice(ProceedingJoinPoint pj, String name) { ... }
          • String 타입의 name 변수를 가지는 메소드가 대상이 됨
          • 해당 변수의 값을 참조하는 것도 가능

       

      • @target

        • 특정 어노테이션을 가지는 모든 클래스와 그 하위 클래스의 메소드를 대상으로 지정

        • example

          • @target(org.springframework.stereotype.Repository)
            => @Repository가 붙은 모든 클래스와 그 하위 클래스의 메소드

       

      • @args

        • 특정 어노테이션을 가지는 객체를 파라미터로 받는 메소드를 대상으로 지정

        • 어노테이션은 Target ElementType이 Type이어야 함(클래스가 대상)

        • example

          • @args(javax.persistence.Entity)
            => @Entity 가 붙은 객체를 파라미터로 받는 메소드

       

      • @within

        • 특정 어노테이션을 가지는 클래스의 모든 메소드를 대상으로 지정

        • example

          • @within(org.springframework.stereotype.Service)
            => @Service 가 붙은 모든 클래스의 메소드를 대상으로 지정 (하위 클래스는 대상 외)

       

      • @annotation

        • 특정 어노테이션을 가지는 메소드를 대상으로 지정

        • example

          • @annotation(com.example.SampleProject.annotation.MyAnnotation)
            => @MyAnnotation이 붙은 모든 메소드를 대상으로 지정

     

    • PCD 는 @Pointcut 대신 Adivce 실행 시점 어노테이션(@Before, @After 등)에서도 사용 가능

     

  • 구현 절차

    • AOP 모듈로 사용할 클래스에 @Aspect, @Component 어노테이션 적용
    • Advice 메소드를 구현한 후 @Before, @After, @Around 등의 어노테이션을 사용하여
      실행 시점을 지정하고 PCD를 사용하여 Advice가 적용될 대상을 지정

     

  • 예시

    package com.example.SampleProject.service;
    
    import com.example.SampleProject.annotation.MyClassAnnotation;
    import com.example.SampleProject.annotation.MyMethodAnnotation;
    import com.example.SampleProject.domain.Category;
    import org.springframework.stereotype.Service;
    
    /* Test Service */
    @Service
    @MyClassAnnotation
    public class TestService implements TestInterface {
    
        // target 테스트
        public void doSomething(int i) {
            System.out.println("TargetTest: Do Something...");
        }
    
        // args 테스트
        public void doSomething(String name) {
            System.out.println("ArgsTest: Do Something...");
        }
    
        // @within 테스트
        public void doSomething(Long a, Long b) {
            System.out.println("@WithinTest: Do Something...");
        }
    
        // @args 테스트
        public void doSomething(Category category) {
            System.out.println("@ArgsTest: Do Something...");
        }
    
        // @annotation 테스트
        @MyMethodAnnotation
        public void doSomething(Boolean a) {
            System.out.println("@AnnotationTest: Do Something...");
        }
    }
    

     

    package com.example.SampleProject.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /* Test AOP */
    @Aspect
    @Component
    public class MyLogger {
        // Execution 테스트: 이름이 doSomething 인 모든 메소드가 각각 실행된 후에 실행
        @After("execution(* doSomething(..)) && within(com.example.SampleProject.service.TestService)")
        public void logAfterDoSomething() {
            System.out.println(">>> LogAfterDoSomething");
        }
    
        // Target 테스트:
        @Around("target(com.example.SampleProject.service.TestInterface)")
        public Object logging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            try {
                System.out.println("AOP 시작");
                return proceedingJoinPoint.proceed();
            } finally {
                System.out.println("AOP 끝\n");
            }
        }
    
        // Args 테스트: 인자로 String name 을 받는 메소드가 실행되기 전에 실행
        @Before("args(name) && within(com.example.SampleProject.service.TestService)")
        public void logBeforeArgs(String name) {
            System.out.println(">>> LogBeforeArgs: Parameter = " + name);
        }
    
        // @target 테스트: @MyClassAnnotation 이 붙은 클래스와 그 하위 클래스의 메소드가 실행된 후에 실행
        @After("@target(com.example.SampleProject.annotation.MyClassAnnotation) && within(com.example.SampleProject.service.TestService)")
        public void logAfterTargetAnnotation() {
            System.out.println(">>> LogAfterTargetAnnotation");
        }
    
        // @within 테스트: @MyClassAnnotation 이 붙은 클래스의 메소드 실행 전에 실행
        @Before("@within(com.example.SampleProject.annotation.MyClassAnnotation)")
        public void logBeforeWithinAnnotation() {
            System.out.println(">>> LogBeforeWithinAnnotation");
        }
    
        // @args 테스트: 인자로 @Entity 어노테이션이 붙은 클래스의 객체를 받는 메소드가 정상적으로 반환되는 시점에 실행
        @AfterReturning("@args(javax.persistence.Entity) && within(com.example.SampleProject.service.TestService)")
        public void logAfterReturingArgs() {
            System.out.println(">>> logAfterReturingArgs");
        }
    
        // @annotation 테스트: @MyMethodAnnotation 이 붙은 메소드가 실행되기 전에 실행
        @Before("@annotation(com.example.SampleProject.annotation.MyMethodAnnotation)")
        public void logBeforeAnnotation() {
            System.out.println(">>> LogBeforeAnnotation");
        }
    
    }
    
    

     

    package com.example.SampleProject;
    
    import com.example.SampleProject.domain.Category;
    import com.example.SampleProject.service.TestService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    /* Test Main */
    @SpringBootTest
    class SampleProjectApplicationTests {
    	private final TestService testService;
    
    	@Autowired
    	public SampleProjectApplicationTests(
    			TestService testService
    	) {
    		this.testService = testService;
    	}
    
    	@Test
    	void contextLoads() {
    
    		// target 테스트
    		testService.doSomething(0);
    
    		// args 테스트
    		testService.doSomething("name");
    
    		// @args 테스트
    		testService.doSomething(new Category());
    
    		// @within 테스트
    		testService.doSomething(0L, 0L);
    
    		// @annotation 테스트
    		testService.doSomething(true);
    
    	}
    }
    
    

     

    === 실행결과 ===
    AOP 시작
    >>> LogBeforeWithinAnnotation
    TargetTest: Do Something...
    >>> LogAfterTargetAnnotation
    >>> LogAfterDoSomething
    AOP 끝
    
    AOP 시작
    >>> LogBeforeWithinAnnotation
    >>> LogBeforeArgs: Parameter = name
    ArgsTest: Do Something...
    >>> LogAfterTargetAnnotation
    >>> LogAfterDoSomething
    AOP 끝
    
    AOP 시작
    >>> LogBeforeWithinAnnotation
    @ArgsTest: Do Something...
    >>> logAfterReturingArgs
    >>> LogAfterTargetAnnotation
    >>> LogAfterDoSomething
    AOP 끝
    
    AOP 시작
    >>> LogBeforeWithinAnnotation
    @WithinTest: Do Something...
    >>> LogAfterTargetAnnotation
    >>> LogAfterDoSomething
    AOP 끝
    
    AOP 시작
    >>> LogBeforeWithinAnnotation
    >>> LogBeforeAnnotation
    @AnnotationTest: Do Something...
    >>> LogAfterTargetAnnotation
    >>> LogAfterDoSomething
    AOP 끝
    
    • within(com.example.SampleProject.service.TestService) 와 결합하여 범위 한정
      => AOP의 대상을 너무 넓게 잡을 경우 의도치 않은 대상에게도 AOP를 적용하게됨

       

  • 하나의 대상에 적용되는 Advice 의 실행 순서

    1. @Around 실행 전 코드

    2. @Before 코드

    3. === 메소드 실행 ===

    4. @AfterReturning 코드

    5. @After 코드

    6. @Around 실행 후 코드

       

  • @Order를 사용한 실행순서 제어

    • 하나의 Advice 내에서의 실행 순서는 제어가 불가능(기본 우선순위를 따름)
    • 같은 대상에 적용될 Advice간에 임의의 순서를 지정해야한다면 다른 Aspect로 분리해야함
    • 다른 Aspect간에는 @Order를 사용하여 우선순위를 지정해줄 수 있음
    • Order안의 값이 높을수록 메소드 실행 이전에는 먼저 실행되고 실행 이후에는 나중에 실행됨
      => Order의 우선순위가 높을수록 보다 바깥의 Proxy가 되기 때문

 

  • AOP의 작동 방식

    • JDK Dynamic Proxy

      • 코드상에는 Proxy가 나타나지 않지만 Runtime에 동적으로 프록시를 생성

      • 대상과 같은 인터페이스를 구현한 Proxy 클래스를 사용

      • 인터페이스를 대상으로만 사용 가능한 방식

         

    • CGLib(Code Generation Library)

      • JDK Dynamic Proxy와 마찬가지로 Runtime에 동적으로 Advice를 적용

      • 대상을 상속하여 오버라이딩한 Proxy 클래스를 사용

      • 인터페이스가 아닌 클래스를 대상으로도 사용 가능

      • final, private 클래스는 상속이 불가능하기에 대상으로 할 수 없음

         

    • Spring에서는 기본적으로 타겟이 되는 클래스가 인터페이스를 구현하고있다면
      JDK Dynamic Proxy 방식을, 구현하고 있지 않다면 CGLib를 사용하도록 설정되어있음

       

    • aop config 파일을 작성하여 특정 방식을 강제할 수도 있음

 

'프레임워크 > Spring' 카테고리의 다른 글

#7 Spring 의 구조  (0) 2022.05.03
#6 PSA(Portable Service Abstraction)  (0) 2022.05.02
#4 Spring 의존성 주입 방법  (0) 2022.04.27
#3 Spring에 적용된 디자인 패턴  (0) 2022.04.24
#2 Spring Bean  (0) 2022.04.22

1. @Autowired

  • @Autowired 어노테이션을 사용하지 않을 때의 의존성 주입

    <!-- XML 설정파일 -->
    <!-- 생성자를 통한 의존성 주입 -->
    <bean id="itemRepository" class="com.example.SampleProject.service.ItemService">
        <constructor-arg ref="itemRepository"/>
    </bean>
    
    <!-- Setter를 통한 의존성 주입 -->
    <bean id="itemRepository" class="com.example.Sample.service.ItemService">
        <property name="itemRepository" ref="itemRepository"/>
    </bean>
    
    • XML 파일에 bean을 주입받을 클래스를 태그를 사용하여 등록

    • bean을 주입받을 클래스는 생성자와 Setter중 어느쪽을 통해 주입받을지 선택

       

  • @Autowired 사용시 의존성 주입

    • 의존성을 주입받아야할 부분에 @Autowired 어노테이션 사용
    • Spring은 bean 컨테이너에서 매칭되는 타입의 bean을 찾아 자동으로 의존성을 주입

     

  • @Qualifier

    • 같은 타입의 bean이 복수 존재할 경우 어느것을 주입받을지 명시하기 위한 어노테이션
    • @Autowired와 함께 @Qualifier('<bean이름>') 을 붙여주면 해당 bean을 주입받을 수 있음
    • 같은 타입의 bean이 여러개일 때 Qualifier를 설정하지 않으면 에러가 발생
    • 해당 타입의 bean이 유일하게 존재하더라도 Qualifier와 매칭되지 않으면 주입받을 수 없음

 

 

2. 세 가지 주입 방식(@Autowired 사용 기준)

  • 생성자를 통한 주입

    package com.example.SampleProject.service;
    
    public class ItemService {
        private final ItemRepository itemRepository;
    
        @Autowired
        public ItemService(ItemRepository itemRepository) {
            this.itemRepository = itemRepository;
        }
    }
    
    • 의존성을 주입받을 필드를 선언

    • bean을 인자로 받아 필드를 초기화하는 생성자를 정의

    • 생성자에 @Autowired 를 사용

    • 생성자가 하나뿐일 경우 @Autowired는 생략 가능

    • 초기화 과정을 거치기 때문에 의존성을 주입받을 필드를 final로 선언할 수 있음

    • 생성 이후에 다른 의존성을 주입하는 것은 불가능

       

  • Setter를 통한 주입

    package com.example.SampleProject.service;
    
    public class ItemService {
        private ItemRepository itemRepository;
    
        @Autowired
        public setItemService(ItemRepository itemRepository) {
            this.itemRepository = itemRepository;
        }
    }
    
    • 생성자 주입과 유사한 구조이나 생성자 대신 Setter를 사용

    • 생성과 동시에 초기화되는 것이 아니기 때문에 final 사용 불가

    • 생성 이후에도 Setter를 사용하여 다른 의존성 주입 가능

       

  • 필드에 직접 주입

    package com.example.SampleProject.service;
    
    public class ItemService {
        @Autowired
        private ItemRepository itemRepository;
    
    }
    
    • 필드에 직접 @Autowired를 붙여줘도 의존성 주입 가능

    • 생성과 동시에 초기화되는 것이 아니기 때문에 final 사용 불가

    • 필드 주입을 지원하는 프레임워크의 존재에 의존적

    • 외부에서 필드에 접근 불가

       

  • Spring에서는 생성자를 통한 주입을 사용할 것을 권장

     

  • 생성자 주입을 사용해야하는 이유?

    • Compile Time에서의 순환 참조 검출

      • 필드 주입, Setter주입의 경우 객체 생성시에 바로 의존성을 주입하지 않음
        => 순환 참조가 발생하는 클래스에 의존하더라도 compile time에서는 에러가 발생하지 않음

         

      • 생성자 주입의 경우 객체 생성 시점에서 의존성을 주입
        => 객체가 생성되는 순간 순환 참조 에러가 검출, compile time에 에러를 발견할 수 있음

         

    • Immutable한 객체 보장 가능

      • 생성자 주입에서는 final을 사용하여 주입받은 객체가 불변임을 보장할 수 있음

      • 생성시에 주입받은 객체가 변경되어야 하는 경우는 극히 드물기 때문에 유용

         

    • 편리한 테스트코드 작성

      • 필드 주입과 달리 생성자는 생성시에 한해 프레임워크의 도움 없이 직접 의존성 주입 가능
      • 테스트용 Mock 클래스를 사용하여 생성자를 호출하는 것으로 간단하게 테스트 코드 작성 가능

 

 

3. @Resource

  • Java 표준이 지원하는 의존성 주입용 어노테이션

  • @Autowired 와 달리 Spring 프레임워크를 사용하지 않아도 사용 가능

     

  • Bean 탐색 순서의 차이

    • @Autowired

      • Type이 일치하는 Bean을 먼저 탐색

      • 없을 경우 에러 발생

      • Type이 일치하는 Bean이 하나라면 바로 주입

      • Type이 일치하는 Bean이 복수 존재할 경우 id로 구분

      • id가 매칭되는 Bean이 하나라면 바로 주입

      • id로도 하나의 Bean을 특정할 수 없을 경우 에러 발생

      • @Qualifier를 통해 id를 지정 가능

         

    • @Resource

      • @Autowired와 유사하나 Type과 id의 우선순위가 역전
      • 어노테이션의 name 속성(attribute)값으로 id 지정 가능

'프레임워크 > Spring' 카테고리의 다른 글

#6 PSA(Portable Service Abstraction)  (0) 2022.05.02
#5 Spring AOP(Aspect Oriented Programming)  (0) 2022.04.30
#3 Spring에 적용된 디자인 패턴  (0) 2022.04.24
#2 Spring Bean  (0) 2022.04.22
#1 Spring Boot  (0) 2022.04.21

1. 어댑터 패턴(Adapter Pattern)

  • 어댑터(변환기)를 사용하여 어떤 클래스의 인터페이스를 클라이언트가 사용하기 위한 인터페이스로 변환

  • 클래스간 인터페이스 호환 문제를 간단하게 해결 가능

  • 사용할 클래스의 인터페이스가 변경되어도 클라이언트는 기존의 인터페이스를 그대로 사용 가능

     

  • 객체 어댑터(Object Adapter)

    // 클라이언트 인터페이스
    interface ClientInterface {
        int versionNumber();
        String informationString();
    }
    
    // 클라이언트 인터페이스로 조작하려는 인터페이스
    interface TargetInterface {
        String introduce();
        int version();
    }
    
    // TargetInterface를 구현하는 클래스들
    class TargetInterfaceImpl1 implements TargetInterface {
        
        public String introduce() {
            return "this is TargetInterfaceImpl1";
        }
        
        public int version() {
            return 1;
        }
    }
    
    class TargetInterfaceImpl2 implements TargetInterface {
        
        public String introduce() {
            return "this is TargetInterfaceImpl2";
        }
        
        public int version() {
            return 2;
        }
    }
    
    // TargetInterface의 구현체들을 ClientInterface로 사용 가능하도록
    // 변환해주는 어댑터 클래스
    class AdapterClass implements ClientInterface {
        private final TargetInterface targetInterface;
    
        public AdapterClass(TargetInterface targetInterface) {
            this.targetInterface = targetInterface;
        }
    
        public int versionNumber() {
            return this.targetInterface.version();
        }
    
        public String informationString() {
            return this.targetInterface.introduce();
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            ClientInterface clientInterface;
    
            // TargetInterfaceImpl1을 clientInterface로 조작
            // result:
            // 1
            // this is TargetInterfaceImpl1
            clientInterface= new AdapterClass(new TargetInterfaceImpl1());
            System.out.println(clientInterface.versionNumber());
            System.out.println(clientInterface.informationString());
    
            // TargetInterfaceImpl2를 clientInterface로 조작
            // result:
            // 2
            // this is TargetInterfaceImpl2
            clientInterface= new AdapterClass(new TargetInterfaceImpl2());
            System.out.println(clientInterface.versionNumber());
            System.out.println(clientInterface.informationString());
        }
    }
    
    • 어댑터 클래스가 클라이언트 인터페이스를 상속한 뒤 구현부에서 타겟 인터페이스의 구현체를 사용

       

  • 클래스 어댑터(Class Adapter)

    • 어댑터 클래스가 클라이언트 인터페이스와 타겟 인터페이스 모두를 구현하도록 하는 방식

    • Java의 경우 다중상속이 허용되지 않기 때문에 구현 불가능

       

  • Spring에서는 Spring Integration이 대표적인 예시이다. Spring Integration은 어댑터 패턴을 사용하여 Spring 프레임워크 내부와 외부 시스템의 통합을 지원한다.

 

 

2. 프록시 패턴(Proxy Pattern)

  • 특정 작업을 Proxy, 즉 대리자가 대신 수행해주는 패턴

  • Proxy는 실제로 호출할 서비스와 동일한 인터페이스를 구현

  • Proxy의 특정 메소드를 호출할 경우 실제 서비스의 해당 메소드를 호출한 결과를 반환

  • 실제 서비스의 메소드 호출 전후로 별도의 로직을 수행하는 경우도 있음

     

  • 예시

    // 학생의 정보를 저장하고 id로 조회할 수 있는 서비스
    interface SchoolService {
        String findById(Long id);
        String save(String studentInfo);
    }
    
    // SchoolService를 구현한 클래스
    class SchoolServiceImpl implements SchoolService {
        
        public String findById(Long id) {
            return "student[id] Dto";
        }
        
        public String save(String studentInfo) {
            return "inserted student Dto";
        }
    }
    
    // 학생용 Proxy
    class StudentProxy implements SchoolService {
        SchoolService schoolService;
    
        public StudentProxy(SchoolService schoolService) {
            this.schoolService = schoolService;
        }
    
        public String findById(Long id) {
            return this.schoolService.findById(id);
        }
    
        // 학생이기 때문에 학생정보 삽입은 허가되지 않음
        public String save(String studentInfo) {
            return "Permission Denied";
        }
    }
    
    // 강사용 Proxy
    class TeacherProxy implements SchoolService {
        SchoolService schoolService;
    
        public TeacherProxy(SchoolService schoolService) {
            this.schoolService = schoolService;
        }
    
        public String findById(Long id) {
            return this.schoolService.findById(id);
        }
    
        // 강사는 학생정보 삽입이 가능
        public String save(String studentInfo) {
            return this.schoolService.save(studentInfo);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            SchoolService service;
    
            // 학생 프록시
            // result:
            // student[id] Dto
            // Permission Denied
            service = new StudentProxy(new SchoolServiceImpl());
            System.out.println(service.findById(3L));
            System.out.println(service.save("info"));
    
            // 강사 프록시
            // result:
            // student[id] Dto
            // inserted student Dto
            service = new TeacherProxy(new SchoolServiceImpl());
            System.out.println(service.findById(3L));
            System.out.println(service.save("info"));
    
        }
    }
    
    
    
    • 실제 서비스인 SchoolServiceImpl에 직접 접근하지 않고 StudentProxy와 TeacherProxy를 통해 접근

    • TeacherProxy를 통해서만 학생 데이터 추가 작업을 수행할 수 있도록 설정

       

  • Proxy의 유형

    • 보호 프록시(Protection Proxy)

      • 실제 객체에 대한 접근을 권한에 따라 제한하기 위해 사용되는 프록시

      • 실제 객체의 public, protected 등의 메소드도 프록시를 거치는 것으로 숨길 수 있음

      • 위의 예시가 이에 해당

         

    • 원격 프록시(Remote Proxy)

      • 원격 객체를 대신하여 원격 객체가 로컬에 존재하는 것 처럼 서비스하기 위한 프록시

         

    • 가상 프록시(Virtual Proxy)

      • 아직 생성되지 않은 객체가 존재하는 것 처럼 보여주기 위한 프록시

      • 실제 객체의 생성을 필요한 순간까지 지연

      • 리소스의 요구량이 많은 작업을 필요한 순간까지 미루고 리소스가 적게드는 작업만 프록시가 처리하여 보여주도록 작업을 분산할 수 있음

         

    • 이 외에도 다양한 유형의 프록시가 존재

       

  • 단점

    • 실제 객체에 접근시 프록시 생성의 오버헤드가 존재

    • 로직이 복잡해지고 가독성이 떨어질 수 있음

    • 프록시 클래스 내에 중복이 발생

       

  • Spring에서는 Java에서 지원되는 Dynamic Proxy를 사용하여 동적으로 프록시를 생성

     

  • Spring에서의 Proxy 패턴의 대표적인 예시는 어노테이션을 통한 AOP(Aspect Oriented Programming)

    • ex) @Transactional 을 사용하면 메소드 호출 전후로 적절한 처리를 하여 메소드를 트랜잭션화

 

 

3. 데코레이터 패턴(Decorator Pattern)

  • 기본적인 구조는 프록시 패턴과 동일

  • 프록시는 서비스 호출 전후로 별도의 처리를 하더라도 서비스 호출 결과 자체는 변화없이 그대로 반환

  • 그에 반해 데코레이터는 서비스 호출의 결과 자체에 변화를 가한 뒤 반환

     

  • 예시

    import java.util.List;
    import java.time.LocalDateTime;
    
    // 통계분석 결과를 반환하는 result 함수가 정의된 인터페이스
    interface StatisticsService {
        String result(List<Integer> seq);
    }
    
    // StatisticsService를 구현한 실제 서비스
    class StatisticsServiceImpl implements StatisticsService {
    
        public String result(List<Integer> seq) {
            int sum_val = 0;
            int cnt_val = seq.size();
            for(int num: seq) {
                sum_val += num;
                cnt_val += 1;
            }
            return String.format("==== Result ====\ncount: %d\ntotal: %d\naverage: %d", cnt_val, sum_val, sum_val / cnt_val);
        }
    }
    
    // StatisticsService를 구현한 데코레이터
    // 실제 서비스의 결과에 현재 Datetime을 덧붙여 반환
    class TimeStampDecorator implements StatisticsService {
    
        StatisticsService statisticsService;
    
        public TimeStampDecorator(StatisticsService statisticsService) {
            this.statisticsService = statisticsService;
        }
    
        public String result(List<Integer> seq) {
            String result = this.statisticsService.result(seq);
            result += String.format("\n\n==== Datetime ====\n%s", LocalDateTime.now());
            return result;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            StatisticsServiceImpl statisticsService = new StatisticsServiceImpl();
            TimeStampDecorator timeStampDecorator = new TimeStampDecorator(statisticsService);
    
            List<Integer> seq = List.of(4, 5, 8, 6, 2, 5, 9);
            
            // 데코레이터를 통해 실제 서비스를 호출
            // result:
            // ==== Result ====
            // count: 14
            // total: 39
            // average: 2
            //
            // ==== Datetime ====
            // 2022-04-24T16:06:34.714910300
            System.out.println(timeStampDecorator.result(seq));
        }
    }
    
    • 프록시와 완벽하게 동일한 구조를 가짐

    • 프록시와는 달리 결과값에 변화를 주어 반환

    • 실제 서비스 호출 결과에 장식(Decoration)을 붙이기 위해 사용 가능

       

  • 프록시 패턴과 거의 동일한 구조이기에 장/단점 또한 공유

 

 

4. 싱글톤 패턴(Singleton Pattern)

  • 특정 객체의 인스턴스를 단 하나만 만들어 사용하는 디자인 패턴

  • 싱글톤 패턴에 따라 설계된 객체는 처음에 하나의 인스턴스만을 생성하여 이를 재사용

  • 만들어진 인스턴스는 static 영역에서 관리되기 때문에 클래스간에 공유 가능

  • 처음에 만든 인스턴스를 재사용하기 때문에 메모리/시간 면에서 효율적

  • 의미상 여러개의 인스턴스가 필요하지 않은 객체들이 하나의 인스턴스만을 가짐을 보장 가능

     

  • 예시

    class SharingCounter {
        static SharingCounter sharingCounter;
        private int count;
    
        // 생성자를 private로 선언하여 외부에서 new를 사용할 수 없도록 함
        private SharingCounter() {
            this.count = 0;
        }
    
        // 외부에서 인스턴스를 얻기위해 호출해야할 메소드
        public static SharingCounter getInstance() {
            if (sharingCounter == null) {
                sharingCounter = new SharingCounter();
            }
            return sharingCounter;
        }
    
        // count를 1 증가시키는 메소드
        public void increase() {
            this.count += 1;
        }
    
        // count값을 반환하는 메소드
        public int getCount() {
            return this.count;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
    
            // SharingCounter 인스턴스를 획득한 뒤 count값을 받아와 출력
            // result:
            // 0
            SharingCounter cnt = SharingCounter.getInstance();
            System.out.println(cnt.getCount());
    
            // count값 1 증가
            cnt.increase();
    
            // SharingCounter 인스턴스를 다시 획득한 뒤 count값을 받아와 출력
            // 인스턴스를 한번만 생성하고 재사용하기 때문에 이전에 증가시켜둔
            // count 값이 유지되고있는 것을 확인할 수 있음
            // result:
            // 1
            cnt = SharingCounter.getInstance();
            System.out.println(cnt.getCount());
        }
    }
    
    • 인스턴스가 처음에 단 한번만 생성되고 그 후 getInstance 를 아무리 호출해도 같은 인스턴스를 반환

       

  • Singleton 객체의 데이터는 결국 공유 가능한 데이터이기 때문에 동시성 문제를 고려한 설계가 필요

     

  • Singleton 객체의 책임이 너무 커져 이를 공유하는 객체들이 늘어나면 객체간 결합도가 높아져 객체지향의 원칙에 위배하게됨에 주의

     

  • 동시성 문제나 결합도 문제를 생각하여 쓰기 가능한 속성(writable attribute)을 가지지 않도록 설계하는게 일반적

     

  • Spring에서는 Bean으로 등록된 객체들을 기본적으로 싱글톤으로 관리하는 방식으로 싱글톤 패턴을 적용

    • 쓰기 가능한 속성을 가지는 객체를 Singleton Bean으로 등록하는 것은 지양해야함

    • 별도로 @Scope 어노테이션을 사용하여 Prototype Bean으로 등록할 경우 해당 Bean은 싱글톤이 아니게되어 주입받을 때 마다 새로 인스턴스를 생성

       

  • 단독으로 적용할 경우 객체지향의 원칙에 위배될 수 있는 위험이 많은 패턴이므로 적용시 주의 필요

 

 

5. 템플릿 메소드 패턴(Template Method Pattern)

  • 변하지 않는 기능(공통 부분)은 상위 클래스에서, 자주 변경되고 확장될 기능은 하위 클래스에서 구현하도록 하는 디자인 패턴

     

  • 예시

    // 상위 클래스 Person
    abstract class Person {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        // 공통부분은 상위 클래스에서 정의(My name is ~)
        public void introduce() {
            System.out.printf("My name is %s, and I'm %d years old\n", name, age);
            giveExtraInfo();
        }
    
        // 변경/확장이 일어날 부분은 하위클래스에게 구현하도록 함
        abstract void giveExtraInfo();
    }
    
    class Student extends Person {
    
        public Student(String name, int age) {
            super(name, age);
        }
    
        // 학생은 자신을 학생으로 소개
        void giveExtraInfo() {
            System.out.println("I am Student");
        }
    }
    
    class Teacher extends Person {
    
        public Teacher(String name, int age) {
            super(name, age);
        }
    
        // 강사는 자신을 강사로 소개
        void giveExtraInfo() {
            System.out.println("I am Teacher");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Person student = new Student("David", 21);
            Person teacher = new Teacher("Sam", 35);
    
            // 학생과 강사의 자기소개
            // result:
            // My name is David, and I'm 21 years old
            // I am Student
            // My name is Sam, and I'm 35 years old
            // I am Teacher
            student.introduce();
            teacher.introduce();
        }
    }
    
    • 추상클래스인 Person을 상속한 학생과 강사가 공통부분을 제외한 부분만을 오버라이딩

    • 코드의 중복을 줄이고 하위 클래스는 하위 클래스의 로직에만 집중 가능

    • 구현해야할 abstract method가 너무 많을 경우 사용하기가 복잡해짐

       

  • Spring에서는 대표적으로 Dispatcher Servlet이 이 패턴을 사용하여 구현되어있음

    • FrameworkServlet에서 공통로직 처리 후 추상메소드 doService 메소드를 호출
    • DispatcherServlet는 FrameworkService를 상속하여 doService를 구현

 

6. 팩토리 메소드 패턴(Factory Method Pattern)

  • 객체를 생성하는 로직의 구현을 하위 클래스에게 위임하는 방식

     

  • 예시

    // Item Factory
    abstract class ItemFactory {
        abstract Item getItem();
    }
    
    // Item
    abstract class Item {
        abstract String info();
    }
    
    // ItemFactory 와 Item 을 상속한 클래스를 두 개씩 생성
    class WeaponFactory extends ItemFactory {
        public Item getItem() {
            return new Weapon();
        }
    }
    class Weapon extends Item {
        public String info() {
            return "This is Weapon";
        }
    }
    
    class ArmorFactory extends ItemFactory {
        public Item getItem() {
            return new Armor();
        }
    }
    class Armor extends Item {
        public String info() {
            return "This is Armor";
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 무기 factory 와 방어구 factory 생성
            ItemFactory weaponFactory = new WeaponFactory();
            ItemFactory armorFactory = new ArmorFactory();
    
            // 각 factory 로부터 아이템 획득
            Item fromWeaponFactory = weaponFactory.getItem();
            Item fromArmorFactory = armorFactory.getItem();
    
            // result:
            // This is Weapon
            // This is Armor
            System.out.println(fromWeaponFactory.info());
            System.out.println(fromArmorFactory.info());
        }
    }
    
    • WeaponFactory와 ArmorFactory의 getItem 메소드를 통해 객체를 획득

    • 서브클래스에 변경사항이 생기더라도 다른곳에는 수정사항이 발생하지 않음(결합도 감소)

    • 확장에는 열려있고 수정에는 닫혀있음(Open-Closed Principle)

       

  • Spring에서 팩토리 패턴이 사용된 대표적 예시로 BeanFactory 가 있음

    • 일반적으로 직접 호출할 일은 없지만 BeanFactory 의 getBean 메소드를 통해 Bean 객체 생성 가능
    • Bean으로 등록된 객체들중 요청받은 Bean을 생성하여 반환
    • 이 때, Singleton 으로 설계된 Bean의 경우 처음 생성한 객체를 다시 반환

 

 

7. 전략 패턴(Strategy Pattern)

  • 로직에서 공통되는 부분만 구현해두고 변하는 부분은 외부로부터 주입받아 사용하는 디자인 패턴

  • 변하는 부분만을 별도로 구현한다는 점에서 템플릿 메소드 패턴과 유사

     

  • 예시

    import java.util.List;
    
    // 전략 메소드를 가진 전략 인터페이스
    interface SelectStrategy {
        int select(List<Integer> candidate);
    }
    
    // 전략 메소드를 구현한 객체들
    // 가장 큰 숫자를 고르는 전략 객체
    class MaxValueStrategy implements SelectStrategy {
        public int select(List<Integer> candidate) {
            int result = Integer.MIN_VALUE;
            for(int item: candidate) {
                if (item > result) {
                    result = item;
                }
            }
            return result;
        }
    }
    
    // 절댓값이 가장 작은 숫자를 고르는 전략 객체
    class MinAbsStrategy implements SelectStrategy {
        public int select(List<Integer> candidate) {
            int result = Integer.MAX_VALUE;
            for(int item: candidate) {
                if (Math.abs(item) < Math.abs(result)) {
                    result = item;
                }
            }
            return result;
        }
    }
    
    // 전략 객체를 사용할 Context
    class Selector {
        public void selectNumber(List<Integer> numbers, SelectStrategy strategy) {
            System.out.println("숫자를 하나 뽑겠습니다.");
            System.out.printf("뽑힌 숫자는 %d 입니다.", strategy.select(numbers));
            System.out.println("감사합니다.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // Selector 를 생성
            Selector s = new Selector();
    
            // 숫자 후보군 리스트를 생성
            List<Integer> numbers = List.of(-20, -7, 1, 15, 67);
    
            // 절댓값이 가장 작은 수를 선택하는 전략을 주입하여 selectNumber 실행
            s.selectNumber(numbers, new MinAbsStrategy());
    
            // 가장 큰 수를 선택하는 전략을 주입하여 selectNumber 실행
            s.selectNumber(numbers, new MaxValueStrategy());
        }
    }
    
    • 공통 부분은 전략 객체를 사용할 컨텍스트 객체인 Selector에서 구현
    • 로직이 변하는 부분은 SelectStrategy를 구현한 전략 객체를 주입받아 작업 수행
    • 주입할 전략을 변경하는 것 만으로 메소드의 로직을 동적으로 변경/확장할 수 있음

 

 

8. Spring의 전략 패턴

  • Spring 에서는 의존성 주입(DI)과 팩토리 패턴을 사용하여 전략 패턴을 적용

     

  • 예시

    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    // 전략 메소드를 가진 전략 인터페이스
    interface SelectStrategy {
        int select(List<Integer> candidate);
        SelectStrategyType getSelectStrategyType();
    }
    
    // 전략을 구분하기 위한 key 값을 정의한 Enum 클래스
    enum SelectStrategyType {
        MIN_ABS,
        MAX_VALUE
    }
    
    // 전략 메소드를 구현한 객체들
    // 가장 큰 숫자를 고르는 전략 객체
    // @Component
    class MaxValueStrategy implements SelectStrategy {
        public int select(List<Integer> candidate) {
            int result = Integer.MIN_VALUE;
            for(int item: candidate) {
                if (item > result) {
                    result = item;
                }
            }
            return result;
        }
    
        public SelectStrategyType getSelectStrategyType() {
            return SelectStrategyType.MAX_VALUE;
        }
    }
    
    // 절댓값이 가장 작은 숫자를 고르는 전략 객체
    // @Component
    class MinAbsStrategy implements SelectStrategy {
        public int select(List<Integer> candidate) {
            int result = Integer.MAX_VALUE;
            for(int item: candidate) {
                if (Math.abs(item) < Math.abs(result)) {
                    result = item;
                }
            }
            return result;
        }
    
        public SelectStrategyType getSelectStrategyType() {
            return SelectStrategyType.MIN_ABS;
        }
    }
    
    // 전략 객체를 생성해줄 Factory 클래스
    // @Component
    class SelectStrategyFactory {
        private final Map<SelectStrategyType, SelectStrategy> selectStrategySet;
    
        // @Autowired
        public SelectStrategyFactory(Set<SelectStrategy> selectStrategies) {
            this.selectStrategySet = new HashMap<>();
            for(SelectStrategy strategy: selectStrategies) {
                this.selectStrategySet.put(strategy.getSelectStrategyType(), strategy);
            }
        }
    
        public SelectStrategy getSelectStrategy(SelectStrategyType selectStrategyType) {
            return selectStrategySet.get(selectStrategyType);
        }
    }
    
    // 전략 객체를 사용할 Context
    class Selector {
        private SelectStrategyFactory selectStrategyFactory;
    
        // @Autowired
        public Selector(SelectStrategyFactory selectStrategyFactory) {
            this.selectStrategyFactory = selectStrategyFactory;
        }
    
        public void selectNumber(List<Integer> numbers, SelectStrategyType strategyType) {
            SelectStrategy strategy = this.selectStrategyFactory.getSelectStrategy(strategyType);
            System.out.println("숫자를 하나 뽑겠습니다.");
            System.out.printf("뽑힌 숫자는 %d 입니다.", strategy.select(numbers));
            System.out.println("감사합니다.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 실제로는 Spring Framework 에서 지원하는 Dependency Injection 으로 해결될 부분
            Selector selector = new Selector(new SelectStrategyFactory(Set.of(new MaxValueStrategy(), new MinAbsStrategy())));
    
            // 숫자 후보군 리스트를 생성
            List<Integer> numbers = List.of(-20, -7, 1, 15, 67);
    
            selector.selectNumber(numbers, SelectStrategyType.MAX_VALUE);
            selector.selectNumber(numbers, SelectStrategyType.MIN_ABS);
        }
    }
    
    • SelectStrategyType 열거체의 값으로 전략을 구분

    • SelectStrategyFactory는 각 타입에 해당하는 전략 객체를 반환하는 getSelectStrategy 메소드를 구현

    • Selector 는 SelectStrategyType를 주입받아 Factory로부터 그에 해당하는 전략객체를 획득/사용

    • main에서 Selector를 생성하는 과정은 실제 Spring에서는 의존성 주입(DI) 방식으로 이루어짐

    • 예시 코드는 Spring Framework를 적용하지 않은 상태이기에 편의상 위와 같이 작성

       

  • Spring에 적용된 Strategy Pattern

    • @GeneratedValue

      • 주로 엔티티 작성시 Id값을 자동생성하기 위해서 사용되는 어노테이션

      • GeneratedValue(strategy = GenerationType.IDENTITY) 와 같이 값 생성을 위한 전략 주입

         

    • @ManytoOne, @OneToMany, @OnetoOne, @ManytoMany

      • 엔티티간 관계 매핑을 위해 사용되는 어노테이션
      • @ManytoOne(fetch = FetchType.LAZY) 와 같이 연관 엔티티를 불러올 타이밍에 대한 전략을 주입

 

'프레임워크 > Spring' 카테고리의 다른 글

#5 Spring AOP(Aspect Oriented Programming)  (0) 2022.04.30
#4 Spring 의존성 주입 방법  (0) 2022.04.27
#2 Spring Bean  (0) 2022.04.22
#1 Spring Boot  (0) 2022.04.21
#0 Spring  (0) 2022.04.20

1. Bean 이란?

  • Spring에서 사용되는 POJO(Plain Old Java Object)의 일종
  • Spring의 IoC 컨테이너가 관리하는 자바 객체
  • Bean으로 등록한 객체는 IoC 컨테이너가 의존성을 주입해줄 수 있음
  • @Controller, @Service, @Repository, @Component 등의 어노테이션이 붙은 클래스는 자동으로 Bean으로 등록
  • @Configuration의 경우 내부에서 @Component를 사용

 

 

2. 특정 객체를 Bean으로 등록하는 방법

    • @Component - 클래스를 Bean으로 등록
      @Component
      public class MyBeanClass {
      
      	...
          
      }
      • @SpringBootApplication내의 @ComponentScan이 처리될 때 자동으로 대상 클래스들을 Bean으로 등록
      • 클래스 레벨의 어노테이션이기 때문에 클래스에만 사용 가능

    • @Bean - 메소드의 반환 객체를 Bean으로 등록
      @Configuration
      public class SpringConfiguration {
      	@Bean
          public MyBeanClass myBeanClass {
          	return new MyBeanClass();
          }
      }
      • @Configuration 어노테이션이 붙은 설정 클래스에서 @Bean 어노테이션을 사용하여 Bean 등록 가능
      • @Configuration 클래스를 Component Scanning 하면서 @Bean 메소드의 반환값들이 Bean으로 등록됨
      • 특정 객체를 반환하는 메소드에 @Bean을 붙이면 반환되는 인스턴스가 Bean으로 등록

  • @Component vs @Bean
    • @Component는 클래스 레벨에서, @Bean은 메소드 레벨에서 사용되는 어노테이션
    • @Bean은 @Configuration 클래스 내에서 사용해야만 Bean 등록이 가능

    • @Bean을 사용해야하는 경우?
      • 외부 라이브러리 등에 정의된 객체이기에 클래스에 직접 @Component를 사용할 수 없을 경우
      • 동적인 조건에 따라 특정한 구현체를 Bean으로 등록하도록 할 경우
      • @Bean 등록을 명시적으로 하고싶을 경우

'프레임워크 > Spring' 카테고리의 다른 글

#5 Spring AOP(Aspect Oriented Programming)  (0) 2022.04.30
#4 Spring 의존성 주입 방법  (0) 2022.04.27
#3 Spring에 적용된 디자인 패턴  (0) 2022.04.24
#1 Spring Boot  (0) 2022.04.21
#0 Spring  (0) 2022.04.20

1. CoC(Convention over Configuration)

  • 소프트웨어 프레임워크에서 사용되는 디자인 패러다임
  • 프레임워크에서 관습적으로 사용되는 패턴을 사용자가 정의할 필요가 없도록 하는 것
  • 사용자는 의미없이 관습적인 내용을 정의하느라 고생할 필요가 없음
  • 그러면서도 사용자가 직접 정의할 수 있어야하는 부분에는 제약이 없기에 유연성을 잃지 않음

 

 

2. Spring Boot

  • Spring 프레임워크의 CoC 솔루션

  • Spring에서 관습적으로 사용되는 설정이나 의존성 관리 방식, 개발환경 구축이나 배포 등의 작업들에서
    사용자가 임의로 정의할 수 있어야한다고 판단되는 부분을 제외한 영역을 모두 자동화해주는 도구 

  • Spring Boot가 지원하는 기능들
    • 설정의 자동화(Auto Configuration) - @SpringBootApplication
      • 스프링부트 프로젝트의 Application 클래스에 붙어있는 설정 자동화 어노테이션
      • 내부적으로 아래에 설명할 세 개의 Spring 어노테이션을 사용
         
      • @EnableAutoConfiguration
        • auto configuration 대상으로 등록된 클래스들을 자동으로 Bean으로 등록
        • 해당 클래스들은 사용자가 별도로 등록해주지 않아도 Bean으로 사용 가능
        • 모든 auto configuration 대상을 항상 Bean으로 등록해두는 것은 리소스 낭비
          => AutoConfigurationImportSelector 가 실제로 Bean으로 등록할 클래스를 선별
          => 클래스들이 구현한 Condition 인터페이스를 확인하여 조건에 맞는 클래스만을 선별

        • Condition 어노테이션
          • Conditional : 해당 클래스가 구현한 모든 Condition 인터페이스를 만족해야 등록
          • CondtionalOnClass : 해당 클래스가 classpath에 존재할경우 등록
          • ConditionalOnBean : 해당 Bean이 BeanFactory에 포함되어있을 경우 등록
          • ConditionalOnMissingBean : 해당 Bean이 BeanFactory에 포함되어있지 않을 경우 등록
          • ConditionalOnCloudPlatform : 해당 클라우드 플랫폼이 사용중일 경우 등록
          • ConditionalOnExpress : SpEL 조건문이 참일 경우 등록

      • @SpringBootConfiguration 
        • Spring의 Configuration 어노테이션을 대체하는 어노테이션
        • 애플리케션에 단 하나만 존재할 수 있으며 스프링부트에서 지원하는 테스트 어노테이션인
          @SpringBootTest는 이 어노테이션이 붙은 클래스를 메인으로 하위 설정을 가져온다.

      • @ComponentScan
        • @Component 어노테이션이 붙은 클래스를 자동으로 스캔하여 Bean으로 등록
        • 사용자 Bean를 등록할 수 있게 해주는 어노테이션

    • 내장 웹 서버(Embedded Server) / 서블릿 자동설정
      • Spring Boot로 프로젝트를 생성할 경우 Tomcat과 Dispatcher Servlet을 자동으로 설정하여
        사용자가 별도로 웹 서버를 설치하고 Dispatcher Servlet을 설정해줄 필요가 없음

      • 또한 배포시에도 내장 서버를 탑재한 jar파일만을 배포하면 jar 파일을 실행하는 것만으로
        서버를 구동할 수 있게됨

    • 의존성 관리
      • starter 를 통해 의존성을 관리
      • 기존 Spring에서 버전에 따른 호환성을 고려하여 의존성을 수동으로 관리해야했던 문제 해결

'프레임워크 > Spring' 카테고리의 다른 글

#5 Spring AOP(Aspect Oriented Programming)  (0) 2022.04.30
#4 Spring 의존성 주입 방법  (0) 2022.04.27
#3 Spring에 적용된 디자인 패턴  (0) 2022.04.24
#2 Spring Bean  (0) 2022.04.22
#0 Spring  (0) 2022.04.20

1. Spring이란?

  • Java 기반 웹 애플리케이션 개발 프레임워크
  • EJB가 지원하던 다양한 고급기술들을 훨씬 가볍게 사용할 수 있게 해주는 경량 프레임워크

 

 

2. Spring의 특성

  • 비침투적(non-invasive)
    • 프레임워크를 적용한다고 해서 코드에 그에 따른 규약이나 제약 사항등이 나타나지 않음
    • EJB와 달리 Spring을 적용하더라도 아무런 제약 없이 Java를 사용할 수 있음
    • 기존 Java에서 하던 방식대로 객체를 구성할 수 있음(POJO)

 

  • 제어의 역전(Inversion of Control)
    • 의존성(Dependency)
      • 객체지향 프로그래밍에서 의존성(Dependency)이란 객체간의 의존관계를 의미
      • A 객체가 동작하기 위해 B 객체를 필요로할 경우 A는 B에 의존하는 상태

    • 의존성 역전 원칙(Dependency Inversion Principal)
      • 문제는 어떤 객체가 구체적인 객체(추상화되지 않은 객체)에 의존할 때 발생
      • A가 B에 의존할 때, B가 변경될 경우 A는 정상적으로 작동할 수 없게될 수 있음
      • 이 문제를 해결하기 위해 추상화된 객체를 사용(Interface)
      • A는 인터페이스 C에 의존하고 B는 C를 구현하도록 구조를 변경
      • 그러나 인터페이스에 의존한다고 해도 결국 인스턴스를 생성할 때는 구체적인 B에 의존해야함
      • A가 C의 구현체에 종속적이지 않도록 구현체를 전달해줄 무언가가 필요

    • 의존성 주입(Dependency Injection)
      • 의존성 역전을 지키기 위해 객체는 자신이 의존할 인터페이스의 구현체를 외부에서 주입받아야함
      • A를 사용할 객체가 A가 의존할 C의 구현체인 B를 생성하여 넣어주는 모순이 발생

    • 결국 실행 흐름에 참여하지 않으며 의존성을 주입해줄 완전한 제3자가 필요해지며 이 역할,
      즉 프로그램의 흐름에 대한 제어권을 프레임워크에게 위임하는 방식을 제어의 역전이라 함

    • IoC 컨테이너가 의존성을 주입해주기에 개발자는 구체적인 것에 종속되지 않는 객체를 설계 가능

    • 코드상에 의존관계가 명시되지 않기 때문에 객체간 결합도가 낮아지고 재사용과 유지보수가 용이

    • Spring의 경우 Spring 컨테이너가 XML 파일이나 Spring 설정파일 등에 따라 자동으로 의존성을 주입

 

  • 관점 지향 프로그래밍(Aspect Oriented Programming)
    • 관점 지향 프로그래밍
    • 비즈니스 로직에서 관심사와 관계없이 등장하는 공통 로직을 분리하는 것
    • 공통 로직이 분리된 비즈니스 로직은 오롯이 관심사에 관련된 기능만을 담을 수 있음
    • 로직의 응집도를 높여줌
    • Spring에서는 프록시 패턴을 사용하여 AOP를 구현

 

 

3. Spring의 장점

  • EJB가 지원하던 다양한 기능중 상당수를 지원
  • EJB에서 해결하지 못했던 복잡함을 해결(IoC를 통해 결합도를 낮추고 AOP를 통해 응집도를 높임)
  • EJB에 비해 쉽게 배울 수 있으며 Tomcat과 같은 가벼운 웹서버로도 운영 가능
  • 다양한 디자인 패턴을 적용하고있기에 적용하는 것만으로 상당수의 디자인 패턴을 따르는 개발이 가능

'프레임워크 > Spring' 카테고리의 다른 글

#5 Spring AOP(Aspect Oriented Programming)  (0) 2022.04.30
#4 Spring 의존성 주입 방법  (0) 2022.04.27
#3 Spring에 적용된 디자인 패턴  (0) 2022.04.24
#2 Spring Bean  (0) 2022.04.22
#1 Spring Boot  (0) 2022.04.21

1. "use strict";

// strict 모드 적용
"use strict";


/* reference 에러가 발생하는 경우 */
a = 3;	// reference error



/* type 에러가 발생하는 경우 */

// 1. 예약어에 대한 할당
let undefined = 5;	// type error
let Infinity = 15;	// type error


// 2. 쓰기 불가능하도록 지정된 속성에 대한 할당
let obj = {};
Object.defineProperty(obj, "val", { value: 7, writable: false });

obj.val = 3;	// type error


// 3. setter가 정의 되지않은 속성에 대한 할당
let obj2 = {
	get val() { return 7; }
};
obj2.val = 3;	// type error


// 4. 확장 불가능한 객체에 속성 추가
let fixed_obj = {};
Object.preventExtensions(fixed_obj);
Object.defineProperty(fixed_obj, "val", {value: 7, writable: true});	// type error


// 5. 삭제 불가능한 속성에 대한 삭제시도
let obj3 = {};
Object.defineProperty(obj3, "val", { value: 7, configurable: false });

delete obj3["val"];	// type error


// 6. 원시(primitive) 값에 대한 프로퍼티 설정
(1).val = 11;	// type error
false.val = 3;	// type error



/* syntax 에러가 발생하는 경우 */

// 1. 중복되는 인자 이름(함수)
function func(a, a, b) { return a + b };	// syntax error


// 2. 8진구문 사용
// 8진법 구문은 사용할 일이 거의 없으며 실수가 발생하기 쉽기 때문에 구문오류로 처리
let a = 0o456;	// syntax error


// 3. with 구문의 사용
// with 구문 사용시 블록안에서 사용되는 이름과 블록 바깥의 이름이 같아진다면
// 둘 중 어느것을 의미하는지 모호한 코드가 되기 때문에 strict모드에서는 with문 자체를 금지
let obj = {x: 15};
let x = 3;
with(obj) {	// syntax error
	console.log(x);
}
  • "use strict"; 구문을 적어준 이후의 코드 실행에서는 strict 모드가 적용됨

  • 원래라면 에러를 발생시키지 않았던 자잘한 오류들이 모두 에러를 발생시키도록 엄격한 문법이 적용됨

  • strict 모드에서 에러를 발생시키도록 변경되는 사항들
    • 선언되지 않은 변수의 참조

    • 쓰기 불가능한 속성에 대한 할당
      • Infinity, undefined 등 예약된 값
      • 객체에서 writable: false로 정의된 속성값
      • setter가 정의되지 않은 속성(getter-only)

    • 확장 불가능하도록 지정된 객체에서 새 속성 할당

    • 삭제 불가능한 속성에 대한 삭제시도

    • 원시값에 대한 속성 설정 시도
    • 모호한 구문의 사용 금지
      • 함수 인자명 중복
      • 8진수 구문
      • with 구문

  • 위에 나열한 사항들 이외에도 많은 것들이 use strict에선 에러로 처리

 

 

2. 블록 strict모드

a = 3;
// func1 블록 내에서만 strict 모드 적용
function func1() {
	"use strict";
  b = 3;
}

func1();	// reference error 발생
  • strict 모드의 적용은 보다 안전한 코드를 짜기 위해 도움이 되지만 유연함이 부족하며
    다른 non-strict 모드 스크립트와의 결합시에도 문제가 발생할 여지가 있음

  • 이를 해결하기 위해 부분적으로 strict 모드를 적용 가능
    => 블록 내에 "use strict"; 구문을 추가할경우 블록 내에서만 strict 모드가 적용

 

 

※ strict 모드를 기존에 non-strict 모드로 작성한 JavaScript 코드에 적용하는 것은
   예상치 못한
에러를 대량으로 발생시킬 수 있기에 권장되지 않음. 처음 코드를
   작성할 때부터 strict 모드를
적용시킨 후 이를 고려하여 작성해나가는 것이 바람직함

'언어 > JavaScript' 카테고리의 다른 글

#11 템플릿 리터럴(Template Literal)  (0) 2022.02.23
#10 destructing  (0) 2022.02.23
#9 rest / spread 연산자  (0) 2022.02.23
#8 import / export  (0) 2022.02.21
#7 클래스(Class)  (0) 2022.02.21
let name = "David";
let age = "16";

// 템플릿 리터럴을 사용하여 변수값을 사용하여 문자열 포매팅
let string = `Hi, my name is ${name}. I'm ${age} years old.`;
console.log(string);
  • `...${<변수명>}...` 의 형태로 문자열의 원하는 위치에 변수의 값을 넣을 수 있음
  • 이 때, 템플릿 리터럴을 감싸는 기호는 따옴표(')가 아닌 백틱(`)을 사용해야함에 유의해야한다.

'언어 > JavaScript' 카테고리의 다른 글

#12 strict 모드  (0) 2022.02.23
#10 destructing  (0) 2022.02.23
#9 rest / spread 연산자  (0) 2022.02.23
#8 import / export  (0) 2022.02.21
#7 클래스(Class)  (0) 2022.02.21
// x, y, z 속성을 가진 객체 obj
let obj = {x: 3, y: 5, z: 2};

// destructing 구문을 사용하여 객체의 속성을 한번에 여러 변수에 나눠담음
const {x: a, y: b, z: c} = obj;

// a, b, c를 출력
console.log(a, b, c);	// 3 5 2
  • const { <속성이름>: <속성값을 저장할 변수>, ... } = <객체> 의 형태로 여러개의 속성값을 변수에 저장

  • Python의 언패킹과 유사하게 동작함

'언어 > JavaScript' 카테고리의 다른 글

#12 strict 모드  (0) 2022.02.23
#11 템플릿 리터럴(Template Literal)  (0) 2022.02.23
#9 rest / spread 연산자  (0) 2022.02.23
#8 import / export  (0) 2022.02.21
#7 클래스(Class)  (0) 2022.02.21

1. rest 연산자

// rest 연산자로 인자를 갯수제한 없이 배열의 형태(args)로 받아
// 모든 인자의 합을 반환
function sum(...args) {
    let res = args[0];
	for(let i=1; i<args.length; i++) {
    	res += args[i];
    }
    return res;
}

console.log(sum(1, 2, 3, 4));	// 10
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));	// 55
console.log(sum('Hello', 'World', '!'));	// "HelloWorld!"
  • rest 연산자(...)를 사용할 경우 갯수제한 없이 다수의 인자를 배열의 형태로 받아올 수 있음

  • Python의 애스터리스크(*)와 유사하게 사용 가능

 

 

2. spread 연산자

// spread 연산자를 사용하여 arr의 모든 요소를 arr_2의 0 다음에 나열
let arr = [1, 2, 3, 4, 5];
let arr_2 = [0, ...arr];

console.log(JSON.stringify(arr_2));	// "[0,1,2,3,4,5]"
  • rest 연산자와 같은 형태(...)

  • iterable한 (iterator를 반환할 수 있는) 객체의 값을 풀어 하나씩 나열한 것과 같은 효과를 가짐

  • rest와 마찬가지로 Python의 애스터리스크(*)와 유사하게 사용 가능

 

 

 

'언어 > JavaScript' 카테고리의 다른 글

#11 템플릿 리터럴(Template Literal)  (0) 2022.02.23
#10 destructing  (0) 2022.02.23
#8 import / export  (0) 2022.02.21
#7 클래스(Class)  (0) 2022.02.21
#6 객체(Object)  (0) 2022.02.18

+ Recent posts