Spring framework core 정리 - Kim-Taesu/study GitHub Wiki

IoC 컨테이너

1.1 스프링 IoC 컨테이너, Bean 소개

  • IoC는 의존성 주입(DI, Depedency Injection)으로 잘 알려져 있다.
  • DI는 아래 상황에서 객체의 의존성을 정의한다.
    • 생성자의 아규먼트
    • 팩토리 메서드의 아규먼트
    • 팩토리 메서드로부터 생성되거나 반환된 객체 인스턴스의 프로퍼티에 의해
  • 컨테이너는 빈을 생성할 때 의존성을 주입한다.
    • 기본적으로 Bean이 직접 클래스를 생성하거나 Service Locator 패턴으로 의존성을 주입하는데 IoC는 반대되는 개념이다.
  • org.springframework.beans, org.springframework.context 패키지는 스프링 프레임워크의 IoC 컨테이너의 기본이다.
  • BeanFacotry 인터페이스는 모든 유형의 객체를 관리할 수 있는 매커니즘을 제공한다.
  • ApplicationContextBeanFacotry의 서브 인터페이스다.
    • Spring AOP를 더 쉽게 사용할 수 있게 해준다.
    • 메시지 Resource 처리 (국제화에 사용)
    • Event 발행
    • 웹 애플리케이션에서 사용하기 위한 WebApplicationContext와 같은 애플리케이션 계층을 제공
  • ApplicationContextBeanFacotry의 상위 집합이다.

1.2 컨테이너 소개

  • org.springframework.context.ApplicationContext 인터페이스는 Spring IoC 컨테이너를 의미하고, Bean의 인스턴스화, 구성, Bean 조립을 담당.
  • 컨테이너는 메타데이터를 읽어서 인스턴스화, 구성, 조립할 개체에 대한 지침을 얻는다.
    • 메타데이터는 XML, Java 어노테이션, Java 코드로 표시된다.
    • 메타데이터로 애플리케이션을 구성하는 객체 간의 상호 의존성을 표현할 수 있다.

Figure 1. The Spring IoC Containter

1.2.1 메타데이터 설정

  • 과거 직관적인 XML 형태로 메타데이터를 관리했지만 요즘은 Java 기반으로 메타데이터 설정을 관리한다.
  • Spring 설정은 적어도 1개 이상의 Bean 정의로 구성된다.
    • XML 기반 설정은 요소 내부에 요소로 구성
    • Java 구성은 @Configuration 클래스 내에서 @Bean 주석이 달린 메소드를 사용
  • 도메인 객체를 만들고 로드하는 것은 DAO 및 비즈니스 로직에서 해결하고 컨테이너에서는 도매인 객체를 구성/생성하지 않는다.
    • AspectJ 를 사용하여 IoC 컨테이너가 관리하지 않는 외부 객체를 구성할 수 있다.

1.3 Bean 소개

  • Spring IoC 컨테이너는 최소 1개 이상의 빈을 관리한다.
  • IoC 컨테이너가 메타데이터를 읽어서 Bean을 생성한다.
  • 컨테이너 내부에서는 Bean 정의를 BeanDefinition으로 표시한다.
    • BeanDefinition은 아래 메타 데이터를 포함한다.
    • 패키지 이름 : Bean의 실제 구현 클래스
    • 동작 구성 요소 : Bean이 커넽이너에서 어떻게 동작해야하는지 (scope, lifecycle callback)
    • 해당 Bean이 작업을 수행하는데 필요한 다른 Bean에 대한 참조
    • 새로 생성된 객체에서 설정한 기타 구성 설정

