프로그래밍 공부/Spring

Spring - IoC, AOP, PSA

tunta 2022. 5. 5. 21:03
반응형

IoC(Inversion of Control)컨테이너

ApplicationContext (BeanFactory)

제어 반전, 제어의 반전, 역제어는 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다. 줄여서 IoC(Inversion of Control)이라고 부른다.

빈 설정

  • 이름 또는 ID
// id 선언시
<bean id="articleDao" class="~~~~~.MySQLArticleDao">
</bean>
// name 선언시
<bean name="articleDao" class="~~~~~.MySQLArticleDao">
</bean>
id 속성은 스프링 컨테이너에서 생성된 객체를 구분하는 데 사용되는 식별 값을 의미한다. id속성 대신에 name을 사용해도 된다.

id 속성은 XML의 ID구성 요소이기 때문에 id속성을 사용할 경우 동일한 id 값을 갖는 <bean>태그를 설정하면 XML 문서 검증 과정에서 예외가 발생한다. 또한, id속성은 슬래시(’/’)나 콤마(’,’) 와 같은 특수 문자를 포함할 수 없다. 반면에 name 속성을 사용할 경우 특수 문자를 이름으로 사용할 수 있다.

  • 타입
<bean class="example.xxxxx">
      <value type="long">300</value>
      <!-- 이 빈이 필요로하는 의존성을 주입한다 -->
</bean>
스프링은 기본적으로 <value> 에 전달된 값을 java.lang.String 타입으로 처리한다. 따라서 코드에서 설정해주지 않으면 String타입인 생성자를 사용해서 객체를 생성하게 된다.
  • 스코프
<bean id="..." ... scope="prototype"></bean>
@Scope annotation을 이용하면 @Bean annotation을 통해서 생성할 빈 객체의 범위를 지정할 수 있다. 아래 코드는 적용 예를 보여주고 있다.

스프링 IoC 컨테이너에 빈으로 등록이 되면 싱글톤으로 관리를 한다. 싱글톤이란 객체를 한번만 만들어서 사용한다는 것이다.

  • singleton : 기본(Default) 싱글톤 스코프. 하나의 Bean 정의에 대해서 Container 내에 단 하나의 객체만 존재한다.
  • prototype : 어플리케이션에서 요청시 (getBean()) 마다 스프링이 새 인스턴스를 생성

proxyMode속성에는 ScopedProxyMode열거 타입을 값으로 지정할 수 있으며, 설정할 수 있는 값은 아래와 같다.

  • ScopedProxyMode.No
    • 프록시를 적용하지 않는다.
  • ScopedProxyMode.INTERFACES
    • 인터페이스에 대해 프록시를 생성한다.
  • ScopedProxyMode.TARGET_CLASS
    • 클래스에 대해 프록시를 생성한다.
  • ScopedProxyMode.DEFAULT
    • 기본 설정을 사용한다.

빈 (Bean)

스프링 IoC 컨테이너가 관리하는 객체

빈 설정 방법

  • Component Scanning
    • @Component
      • @Repository
      • @Service
      • @Controller
      • @Configuration
ComponentScan으로 범위를 지정해서 해당 범위에 안에 있는 어노테이션을 찾아서 해당 이노테이션에서 정의된 클래스를 찾아서 빈 설정을 한다.
@Repository 의 경우에는 상속받는 인터페이스의 클래스를 찾아서 해당 인터페이스의 구현체를 생성해서 빈 설정을 한다.

XML

// id 선언시
<bean id="articleDao" class="~~~~~.MySQLArticleDao"></bean>

자바 설정 파일

@Configuration
public class init{
    @Bean
    public TestData Data_init(){
        //  ...   // 
    }
}
ComponentScan으로 해당 클래스를 찾은 뒤 Bean으로 설정된 처리를 Bean처리를 한다.

@Autowired, @Inject, ApplicationContext에서 getBean() 사용법

@Autowired

@Autowired 어노테이션은 생성자, 필드, (setter)매서드의 세 곳에 적용이 가능하다.

import org.springframework.beans.factory.annotation.Autowired;

public class monitorViewer implements Viewer{
    private DisplayStrategy displaystratgy;

    // 생성자
    @Autowired
    public void setDisplayStrategy(DisplayStrategy displaystratgy){
        this.displaystratgy = displaystratgy;
    }
}
//-----------------------------------------//
public class monitorViewer implements Viewer{
    // 필드
    @Autowired
    private DisplayStrategy displaystratgy;
}

//--------------------------------------------------//
@Component
public class MadExample {

    // final로 선언할 수 있는 보너스
    private final HelloService helloService;

    // 단일 생성자인 경우는 추가적인 어노테이션이 필요 없다.
    public MadExample(HelloService helloService) {
        this.helloService = helloService;
    }
}

@Inject

@Autowired와 유사하게 주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입한다.

@Resource는 Java 제공 애노테이션이며 필드, 생성자, Setter에 붙일 수 있다.

@Autowired와 마찬가지로 필드, Setter에 사용할 경우 반드시 기본 생성자가 정의되어 있어야 한다.

1) 의존성 설정

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

2) @Inject - 필드 주입

import javax.inject.Inject;

public class WordRegisterServiceUseAutowired {

    @Inject
    private WordDao wordDao;

