[프레임워크] 스프링 프레임워크 개요


스프링 프레임워크 개요

1. Spring Framework 개요

자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크

1.1. EJB 아키텍처

  • 비즈니스 로직의 복잡성과 기술적인 복잡성의 분리
    • 선언적 트랜잭션, 보안
    • 컨테이너를 통한 리모팅 기술의 적용
    • 컴포넌트 단위의 배치
    • JNDI를 이용한 서비스 검색
    • 서비스 오브젝트의 풀링
    • 컴포넌트 생명주기 관리
  • But, EJB라는 환경과 스펙에 종속(침투적 기술)
    • EJB 환경에 동작하기 위해 특정 인터페이스를 구현하고 특정 클래스를 상속
    • 서버에 종속적인 서비스를 통해서만 접근하고 사용이 가능
  • 객체지향적인 특성을 잃어버리는 결과
    • 상속, 다형성 적용의 제한
  • 대안은?
    • 비침투적(non-invasive)인 기술의 적용
    • 기술의 적용 사실이 코드에 직접 반영되지 않음
    • 코드의 설계와 구현 방식을 제한하지 않음

1.2 스프링의 기원

  • Rod Johnson, “Expert One-on-One J2EE Design and Development”, 2003
  • “항상 프레임워크 기반으로 접근하라”

1.3 스프링의 전략

Lightweight Container Architecture
객체지향 언어의 장점을 최대한 활용 => 스프링은 단지 거들 뿐이다.

  • 개발의 복잡함을 제거하고 개발을 편하게 해주는 해결책을 제시
    • 로우레벨의 트랜잭션이나 상태 관리, 멀티 스레딩, 리소스 풀링 …
  • 비즈니스 로직을 빠르고 효과적으로 구현

서비스 추상화(Portable Service Abstraction)

  • 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리
  • 환경과 세부 기술에 독립적인 접근 인터페이스를 제공
  • 엔터프라이즈 개발에 사용되는 다양한 기술에 대한 서비스 추상화 기능을 제공
  • 설정을 통해 어떤 종류의 기술을 사용할지 지정
    • 트랜잭션 서비스 추상화
      • JDBC TX: PlatformTransactionManager
      • JTA TX: JtaTransactionManager

AOP (Aspect Oriented Programming)

  • 기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 방법
  • 애플리케이션 로직을 담당하는 코드에 남아 있는 기술 관련 코드를 분리하여 별도 모듈로 관리
  • 객체지향의 언어의 장점을 살려서 비즈니스 로직의 효과적인 구현이 가능
    • 유연한 설계
    • 재사용성

핵심도구 : 객체지향과 DI

  • 비즈니스 로직 자체의 복잡함은 객체지향 설계 기법이 더 중요
  • 즉, 객체지향 설계 기법을 잘 적욯할 수 있는 구조를 잘 만들 수 있도록 도와주는 전략
  • DI(Dependency Injection)
    • 바뀔 수 있는 것을 오브젝트로 분리하고,
    • 인터페이스를 도입하고,
    • DI로 관계를 연결

POJO (Plain Old Java Object)

  • Martin Fowler, 2000, EJB <-> POJO
  • 간단한 자바 오브젝트 라는 의미
  • 특정 규약 및 환경에 종속되지 않는다.
  • 자동화된 테스트에 유리하다.
  • 스프링의 주요 기술은 애플리케이션을 POJO로 개발할 수 있게 해주는 가능 기술이다.
  • IoC, DI, AOP …

1.4 스프링의 기술

1.4.1 제어의 역전(Inversion of Controll)

1.4.2 의존관계 주입(Dependency Injection)

  • 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 이용해서 외부에서 지정
  • 유연한 확장이 가능하게 하기 위함 (OCP) : A -> B
  • 의존 대상의 구현을 변경 -> DAO
  • 동적인 기능 변경 (다이내믹 라우팅 프록시, 프록시 오브젝트)
  • 부가 기능의 추가 (데코레이터 패턴)
  • 인터페이스의 변경 (어댑터 패턴) : A -> B -> C
    • 어댑터 레이어
  • 일관성 있는 서비스 추상화(PSA)
  • 프록시
    • 지연된 로딩(lazy loading)
    • 원격 프록시(EJB, 웹서비스, REST, HTTP)
  • 싱글톤과 오브젝트 스코프
    • 대상 오브젝트를 컨테이너가 관리
    • 오브젝트의 생성부터 관계 설정, 이용, 소멸
    • 싱글톤이 기본 스코프
  • 테스트
    • 테스트 대상인 오브젝트의 기능에 충실하게 테스트 가능
    • 의존 오브젝트를 대신해서 스텁 또는 목 오브젝트 활용이 용이
    • 테스트용으로 설정을 별도록 만드는 방법도 가능

1.4.3 관점 지향 프로그래밍(Aspect Oriented Programming)