1.3.1 Bean 네이밍

  • 모든 Bean에는 하나 이사의 식별자가 있다.
    • 컨테이너 내부에서 Bean의 식별자는 고유한 값이어야 한다.
  • id, name 속성으로 Bean의 식별자를 지정할 수 있다.
  • 네이밍 컨벤션 : 소문자로 시작하고 카멜 케이스
  • Componenet Scan을 통해 식별자를 따로 지정해주지 않은 Bean 들은 클래스 이름의 제일 앞 문자를 소문자로 바꾸고 사용한다.
    • 두 개 이상의 문자가 있고 첫 번째와 두 번째 문자가 모두 대문자인 특수한 경우에는 원래 대소문자가 유지된다.

1.3.2 Bean 초기화

  • 컨테이너는 생성할 Bean의 구성 메타데이터를 사용하여 실제 객체를 생성한다.
  • Bean의 클래스는 2가지 방법으로 지정할 수 있다.
    1. 컨테이너 자체가 생성자를 호출하여 빈을 직접 생상하는 경우

      • 자바의 new SomeClass()와 동일하다.

        <bean id="exampleBean" class="examples.ExampleBean"/>
    2. 컨테이너가 클래스의 static 팩토리 메서드를 호출하여 빈을 만드는 경우

      • static 팩토리 메서드에서 실제 클래스를 지정한다.

        <bean id="clientService"
            class="examples.ClientService"
            factory-method="createInstance"/>
        public class ClientService {
            private static ClientService clientService = new ClientService();
            private ClientService() {}
        
            public static ClientService createInstance() {
                return clientService;
            }
        }
        public class DefaultServiceLocator {
        
            private static ClientService clientService = new ClientServiceImpl();
        
            private static AccountService accountService = new AccountServiceImpl();
        
            public ClientService createClientServiceInstance() {
                return clientService;
            }
        
            public AccountService createAccountServiceInstance() {
                return accountService;
            }
        }

1.4 의존성 소개

  • 일반적인 애플리케이션은 단일 객체로 구성되지 않는다.

1.4.1 의존성 주입 (DI, Dependency Injection)

  • 생성자 아규먼트, 팩토리 메서드에 대한 아규먼트, 팩토리 메서드에서 반환된 후 객체 인스턴스에 설정된 프로퍼티를 통해 객체의 의존성이 설정되는 프로세스이다.
  • 객체는 의존성을 검색하지 않으며 의존성의 위치 또는 클래스를 알지 못한다.
    • 특히 인터페이스나 추상 클래스에 의존성이 있는 경우, 단위 테스트에서 stub/mock 으로 구현하여 사용할 수 있다.
  • DI는 크게 2가지로 나뉜다.
    • 생성자 기반 의존성 주입
    • setter 기반 의존성 주입

