본문 바로가기

책/코틀린 리액티브 프로그래밍

10. 코틀린 개발자를 위한 스프링 웹 프로그래밍 소개

스프링 과 코틀린

스프링은 다음과 같은 개념을 이용 POJO 지향 프로그래밍 모델을 지원하도록 되어 있다. POJO란 특정 라이브러리에 종속되지 않게 순수한 코드로 프로그래밍 하는 것을 의미한다(기본 java 라이브러리는 제외)

  • 의존성 주입(Depedency Injection)
  • 제어의역전(Inversion of Control)
  • 관점지향프로그래밍(Aspect-oriented Programming) 

2017년 1월 스프링은 코틀린 지원을 발표 했는데 코틀린 지원의 이유는 자바로 작성된 라이브러리와의 상호 운용성이 뛰어 났기 때문이다. 

스프링 어노테이션 기반 설정

스프링은 xml 기반 설정, 어노테이션 기반 설정 둘다 가능하다. 우선 어노테이션 기반의 설정을 알아보자.( xml 설정에 관한 부분은 aop 설정시에 알아본다.)

 

다음은 클래스의 의존성흐름을 보여준다.

Student   ▶   Assignment  ▶   task 정의   

람다 task를 인자로 받아서 performAssignment() 메서드를 실행하는 Assignment 클래스이다.

package com.rivuchk.reactivekotlin.springdi.student_assignment

class Assignment(val task:(String)->Unit) {
    fun performAssignment(assignmentDtl:String) {
        task(assignmentDtl)
    }
}

 

다음은 Assignment 인스턴스를 인자로 받는 Student 클래스이다.

 

package com.rivuchk.reactivekotlin.springdi.aop_student_assignment

open class Student(public val assignment: Assignment) {
    open public fun completeAssignment(assignmentDtl:String) {
        assignment.performAssignment(assignmentDtl)
    }
}

의존성 흐름을 어노테이션 기반으로 설정한 Configuration 클래스이다.

package com.rivuchk.reactivekotlin.springdi.student_assignment

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class Configuration {

    @Bean
    fun student() = Student(assignment())

    @Bean
    fun assignment()
            = Assignment { assignmentDtl -> println("Performing Assignment $assignmentDtl") }
}

다음은 스프링의 AnnotationConfigApplicationContext 를 이용하여 스프링을 사용한 예제이다.

package com.rivuchk.reactivekotlin.springdi.student_assignment

import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext


class SpringdiApplication

fun main(args: Array<String>) {
    val context = AnnotationConfigApplicationContext (Configuration::class.java)
    val student = context.getBean(Student::class.java)
    student.completeAssignment("One")
    student.completeAssignment("Two")
    student.completeAssignment("Three")

    context.close()
}

/* 결과
Performing Assignment One
Performing Assignment Tow
Performing Assignment Three
*/

스프링: AOP

 

 위의 어노테이션 기반 설정에서 클래스의 의존성 흐름을 보면 Student(학생)이 Assignment(과제) 를 task(수행)하는 프로세스를 가지고 있다. 여기에 Faculty(교직원)이 Student(학생)를 평가하는 프로세스를 추가 한다고 해보자. 

 일반적으로 Student 클래스에 Faculty 가 평가하는 로직이 들어가게 되는데, 이는 Student 클래스의 로직에 수정이 된다는 것이고, 코드가 더 복잡해지고, 결합력이 커진다는 의미가 된다.

  스프링에서는 이를 횡단관심사라고 하고 이를 AOP(관점지향프로그래밍)로 해결하고 있다. 간단히 말하면 Student 클래스의 로직을 변경하지 않고, Faculty의 로직을 끼워 넣는 것이다. 아래의 코드를 보자.

 

Faculty 클래스의 구현이다.

package com.rivuchk.reactivekotlin.springdi.aop_student_assignment

import java.util.*
/* 교직원 */
open class Faculty() {
    open public fun evaluateAssignment():Int {
        val marks = Random().nextInt(10)
        println("This assignment is evaluated and given $marks points")
        return marks
    }
}

Student 클래스의 구현이다. 

package com.rivuchk.reactivekotlin.springdi.aop_student_assignment

/* 학생 */
open class Student(public val assignment: Assignment) {
    open public fun completeAssignment(assignmentDtl:String) {
        assignment.performAssignment(assignmentDtl)
    }
}

Assignment의 구현이다. 

package com.rivuchk.reactivekotlin.springdi.aop_student_assignment

/* 과제 */
open class Assignment() {
    open public fun performAssignment(assignmentDtl:String) {
        println("Performing Assignment $assignmentDtl")
    }
}