AOP 적용 기법

  • 다이내믹 프록시 방식
    • 기존코드에 영향을 주지 않고 부가기능을 적용하게 해주는 데코레이터 패턴을 응용한 것
    • 부가 기능을 부여할 수 있는 곳은 메소드의 호출이 일어나는 지점 뿐이다.
      (인터페이스와 DI를 활용)
  • AspectJ
    • 프록시 방식의 AOP에서는 불가능한 다양한 조인 포인트를 제공
    • 메소드 호출, 인스턴스 생성, 필드 액세스 …
    • 단점: AOP 컴파일러를 이용한 빌드 과정,
      또는 클래스가 메모리로 로딩 될 때 바이트 코드를 조작하는 위빙 방법이 필요

AOP 적용 예

  • 미리 준비된 AOP 이용
    • 스프링이 제공하는 AOP 기능 적용
    • 예> 트랜잭션
  • 프로젝트 정책적으로 적용
    • 공통 레벨에서 구현하여 적용
    • 예> 보안, 로깅, 트레이싱, 성능 모니터링, …

1.5 Spring IoC Container

  • instantialing
  • Configuring

1.6 Spring Bean

  • Annotation
    • @Component annotation in class
    • @BEan annotation in method

1.7 Dependency Management Tools

  • Gradle
    • DSL(Groovy or Kotlin)
  • Maven
    • XML
  • Maven Repository

2. Hello World Example

Hello.java

public interface Hello {
    String sayHello(String message);
}

HelloImpl.java

@Component
public class HelloImpl implements Hello {

    @Override
    public String sayHello(String message) {
        return "Hello " + message;
    }
}

HelloTest.java

@ContextConfiguration(classes = DbConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class HelloTest {

    @Resource
    Hello hello;

    @Test
    public void sayHello() {
        String message = hello.sayHello("World");
        assertEquals("Hello World", message);
    }
}

컴포넌트 스캔을 위해서 WebConfig 클래스의 ComponentScan 패키지에 다음을 추가한다.

WebConfig.java

@ComponentScan(basePackages = { "kyun.framework.hello" })

3. Spring Web MVC

The request processing workflow in Spring Web MVC (high level)

사진출처: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html

MVC Architecture

Hello Web Application

Controller

HelloController.java

@Controller
public class HelloController {

    @Resource
    Hello hello;

    @RequestMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello World!");
        return "hello";
    }

    @RequestMapping("/hello/sayHello")
    @ResponseBody
    public String sayHello(String message) {
        return hello.sayHello(message);
    }
}

JSP

hello.jsp

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <title>Hello</title>
</head>
<body>
<H1>${message}</H1>
</body>
</html>

WEB Application Configuration 클래스

WebConfig.java

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = { "kyun.framework.team.controller, kyun.framework.hello" })
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public UrlBasedViewResolver setupViewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

WebInitializer.java

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { DbConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

Spring Web MVC 사용을 위한 라이브러리 의존성 추가

pom.xml

        <!-- Spring Web MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.2.RELEASE</version>
        </dependency>

        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- JSTL -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

Tomcat Deploy & Test

  • http://mirror.navercorp.com/apache/tomcat/tomcat-8/v8.0.36/bin/apache-tomcat-8.0.36-windows-x64.zip

  • 다운로드 받은 파일을 적당한 디렉토리에 압축을 푼다.

  • Eclipse Servers 뷰에서 New > Servers 를 클릭한다.

  • Apache > Tomcat v8.0 Server 를 선택하고 Next 를 클릭한다.

  • Tomcat installation directory 를 선택하고 Next 를 클릭한다.

  • basic 어플리케이션을 Add하고 Finish 를 클릭한다.

테스트

http://localhost:8080l4.png/hello

http://localhost:8080/basic/hello/sayHello?message=World

4. REST API

REST 개요

REST는 웹의 창시자(HTTP) 중의 한 사람인 Roy Fielding의 2000년 논문에 의해서 소개되었다. 현재의 아키텍쳐가 웹의 본래 설계의 우수성을 많이 사용하지 못하고 있다고 판단했기 때문에, 웹의 장점을 최대한 활용할 수 있는 네트워크 기반의 아키텍쳐를 소개했는데 그것이 바로 Representational safe transfer (REST)이다.

REST 기본요소

  • 리소스
    REST는 리소스 지향 아키텍쳐 스타일이라는 정의 답게 모든 것을 리소스 즉 명사로 표현을 하며, 각 세부 리소스에는 id를 붙인다.

    http://localhost:8080/basic/api/team/1

  • 메서드

MethodCRUDIdempotent
POSTCreateNo
GETSelectYes
PUTUpdateYes
DELETEDeleteYes
  • 메세지
    요청/응답 데이터
[{"id":1,"teamName":"basic","rating":5}]

JSON 을 이용한 요청/응답

RestConfig.java

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "kyun.framework.team.controller" }, useDefaultFilters = false, includeFilters = {
        @Filter(Controller.class) })
public class RestConfig extends WebMvcConfigurerAdapter {

    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
    }

    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        return converter;
    }
}

TeamService.java

public interface TeamService {
    public void addTeam(Team team);
    public void updateTeam(Team team);
    public Team getTeam(int id);
    public void deleteTeam(int id);
    public List<Team> getTeams();
}

TeamService.java

@Service
@Transactional
public class TeamServiceImpl implements TeamService {