생성자 기반 의존성 주입

  • 의존성을 나타내는 여러 아규먼트가 있는 생성자를 호출하는 컨테이너에 의해 수행된다.

  • Bean을 구성하기 위해 특정 아규먼트를 가진 static 팩토리 메서드를 호출하는 것과 거의 동일하다.

    public class SimpleMovieLister {
    
        // the SimpleMovieLister has a dependency on a MovieFinder
        private final MovieFinder movieFinder;
    
        // a constructor so that the Spring container can inject a MovieFinder
        public SimpleMovieLister(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // business logic that actually uses the injected MovieFinder is omitted...
    }
  • @ConstructorProperties 를 사용하여 생성자 프로퍼티를 지정할 수 있다.

    public class ExampleBean {
    
        // Fields omitted
    
        @ConstructorProperties({"years", "ultimateAnswer"})
        public ExampleBean(int years, String ultimateAnswer) {
            this.years = years;
            this.ultimateAnswer = ultimateAnswer;
        }
    }

setter 기반 의존성 주입

  • 빈 생성자 또는 빈 static 팩토리 메서드를 호출 한 후, Bean의 컨테이너 호출 setter 메서드에 의해 수행된다.

    public class SimpleMovieLister {
    
        // the SimpleMovieLister has a dependency on the MovieFinder
        private MovieFinder movieFinder;
    
        // a setter method so that the Spring container can inject a MovieFinder
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // business logic that actually uses the injected MovieFinder is omitted...
    }

생성자 기반 의존성 주입 vs setter 기반 의존성 주입

  • 생성자 기반, setter 기반 DI를 혼합해서 사용할 수 있다.
    • 따라서 필수 의존성에는 생성자를 사용하고, 선택적 의존성에는 setter 메서드 또는 설정 메서드를 사용하는 것이 좋다.
    • setter 메서드에 @Required를 사용하면 필수 의존성으로 만들수 있지만 유효성 검사가 포함된 생성자 기반 DI를 사용하는 것이 좋다.
  • 일반적으로 생성자 주입을 권장
    • 구성 요소를 final로 사용할 수 있다.
    • null 체크 가능
    • 생성자 아규먼트가 너무 많다면 리팩토링 대상이다.
  • setter 기반 주입은 default 값을 할당할 수 있는 선택적인 의존성에만 사용해야 한다.
    • setter 기반 주입의 이점의 해당 객체를 재구성하거나 다시 주입할 수 있다.

1.5 Bean Scope

  • Bean을 생성할 때 생성될 객체의 scope를 제어할 수 있다.
  • 총 6가지 Scope를 지원하며, 4개는 웹용 ApplicationContext를 사용하는 경우에만 사용할 수 있다.
Scope 설명
singleton (Default) 각 Spring IoC 컨테이너마다 단 1개의 객체 인스턴스를 정의
prototype n개 이상의 객체 인스턴스를 정의할 수 있음
request 단일 HTTP 요청 생명주기로 scope를 지정
각 HTTP request 마다 고유한 객체 인스턴스를 가지고 있다.
웹용 ApplicationContext를 사용하는 경우에만 사용할 수 있다.
session HTTP 세션 생명주기로 scope를 지정
웹용 ApplicationContext를 사용하는 경우에만 사용할 수 있다.
application ServletContext의 생명주기로 scope를 지정
웹용 ApplicationContext를 사용하는 경우에만 사용할 수 있다.
websocket WebSocket의 생명주기로 scope를 지정
웹용 ApplicationContext를 사용하는 경우에만 사용할 수 있다.

1.6 Bean 커스터마이징

  • 스프링 프레임워크는 Bean을 커스터마이징 할수 있도록 여러 인터페이스를 제공한다.

1.6.1 Lifecycle Callbacks

  • InitializingBeanDisposableBean 인터페이스를 구현하여 bean의 lifecycle 를 관리할 수 있다.
    • InitializingBean::afterPropertiesSet 으로 Bean 초기화 시 액션을 걸 수 있다.

      <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
      public class ExampleBean {
      
          public void init() {
              // do some initialization work
          }
      }
      <bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
      public class AnotherExampleBean implements InitializingBean {
      
          @Override
          public void afterPropertiesSet() {
              // do some initialization work
          }
      }
    • DisposableBean::destroy 으로 Bean 초기화 시 액션을 걸 수 있다.

      <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
      public class ExampleBean {
      
          public void cleanup() {
              // do some destruction work (like releasing pooled connections)
          }
      }
      <bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
      public class AnotherExampleBean implements DisposableBean {
      
          @Override
          public void destroy() {
              // do some destruction work (like releasing pooled connections)
          }
      }
  • @PostConstruct@PreDestroy 어노테이션을 사용하여 lifecycle Callback을 설정할 수 있다.
  • 내부적으로 스프링 프레임워크는 BeanPostProcessor 의 구현체를 사용하여 lifecycle callback을 수행한다.
    • 커스터마이징이 필요하면 BeanPostProcessor를 구현해서 사용하면 된다.

lifecycle 매커니즘 결합

  • spring 2.5 부터, bean의 lifecycle callback을 정의하는 방법은 3가지가 있다.
    1. The InitializingBean and DisposableBean callback interfaces
    2. Custom init() and destroy() methods
    3. The @PostConstruct and @PreDestroy annotations.
  • 여러 매커니즘을 동시에 설정한 경우 아래 순서대로 실행된다.
    • The @PostConstruct and @PreDestroy annotations.
    • The InitializingBean and DisposableBean callback interfaces
    • Custom init() and destroy() methods
  • 여러 매커니즘 구성에 대해 만약 동일한 이름의 메서드가 존재한다면 1번 만 실행된다.

1.6.2 ApplicationContextAware / BeanNameAware 소개

  • ApplicationContextorg.springframework.context.ApplicationContextAware 인터페이스를 구현한 객체 인스턴스를 생성하는데, 해당 인스턴스는 특정 ApplicationContext를 참조하는 메서드와 함께 제공된다.

    public interface ApplicationContextAware {
        void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
    }
  • 위 인터페이스의 setApplicationContext 메서드를 통해 ApplicationContext에 접근할 수 있다.

    • ex) 다른 Bean 검색 용도

      public class ExampleClass implements ApplicationContextAware{
      
          private SomeClass someClass;
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              // logic ...
              this.someClass = applicationContext.getBeansOfType(SomeClass.class)
          }    
      }
  • @Autowired 어노테이션을 사용해서 ApplicationContext에 대한 참조를 얻을 수 있다.

    • 필드, 생성자 또는 메서드에 @Autowired 어노테이션이 있는 경우 ApplicationContext가 자동으로 제공된다.
  • ApplicationContextorg.springframework.beans.factory.BeanNameAware 인터페이스를 구현한 클래스를 생성하는데, 해당 클래스는 특정 name을 가지는 Bean을 참조하는 메서드와 함께 제공된다

    public interface BeanNameAware {
    
        void setBeanName(String name) throws BeansException;
    }