    public WordRegisterServiceUseAutowired() { }
}

3) @Inject - 생성자 주입

import javax.inject.Inject;

public class WordRegisterServiceUseAutowired {

    private WordDao wordDao;

    @Inject
    public WordRegisterServiceUseAutowired(WordDao wordDao) {
        this.wordDao = wordDao;
    }
}

4) @Inject - Setter 주입

import javax.inject.Inject;

public class WordRegisterServiceUseAutowired {

    private WordDao wordDao;
	
    public WordRegisterServiceUseAutowired() { }

    @Inject
    public void setWordDao(WordDao wordDao) {
        this.wordDao = wordDao;
    }
}

getBean()

public class MyApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                                = new AnnotationConfigApplicationContext(Config.class);

        MyBean myBean = context.getBean("myBean", MyBean.class);
        myBean.doSomething();
    }
}
// getBean을 호출함으로써 인스턴스가 생성되고 스프링 컨테이너의 Singleton으로 등록이 된다.

AOP

AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.

AOP 주요 개념

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
  • Target : Aspect를 적용하는 곳 (클래스, 메서드 .. )
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
  • PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

스프링 AOP 특징

  • 프록시 패턴 기반의 AOP 구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서임
  • 스프링 빈에만 AOP를 적용 가능
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 ...)에 대한 해결책을 지원하는 것이 목적

스프링 AOP : @AOP

스프링 @AOP를 사용하기 위해서는 다음과 같은 의존성을 추가해야 한다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

다음에는 아래와 같이 @Aspect 어노테이션을 붙여 이 클래스가 Aspect를 나타내는 클래스라는 것을 명시하고 @Component를 붙여 스프링 빈으로 등록한다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

@Component
@Aspect
public class LogAspect {
    Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Around("@annotation(LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 호출 적용하는 메소드 혹은 클래스
        Object proceed = joinPoint.proceed();

        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());
        return proceed;
    }
}

위와 같이 선언한 후 다른 매서드에 @LogExecutionTime를 추가하는 것으로 해당 매서드를 자동으로 불러와서 처리하게 된다.

PSA

추상화 계층을 사용해서 어떤 기술을 내부에 숨기고 개발자에게 편의성을 제공해주는 것을 Service Abstraction이라 한다.

더하여 Service Abstraction으로 제공되는 기술을 다른 기술 스택으로 간편하게 바꿀 수 있는 확장성을 갖고 있는 것이 Portable Service Abstraction이다.

Spring은 Spring Web MVC, Spring Transaction, Spring Cache 등의 다양한 PSA를 제공한다.

Spring Web MVC

일반 클래스에 @Controller 애노테이션을 사용하면 요청을 매핑할 수 있는 컨트롤러 역할을 수행하는 클래스가 된다.

그 클래스에서는 @GetMapping과 @PostMapping 애노테이션을 사용해서 요청을 매핑할 수 있다.

//
@Controller
//
class OwnerController {

    private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";

    private final OwnerRepository owners;

    public OwnerController(OwnerRepository clinicService) {
        this.owners = clinicService;
    }

    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder) {
        dataBinder.setDisallowedFields("id");
    }
//
    @GetMapping("/owners/new")
//
    public String initCreationForm(Map<String, Object> model) {
        Owner owner = new Owner();
        model.put("owner", owner);
        return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
	}
}

httpservlet과 비슷하게 doGet, doPost의 형태 처럼 동작하게 된다.

Spring Transaction

Low level로 트랜잭션 처리를 하려면 setAutoCommit()과 commit(), rollback()을 명시적으로 호출해야 한다.

그러나 Spring이 제공하는 @Transactional 애노테이션을 사용하면 단순히 메소드에 애노테이션을 붙여줌으로써 트랜잭션 처리가 이루어진다.

@Query("SELECT DISTINCT owner FROM Owner owner left join  owner.pets WHERE owner.lastName LIKE :lastName% ")
@Transactional(readOnly = true)
Page<Owner> findByLastName(@Param("lastName") String lastName, Pageable pageable);

이또한 PSA로써 다양한 기술 스택으로 구현체를 바꿀 수 있다.

예를 들어 JDBC를 사용하는 DatasourceTransactionManager, JPA를 사용하는 JpaTransactionManager, Hibernate를 사용하는 HibernateTransactionManager를 유연하게 바꿔서 사용할 수 있다.

즉 기존 코드는 변경하지 않은 채로 트랜잭션을 실제로 처리하는 구현체를 사용 기술에 따라 바꿔 끼울 수 있는 것이다.

Spring Cache

Cache도 마찬가지로 JCacheManager, ConcurrentMapCacheManager, EhCacheCacheManager와 같은 여러가지 구현체를 사용할 수 있다.

@Transactional(readOnly = true)
@Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException;

사용자는 @Cacheable 애노테이션을 붙여줌으로써 구현체를 크게 신경쓰지 않아도 필요에 따라 바꿔 쓸 수 있는 것이다.

이렇게 spring이 제공해주는 다양한 PSA 기술 덕분에 코드는 더 견고해지고 기술이 바뀌어도 유연하게 대처할 수 있게 된다.


[강의 자료]

https://www.inflearn.com/course/spring_revised_edition/dashboard

반응형