    @Autowired
    private TeamDao teamDao;

    @Override
    public void addTeam(Team team) {
        teamDao.addTeam(team);
    }

    @Override
    public void updateTeam(Team team) {
        teamDao.updateTeam(team);
    }

    @Override
    public Team getTeam(int id) {
        return teamDao.getTeam(id);
    }
}

TeamRestController.java

@Controller
@RequestMapping(value = "/api/team")
public class TeamRestController {

	@Autowired
	TeamService teamService;
	
    @RequestMapping(value = "/list", method = RequestMethod.GET)
	@ResponseBody
	public List<Team> getTeams(){
		List<Team> teams = teamService.getTeams();
		
		return teams;
	}
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
	@ResponseBody
    public String addTeam(@RequestBody Team team) {

        teamService.addTeam(team);

        return "OK"; 
    }
}

다음과 같이 getServletConfigClasses()RestConfig.class를 추가한다.
WebInitializer.java

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class, RestConfig.class };
    }
}

JSON 사용을 위한 라이브러리 의존성 추가

pom.xml

        <!-- JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.1</version>
        </dependency>

REST Test

Postman 설치

다음의 크롬 웹스토어에서 Postman 을 찾아 설치한다.
https://chrome.google.com/webstore/category/apps?utm_source=chrome-ntp-icon


크롬앱에서 Postman을 실행하고 다음과 같이 테스트를 수행한다.

  • Method: POST 선택
  • URL: http://localhost:8080/basic/api/team/add
  • Body 탭에서 raw 타입을 선택하고 JSON 포멧으로 변경한다.
  • 다음과 같이 요청 메세지를 입력한다.
    {
    "teamName":"basic",
    "rating":5
    }
    

  • Send 버튼을 누르면 요청이 전송되고 다음과 같이 “OK” 응답이 돌아오면 정상 처리된 것이다.

  • 다음 URL을 호출하여 정상 응답을 확인한다.

http://localhost:8080/basic/api/team/list

[{"id":1,"teamName":"basic","rating":5}]

5. Anotation vs XML

스프링에서 빈(Bean) 객체의 의존성 관리를 위한 설정 방법

5.1 Anotation

Service classes

@Resource
AaccountDao ccountDao

@Resource
ItemDao itemDao

DAO classes

@Repository
public class AaccountDao {
    ...
} 

@Repository
public class ItemDao {
    ...
} 

5.2 XML

service.xml

<!-- 서비스 -->
  <bean id="petStore"
        class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
    <property name="accountDao" ref="accountDao"/>
    <property name="itemDao" ref="itemDao"/>
    <!-- 이 빈에 대한 추가적인 협력 객체나 설정은 여기에 작성한다 -->
  </bean>

  <!-- 서비스에 대한 추가적인 빈 정의는 여기에 작성한다 -->

dao.xml

<bean id="accountDao"
      class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapAccountDao">
    <!-- 이 빈에 대한 추가적인 협력 객체나 설정은 여기에 작성한다 -->
  </bean>

  <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapItemDao">
    <!-- 이 빈에 대한 추가적인 협력 객체나 설정은 여기에 작성한다 -->
  </bean>

  <!-- 데이터 접근 객체에 대한 추가적인 빈 정의는 여기에 작성한다 -->

5.3 비교

5.3.1 Anotation

  • 모든 정보가 소스코드에서만 관리되므로 가독성 측면에서 단순하고 명확
  • 자바소스코드만 관리하면 되므로 관리가 용이
  • 클래스가 변경되었을 경우 XML을 수정할 필요가 없음
  • 생산성 측면으로도 XML 방식보다 더 유리

5.3.2 XML

  • 코드와 설정의 명확한 분리
  • 코드정보의 탐색 측면에서는 XML 바운더리만 검색하면 되므로 Anotation 보다 좀 더 용이
  • XML 파일과 자바코드를 병행해서 트레이스 해야 하므로 가독성이 떨어짐
  • XML 파일과 자바소스 코드를 모두 관리해야 함
  • 오타로 인한 오류 발생 가능성이 더 높음

  • 표로 정리하면 대략 다음과 같다.
구분AnotationXML
ReadabilityOX
ProductivityOX
FlexibilityXO
Length짧다길다

5.3.3 기타 의견

  • 의존성 대상 객체의 추적은 IDE 툴에서 대부분 지원하는 기능임 따라서, XML, 어노테이션 모두 쉽게 트레이스 가능함
  • 개발 편의성 측면에서는 위에서 언급한 바로 어노테이션 방식이 편리하다고 생각됨
  • 소스코드의 양이 많을 수록 XML의 트레이싱은 더욱 어려워지고 Consistency 측면에서도 득이 될 것이 없음
  • 유지보수성을 고려한다면 자산관리 및 거버넌스까지 고려되어야 장점을 찾을 수 있다고 생각됨 단순히 오류내역을 트레이스하고 소스코드를 찾기 위한 방법은 툴차원으로 해결 가능하다고 생각됨 (물론, 개발툴 없이 생각한다면 달라질 수 있음)





© 2017. by yeopoong.github.io

Powered by yeopoong