1.8 Container Extension Points

  • 일반적으로 개발자는 ApplicationContext 구현한 클래스의 서브클래스를 구현할 필요가 없습니다.
  • 대신 integretion interface를 통해 Spring IoC 컨테이너를 확장할 수 있습니다.

1.8.1 BeanPostProcessor로 커스터마이징하기

  • BeanPostProcessor 인터페이스는 컨테이너의 초기화 로직, 의존성 해결 로직 등을 제공하는 callback 메서드를 정의한다.
    • ApplicationContext는 자동으로 BeanPostProcessor 인터페이스 구현한 클래스를 감지한다.
  • Spring 컨테이너 초기화, 설정 또는 Bean의 초기화 이후 사용자 커스텀 로직을 실행하려면 BeanPostProcessor를 구현하면 된다.
  • 여러개의 BeanPostProcessor 인스턴스를 구성할 수 있고 order 프로퍼티를 설정하여 순서를 지정할 수 있다.
    • order 프로퍼티는 BeanPostProcessorOrdered 인퍼페이스를 구현해야 한다.
  • BeanPostProcessor 인스턴스는 Bean 인스턴스에서 작동한다.
    • Spring IoC 컨테이너가 Bean 인스턴스를 만들고 이후에 BeanPostProcessor 인스턴스가 작업을 수행한다.
  • BeanPostProcessor의 scope는 컨테이너 단위이다.
  • BeanPostProcessor에는 2가지 callback 메서드가 존재한다.
    • postProcessBeforeInitialization : Bean 초기화 전에 실행

    • postProcessAfterInitialization : Bean 초기화 전에 실행

      public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
      
          // simply return the instantiated bean as-is
          public Object postProcessBeforeInitialization(Object bean, String beanName) {
              return bean; // we could potentially return any object reference here...
          }
      
          public Object postProcessAfterInitialization(Object bean, String beanName) {
              System.out.println("Bean '" + beanName + "' created : " + bean.toString());
              return bean;
          }
      }

1.13 Environment Abstraction

  • Environment 인터페이스는 애플리케이션 확녕에서 properties와 profiles를 모델링한다.
  • profile : 지정된 profile이 활성 상태인 경우에만 Bean이 컨테이너에 등록
  • property :