스프링 xml 방식으로 설정한 부분이다. 여기서 중요한 부분이 aop 테그 이다.

  1. 주석 1 에서는 스프링에서 관리하도록 faculty 를 빈으로 생성하였다.
  2. 주석 2 에서는 aop를 설정하는 테그이다.
  3. 주석 3 에서는 aop를 사용할 때 faculty 빈을 참조하겠다라는 내용이다.
  4. 주석 4 에서는 aop를 사용할 때 어디지점(pointcut) 에서 faculty 빈을 사용할지를 나태나고 있다.
    1. expression 에서는 정규표현식 등을 이용하여 여러 클래스의 pointcut을 선택할 수 있다.
  5. 주석 5 에서는 aop를 사용할 때 pointcut의 실행 전, 후 등을 선택할 수 있는데, 여기서는 실행 후(after)로 faculty의 evaluateAssignment 메서드를 실행하겠다고 설정되어 있다.

aop의 설정으로 Student 클래스에 넣을 불필요한 코드를 없애, 결합도를 낮출수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="student" 
        class="com.rivuchk.reactivekotlin.springdi.aop_student_assignment.Student">
        <constructor-arg ref="assignment"/>
    </bean>

    <bean id="assignment" 
        class="com.rivuchk.reactivekotlin.springdi.aop_student_assignment.Assignment" />

    <!--1-->
    <bean id="faculty" 
        class="com.rivuchk.reactivekotlin.springdi.aop_student_assignment.Faculty" />

    <aop:config><!--2-->
        <aop:aspect ref="faculty"><!--3-->
            <aop:pointcut
                    id="assignment_complete"
                    expression="execution(* *.completeAssignment(..))"/><!--4-->

            <aop:after
                    pointcut-ref="assignment_complete"
                    method="evaluateAssignment" /><!--5-->
        </aop:aspect>
    </aop:config>

</beans>

다음은 스프링에서 xml 설정을 사용하는 ClassPathXmlApplicationContext 클래스를 이용하여 실행하고 있다.

  • 결과에서 보다 시피 Student 클래스의 completeAssignment 메소드를 실행할때 마다. faculty.evaluateAssignment메소드가 실행되는 것을 볼 수 있다.
package com.rivuchk.reactivekotlin.springdi.aop_student_assignment

import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext


class SpringdiApplication

fun main(args: Array<String>) {
    val context = ClassPathXmlApplicationContext(
            "META-INF/spring/student_faculty.xml"
    )
    val student = context.getBean(Student::class.java)
    student.completeAssignment("One")
    student.completeAssignment("Two")
    student.completeAssignment("Three")

    context.close()
}

/* 결과
Performing Assignment One
This assignment is avaluated and given 5 points
Performing Assignment Tow
This assignment is avaluated and given 0 points
Performing Assignment Three
This assignment is avaluated and given 9 points
*/

스프링 부트로 Rest API 만들기

스프링은 프로젝트를 구성하는게 번거롭고 어려운 부분인데, 스프링부트는 그러한 부분을 자동으로 작성하여 개발시간을 단축시켜 준다. 

  • start.spring.io 로 이동한다.
  • 원하는 프로젝트에 맞게 설정을 하고 Generate Projects를 클릭한다. 
  • 환경설정이 되어 있는 프로젝트가 다운로드되어 진다.

 

다음 코드는 다운로드 되어 자동으로 생성된 메인 이다.

package com.rivuchk.reactivekotlin.RestExample

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class RestExampleApplication

fun main(args: Array<String>) {
    runApplication<RestExampleApplication>(*args)
}

Todo 클래스를 만들어준다.

package com.rivuchk.reactivekotlin.RestExample

import javax.validation.constraints.NotBlank

data class Todo (
        var id:Int = 0,
        var todoDescription:String,
        var todoTargetDate:String,
        var status:String
)

REST API를 사용할 스프링의 Controller 클래스를 구현해준다. 

  • 메인을 실행하면 스프링 부트의 웹어블리케이션이 실행된다.
  • 127.0.0.1:8080/api/get_todo 컨트롤러가 실행되는데 기본적으로 @RestController 선언일 경우는 json 형태로 결과가 리턴된다.
  • 결과에서 보듯이 Todo 인스턴스가 json 형태로 변경되어 브라우저에 보이게 된다.
package com.rivuchk.reactivekotlin.RestExample

import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api")
class TodoController {
    @RequestMapping("/get_todo")
    fun getTodo() = Todo(1,"TODO Project","31/11/2017","Running")
}

/* 결과
{"id" 1, "todoDescription":"TODO Project", "todoTargetDate" : "31/11/2017", "status":"Running"}
*/