1.13.3 @PropertySource

  • Spring의 Environment에 property 값을 제공한다.
# app.properties
testbean.name=myTestBean
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

1.15.1 MessageSource

  • ApplicationContext 인터페이스는 국제화 기능을 제공하는 MessageSource 인터페이스를 사용할 수 있다.
    • HierarchicalMessageSource 인터페이스도 제공하여 계층적인 메시지를 제공할 수 있다.
  • String getMessage(String code, Object[] args, String default, Locale loc)
    • MessageSource로부터 메시지를 가져오는 기본적인 메서드이다.
    • 특정 Locale의 메시지가 없는 경우 default 메시지가 사용된다.
  • String getMessage(String code, Object[] args, Locale loc)
    • 위 메소드와 동일한 기능이나 출력할 메시지가 없는 경우 NoSuchMessageException 예외를 발생시킨다.
  • ApplicationContext가 로딩될 때 context 내부에서 자동으로 MessageSource Bean을 찾는다.
    • 반드시 bean 이름은 messageSource로 해야 한다.
  • Spring은 3가지 MessageSource 구현체를 제공한다. (모두 HierarchicalMessageSource를 구현한다.)
    • ResourceBundleMessageSource
    • ReloadableResourceBundleMessageSource
    • StaticMessageSource : 거의 사용되지 않음

MessageSource 예시

  • MessageSource bean 설정
    • format.properties, exceptions.properties, windows.properties 파일이 classpath 경로에 있어야 한다.
<beans>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>
  • properties 예시
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
  • 메시지 출력 예시
public static void main(String[] args) {
    // 모든 ApplicationContext의 구현체는 MessageSource의 구현체이므로 바로 캐스팅이 가능하다.
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message); // output: Alligators rock!
}

2.1 Resources 소개

  • Java 표준인 java.net.URL 클래스는 모든 low-level resource에 접근하기에 충분하지 않다.
    • classpath 또는 servletContext에서 상대경로로 resource를 가져와야 하는 경우 접근하기 위한 표준화된 URL 구현체가 없다.

2.2 Resource 인터페이스

  • Resource 인터페이스는 org.springframework.core.io 패키지 하위에 위치한다.
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
  • Resource 인터페이스는 InputStreamSource 인터페이스를 상속한다.
public interface InputStreamSource {
    // resource를 읽을 수 있는 InputStream을 반환한다.
    // stream의 닫는 책임도 맡고있다.
    InputStream getInputStream() throws IOException;
}
  • WriteableResource 인터페이스를 상속하여 쓰기도 가능하다.

2.3 Built-in Resource 구현체

  • UrlResource
    • java.net.URL를 래핑하여 URL resource에 접근할 수 있다.
      • files
      • https target
      • ftp target
      • ...
    • URL은 일반적으로 String 타입으로 표시하며 resource에 따라 prefix를 설정한다.
      • file: : filesystem path인 경우
      • https: : HTTPS protocol을 통해 resource에 접근하는 경우
      • ftp: : FTP를 통해 resource에 접근하는 경우
  • ClassPathResource
    • classpath에 위치하는 resource에 대해 접근한다.
    • java.io.File로 classpath의 resource에 접근할 수 있다.
    • classpath 경로에 없을 경우를 대비해 java.net.URL도 지원한다.
  • FileSystemResource
    • java.nio.file.Path, java.nio.file.Files를 지원하여 filesystem의 resource에 접근할 수 있다.
  • PathResource
  • ServletContextResource
    • 웹 애플리케이션 Root dir의 resource에 접근할 수 있다.
    • java.io.File을 지원하여 filesystem의 resource에 접근할 수 있다.
  • InputStreamResource
  • ByteArrayResource

3. Validateion, Data Biding, and Type Conversion

  • Validation은 웹 계층에만 묶여있지 않아야 하고, 모든 사용 가능한 validator를 연결할 수 있어야 한다.
    • Spring Validation은 모든 계층에서 사용 가능한 Validator를 지원한다.
  • Data biding은 input 값이 동적으로 domain 객체에 바인딩되도록 한다.
  • Validator와 Data binding은 주로 웹 계층에서 사용하지만 다른 계층에서도 사용이 가능하다.
  • Data binder와 BeanWrapper 모두 PropertyEditorSupport을 구현하여 property 값을 파싱하고 포맷팅한다.\

3.1 Spring Validator interface

  • Validator 인터페이스를 구현해서 유효성 검증을 하면 된다.
    • 이 때 검증 실패 시 Errors 객체에 실패 결과를 기입한다.
public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}
public class PersonValidator implements Validator {

    /**
     * This Validator validates only Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz); // 해당 Validator가 주어진 클래스에 대해 유효성 검사를 할 수 있는지 여부
    }

    // 실질적인 검증 로직
    public void validate(Object obj, Errors e) {
        // name 프로퍼티 값이 null 이거나 빈 문자열인 경우 검증 실패
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");

        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
  • 특정 Validator에서 다른 Validator를 가져와 사용할 수 있다.
public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    /**
     * This Validator validates Customer instances, and any subclasses of Customer too
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}
  • 검증 에러 표시할 때 기본적으로 DefaultMessageCondesResolver를 사용한다.
    • reject, rejectXXX 메소드에서 설정한 에러 코드와 함께 에러 메시지를 등록
    • rejectValue("age", "too.darn.old")
      • 코드 값 : too.darn.old
      • Spring은 추가로 too.darn.old.agetoo.darn.old.age.int를 등록한다.

5. AOP

  • 프로그램 구조에 대한 다른 사고 방식을 제공하여 OOP를 보완
  • AOP의 모듈화 단위는 aspect 이다.
  • IoC 컨테이너가 AOP에 의존하지 않는다. (꼭 사용하지 않아도 된다)
  • Spring의 AOP는 스키마 기반 접근 방식 또는 @Aspectj 주석 방식으로 사용할 수 있다.

5.1 AOP 개념

  • Aspect : 여러 클래스를 crosscutting 하는 관심사(concern)의 모듈화
    • EX) 트랜잭션 관리, 로깅
    • Spring AOP에서는 @Aspect 주석을 사용하거나 스키마 선언을 사용하여 구현한다.
  • Join Point : 프로그램 실행 중의 특정 지점
    • Spring AOP에서 Join Point는 항상 메소드 실행을 나타낸다.
  • Advice : 특정 Join Point에서 Aspect에 의해 실행되는 액션
    • around, before, after와 같은 다양한 유형의 advice가 있다.
    • Spring을 포함한 많은 AOP 프레임워크는 advice를 인터셉터로 모델링하고 Join Point 주변에 intercepter 체인을 유지한다.
  • Pointcut : Join Point와 비슷한 용어
    • Advice는 Pointcut 표현식과 연관되며 Pointcut에 일치하는 모든 Join Point에서 실행된다.
      • EX) 특정 이름의 메소드 실행
    • Pointcut 표현식과 일치하는 Join point 구조는 AOP의 핵심 개념이다.
    • Spring은 기본적으로 AspectJ pointcut 표현식을 사용한다.
  • Target object : 1개 이상의 advice가 적용된 aspect object
    • Spring AOP는 런타임 프록시를 사용해서 구현하기 때문에 항상 프록시 객체이다.
  • AOP proxy : aspect를 구현하기 위해 AOP 프레임워크에 의해 생성된 객체
    • Spring Framework에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시이다.
      • JDK Dynamic Proxy : Java의 리플렉션 패키지에 존재하는 Proxy라는 클래스를 통해 생성된 Proxy 객체
      • target object가 하나 이상의 인터페이스를 구현하고 있는 클래스라면 JDK DYnamic Proxy 방식으로 생성
      • target object가 인터페이스를 구현하지 않은 클래스라면 CGLIB 방식으로 AOP 프록시를 생성
    • 추가 설명 링크

Advice 유형

  • before advice : join point 이전에 실행되지만 Join Point로 진행하는 실행 흐름을 막을수는 없다.
    • 예외를 던지면 막을 수 있다.
  • after advice : Join point가 정상적으로 완료된 후 실행할 Advice
    • 메서드가 예외 없이 반환되어야 한다.
  • after throwing advice : 메서드가 예외를 throw 하여 종료되는 경우 실행할 Advice
  • after advice : Join Point 종료 시 실행될 Advice
    • 정상 종료 / 예외 발생 모두 포함
  • around advice : 메서드 호출과 같은 Joint point를 둘러싸는 advice
    • 메서드 호출 전후에 사용자 코드를 실행할 수 있다.
    • 예외를 throw 하여 join point의 실행을 막을 수 있습니다.

Pointcut 표현식

  • execution : 특정 메서드와 일치하는지에 대한 표현식
    • Spring AOP로 작업할 때 사용하는 기본 포인트컷 지정자
// 모든 public 메소드를 대상
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

// set으로 시작하는 모든 메서드
@Pointcut("execution(* set*(..))")
...

// AccountService 인터페이스 하위 메서드 대상
@Pointcut("execution(* com.xyz.service.AccountService.*(..))")
...

// service 패키지 하위 모든 메서드 대상
@Pointcut("execution(* com.xyz.service.*.*(..))")
...

// service 패키지 또는 service의 하위 패키지 
@Pointcut("execution(* com.xyz.service..*.*(..))")
...

// service 패키지 하위
@Pointcut("within(com.xyz.service.*)")
...

// service 패키지 또는 service 하위 패키지
@Pointcut("within(com.xyz.service..*)")
...
  • within : 특정 유형과 일치하는지에 대한 표현식
// trading 모듈 내부 메소드를 대상
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 
  • this : Bean의(Spring AOP Proxy) 특정 유형의 인스턴스와 일치하는지에 대한 표현식
// AccountService 인터페이스인 경우
// this instanceof AccountService 인 경우
@Pointcut("this(com.xyz.service.AccountService)")
...
  • target : target object가 특정 인스턴스와 일치하는지에 대한 표현식
// AccountService 인터페이스를 구현한 object 대상
// this AccountSereviceImpl instanceof AccountService 인 경우
@Pointcut("target(com.xyz.service.AccountService)")
  • args : 인스턴스의 아규먼트가 특정 타입과 일치하는지에 대한 표현식
// 메서드 아규먼트가 1개이고 타입이 Serializable인 경우
@Pointcut("args(java.io.Serializable)")
  • @target : 실행 객체의 클래스가 특정 타입과 일치하는지에 대한 표현식
// target object가 @Transactional 어노테이션이 있을 때
@Pointcut("@target(org.springframework.transaction.annotation.Transactional)")
  • @args : 실제 전달된 아규먼트의 타입이 특정 타입과 일치하는지에 대한 표현식
// 메서드 아규먼트가 1개이고 타입이 Classified인 경우
@Pointcut("@args(com.xyz.security.Classified)")
  • @within : 특정 어노테이션이 존재하는지에 대한 표현식
// target에 @Transactional 어노테이션이 있을 때
@Pointcut("@within(org.springframework.transaction.annotation.Transactional)")
  • @annotaion : join point에 특정 어노테이션이 있는지에 대한 표현식
// 실행 메서드 중 @Transactional 어노테이션이 있을 때
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
// 모든 public 메소드를 대상
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

// trading 모듈 내부 메소드를 대상
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 

// 모든 public 메소드 + trading 모듈 내부 메소드 대상
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 
⚠️ **GitHub.com Fallback** ⚠️