엄청난 요약본이다.

토비의 스프링이 너무 정리가 잘 돼있어서.... 음...

결국 받아쓰기가 된 것 같은 느낌이 ㅠㅠ

 

AOP

 

 이제 AOP 에 대해서 알아보자. AOP는 IoC/DI, 서비스 추상화와 더불어 스프링의 3대 기반기술의 하나다. AOP를 바르게 하려면 AOP의 필연적인 등장 배경과 스프링이 도입한 이유, 장점을 제대로 이해해야 한다. 스프링에 적용된 가장 인기 있는 AOP의 적용 대상은 바로 "선언적 트랜잭션 기능" 이다. 

 

제대로 들어가기전에 다이내픽 프록시와 팩토리 빈에 대해서 알고 넘어가자.

 

 단순히 확장성을 고려해서 한 가지 기능을 분리한다면 전형적인 전략 패턴으르 사용하면 된다. 하지만 우리가 원하는건 트랜잭션을 적용한다는 사실 자체가, 우리가 짠 코드에는 보이지 않는 것이다. (메소드 안에 안 드러나게)

 

 트랜잭션이라는 기능은 사용자 관리 비즈니스 로직과는 성격이 다르기 때문에 아예 그 적용 사실 자체를 밖으로 분리할 수 있다.

 

 핵심기능은 부가기능을 가진 클래스의 존재 자체를 모른다. 그래서 부가기능은 마치 자신이 핵심기능을 가진 클래스인것처럼 꾸며서, 클라이언트가 자신을 거쳐서 핵심기능을 사용하도록 만들어야 한다. 

 

 이렇게 마치 자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것을 대리자, 대리인과 같은 역할을 한다고 해서 프록시(proxy) 라고 부른다. 

 

클라이언트 -> 프록시 -> 타깃

 

프록시의 특징은 타깃과 같은 인터페이스를 구현했다는 것과 프록시가 타깃을 제어할 수 있는 위치에 있다는 것이다.

 

데코레이터 패턴.

 

 간단히 설명하자면, 프록시 개수를 확정하지 않는, 프록시 개념을 이용하는 프록시 비스무레한 패턴이다. (물론 둘은 다르다.)

InputStream 이라는 인터페이스를 구현한 타깃인 FileInputStream에 버퍼 읽기 기능을 제공해주는 BufferedInputStream 이라는 데코레이터를 사용하는 예시를 보자.

 

InputStream is = new BufferedInputStream(new FileInputStream("a.txt"));

 

프록시로 동작하는 각 데코레이터는 위임하는 대상에도 인터페이스로 접근하기 때문에 자신이 최종 타깃으로 위임하는지, 아니면 다음 단계의 데코레이터 프록시로 위임하는지 알지 못한다.

 

프록시 패턴.

 

일반적으로 사용하는 프록시라는 용어와 디자인 패턴에서 말하는 프록시 패턴은 구분할 필요가 있다. 전자는 클라이언트와 사용 대상 사이에 대리 역할을 맡은 오브젝트를 두는 방법을 총칭한다면,

 

 후자는 프록시를 사용하는 방법 중에서 타깃에 대한 접근 방법을 제어하려는 목적을 가진 경우를 가리킨다.

 

프록시 패턴의 활용 예 중 하나는, 타깃 오브젝트를 미리 만들지 않고, 사용하는 시점에 만들고 싶다. 하지만 타깃 오브젝트에 대한 레퍼런스가 미리 필요하다. 요런 상황에, 프록시 패턴을 적용해서 실제 타깃 오브젝트 대신 프록시를 넘겨준다. 프록시의 메소드를 통해 타깃을 사용하려고 하면, 그 때 프록시가 타깃 오브젝ㅌ르르 생성하고 요청을 위임해준다.

 

 또는 원격 프로젝트를 이용하는 경우... 다른 서버에 존재하는 오브젝트를 사용해야 한다면, 원격 오브젝트에 대한 프록시를 만들어주고 클라이언트는 마치 로컬에 존재하는 오브젝트를 쓰는 것처럼 프록시를 사용하게 할 수 있다.

 

 또튼 타깃에 대한 접근권한을 제어하기 위해 프록시 패턴을 사용할 수 있다. 프록시의 특정 메소드를 사용하려고 하면 접근이 불가능 하다고 예외를 던진다.

 Collections의 unmodifiableCollection() 을 통해 만들어진 오브젝트가 전형적인 접근권한 제어용 프록시라 볼 수 있다.

 

 프록시와 데코레이터는 상당히 유사하지만, 프록시는 코드에서 자신이 만들거나 접근할 타깃 클래스 정보를 알고있는 경우가 많다. 

 

 

 

 다이내믹 프록시

 

프록시를 일일이 만드는 것은 귀찬핟. 인터페으스를 구현해서 클래스를 새로 정의하지 않고 프록시를 편리하게 사용할 방법은 없을까? 있다. 자바에서는 java.lang.reflect 패키지 않에 프록시를 손쉽게 만들 수 있도록 지원해주는 클래스들이 있다. 일일이 프록시 클래스를 정의하지 않고도 몇 가지 API를 이용해 프록시처럼 동작하는 오브젝트를 다이내믹하게 생성하는 것이다.

 

 프록시는 다음의 두 가지 기능으로 구성된다.

 - 타깃과 같은 메소드를 구현하고 있다가 메소드가 호출도면 타깃 오브젝트로 위임한다.

 - 지정된 요청에 대해서는 부가기능을 수행한다.

 

 

 리플렉션

 다이내믹 프록시는 리플렉션 기능을 이용해서 프록시를 만들어준다. 

 자바의 모든 클래슨느 그 클래스 자체의 구성정보를 담은 Class 타입의 오브젝트를 하나씩 갖고 있다. 클래스이름.class 라고 하거나 오브젝트의 getClass() 메소드를 호출하면 클래스 정보를 담은 Class 타입의 오브젝트를 가져올 수 있다.

 

 

리플렉션 API 메소드 중에서 메소드에 대한 정의를 담은 Method라는 인터페이스를 이용해 메소드 호출을 해보자.

 

Method 인터페이스에 정의된 invoke() 메소드를 사용하자.

 

 public Object invoke(Object obj, Object... args);

 

Method lengthMethod = String.class.getMethod("length");

int length = lengthMethod.invoke(name);

 

 

 

 다이내믹 프록시는 "프록시 팩토리" 에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트다. 다이내믹 프록시 오브젝트는 타깃의 인터페이스와 같은 타입으로 만들어진다.

 

 클라이언트는 다이내믹 프록시 오브젝트를 타깃 인터페이스를 통해 사용할 수 있다. 이 덕분에 프록시를 만들 때 인터페이스를 모두 구현해가면서 클래스를 정의하는 수고를 덜 수 있다. 프록시 팩토리에게 인터페이스 정보만 제공해주면 해당 인터페이스를 구현한 클래스의 오브젝트를 자동으로 만들어주기 때문이다.

 

 다이내믹 프록시가 인터페이스 구현 클래스의 오브젝트는 만들어주지만, 프록시로서 필요한 부가긴으 제공 코드는 직접 작성해야 한다. 부가기능은 프록시 오브젝트와 독립적으로 InvocationHandler를 구현한 오브젝트에 담는다. InvocationHandler 인터페이스는 다음과 같은 메소드 한 개만 가진 간단한 인터페이스다.

 

에라이...

 

 

간단히 InvocationHandler 의 구현 클래스를 정리하고 넘어가자...

 

public class UppercaseHandler implements InvocationHandler {

 Hello target;

 

 public UppercaseHandler(Hello target) {

  this.target=target;

 }

 public Object invoke(Object proxy, Method method, Object[] args) {

  String ret = (String) method.invoke(target,args);

 return ret.toUpperCase();

 }

}

 

다이내믹 프록시의 생성은 Proxy클래스의 newProxyInstance() 스태틱 팩토리 메소드를 이용하면 된다.

 

Proxy.newProxyInstance( getClass().getClassLoader(), newClass[] {Hello.calss}, new UppercaseHandler(new HelloTarget()));

 

....

첫 번째 파라미터는 동적으로 생성되는 다이내믹 프록시 클래스의 로딩에 사용할 클래스 로더,

두번 째는 구현할 인터페이스,

세 번재는 부가기능과 위임 코드를 담은 Invocationhandler이다.

 

 

Class의 이름을 알고 있다면 Class.forName("java.util.Date").newInstance() 로 기본 생성자를 호출해서 값일 받아올 수 있다.

리플렉션 API이다.

 

 

 

팩토리 빈

 

 팩토리빈이란 스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈을 말한다.

 

 

다이내믹 프록시를 만들어주는 팩토리 빈

 

 Proxy의 newProxyInstance() 메소드를 통해서만 생성이 가능한 다이내믹 프록시 오브젝트는 일반적인 방법으로는 스프링의 빈으로 등록할 수 없다. 대신 팩토리 빈을 사용하면 다이내믹 프록시 오브젝트를 스프링의 빈으로 만들어줄 수가 있다.

 

 

 다이내믹 프록시를 생성해주는 팩토리 빈을 사용하는 방법은 여러 가지 장점이 있다. 한 번 부가기능을 가진 프록시를 생성하는 팩토리 빈을 만들어두면 타깃의 타입에 상관없이 재사용 할 수 있기 때문이다.

 

 이렇게 프록시 팩토리 빈을 사용하면 1. 인터페이스를 구현하는 프록시 클래스를 일일이 만들 필요가 없어짐. 2. 부가적인 기능이 여러 메소드에 반복적으로 나타나게 되는 코드 중복이 없어짐.

 

 하지만 단점이 있다.

 1. 비슷한 프록시 팩토리 빈의 설정이 중복되는건 막을 수 없음. (트랜잭션과 같이 비즈니스 로직을 담은 클래스의 메소드에 적용한다면...)

2. 하나의 타갯에 여러개의 부가기능을 넣는것도 애매함.

3. 프록시 팩토리 빈 개수만큼 InvocationHandler 오브젝트가 생김.

 

 

 

스프링의 프록시 팩토리 빈

 

이제 스프링의 프록시 팩토리 빈을 살펴보자.

 

스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어를 제공한다.

 

생성된 프록시는 스프링의 빈으로 등록돼야 한다. 스프링은 프록시 오브젝트를 생성해주는 기술을 추상화한 "팩토리 빈"을 제공해준다.

 

 여기는 InvoacationHandler가 아니라, MethodInterceptor 인퍼에스를 구현해서 부가기능을 만든다.

InvocationHandler의 invoke() 메소드는 타깃 오브젝트에 대한 정보를 제공하지 않는다, 따라서 타깃은 InvocationHandler를 구현한 클래스가 직접 알고 있어야 한다. 반면 MethodInterceoptor의 invoke() 메소드는 ProxyFactoryBean으로부터 타깃 오브ㅔㄱ트에 대한 정보도 함께 제공 받는다. 그 차이 덕분에 MethodInterceptor는 타깃 오브젝트에 상관 없이 독립적으로 만들어질 수 있다. 따라서 여러 프록시에서 함께 사용할 수 있고, 싱글톤 핀으로 등록 가능하다.

 

 

 어드바이스: 타깃이 필요없는 순수한 부가기능.

 

 MethodInterceptor 처럼 타깃 오브젝트에 적용하는 부가기능을 담은 오브젝트를 스프링에서는 어드바이스(advice) 라고 부른다. 어드바이스는 타깃 오브젝트에 "종속 되지 않는" 순수한 부가기능을 담은 오브젝트다.

 

 포인트컷: 부가기능이 적용될 메소드 선정 알고리즘을 담은 오브젝트. 

 

 MethodInterceptor 오브젝트는 여러 프록시가 공유해서 사용할 수 있다. 따라서 타깃 정보를 갖고 있지 않다. 그 덕분에 MethodInterceptor 는 스프링의 싱글톤 빈으로 등록할 수 있다. 

 그렇다면 타깃에 대한 벙보가 없는 MethodInterceptor는 어떻게 부가기능을 적용할 메소드를 "선별" 할 수 있을까?

스프링은 메소드 선정 알고리즘을 담은 오브젝트를 포인트컷이라고 부른다.

 

 

 

 

 

스프링의 AOP

 

위와 뭐가 다르냐? 방금전 위는 스프링의 프록시 팩토리 빈이었다.

 부가기능이 타깃 오브젝트마다 새로 만들어지는 문제는 스프링의 ProxyFactoryBean의 어드바이스를 통해 해결됐다.

남은 것은 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정 정보를 추가해주는 부분이다.

 

 반복적인 프록시의 메소드 구현을, 코드 자동생성 기법을 이용해 해결했다. -> 반복적인 ProxyFactoryBean 설정 문제는 설정 자동방법 기법으로 해결하면 안되나? -> 또는 실제 빈 오브젝트가 되는 것은 ProxyFactoryBean을 통해 생성되는 프록시 그 자체이니까 프록시가 자동으로 빈으로 생성되게 할 수는 없을까?

 

빈 후처리기를 이용한 자동 프록시 생성기

 

  스프링은 컨테이너로서 제공하는 기능 중에서 변하지 않는 핵심적인 부분 외에는 대부분 확장할 수 있도록 확장 포인트를 제공해준다.

 

 그중에서 관심을 가질 만한 확장 포인트는 BeanPostProcessor 인터페이스를 구현해서 만드는 "빈 후 처리기" 이다.

 빈 후처리기는 말 그대로 스프링 빈 오브젝트로 만들어지고 난 후에, 빈 오브젝트를 다시 가공할 수 있게 해준다.

 

 여기서는 스프링이 제공하는 빈 후처리기 중의 하나인 DefaultAdvisorAutoProxyCreator 를 살펴보겠다. 이름을 보면 알 수 있듯이 DefaultAdvisorAutoProxyCreator는 어드바이저를 이용한 자동 프록시 생성기다.

 

 스프링은 빈 후처리기가 빈에 등록돼 있으면 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청한다.

빈 오브젝트의 프로퍼티를 강제로 수정할 수도 있고, 별도의 초기화 작업을 수행할 수도 있다. 심지어는 만들어진 빈 오브젝트를 바꿔칠수도 있다. 따라서 스프링으 설정을 참고해서 만든 오브젝트가 아닌 다른 오브젝트를 빈으로 등록시키는 것이 가능하다..!

 

 -> 잘 이용하면 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장하고, 프록시를 빈으로 대신 등록할 수 있다!

 

확장된 포인트컷

 사실 포인트컷은 메소드만 선별할 수 있는게 아니라, 클래스도 선별 가능하다. DefaultAdivisorAutoProxyCreator는 클래스와 메소드 선정 아록리즘을 모두 갖고 있는 포인트컷이 필요하다. 정화깋는 그런 포인트컷과 어드바이스가 결합되어 있는 "어드바이저" 가 등록돼 있어야 한다.

 

 

 자동 프록시 생성기인 DefaultAdvisorAutoProxyCreator는 등록된 빈 중에서 Advisor 인터페이스를 구현한 것을 모두 찾는다. 그리고 생성되는 모든 빈에 대해 어드바이저의 포인트컷을 적용해보면서 프록시 적용 대상을 선정한다. 빈 클래스가 프록시의 선정 대상이라면 프록시를 만들어 원래 빈 오브젝트와 바꿔치기한다.

 

 

 

AOP: 애스펙트 지향 프로그래밍

 

전통적인 객체지향 기술의 설계 방법으로는 독립적인 모듈화가 불가능한 트랜잭션 경계설정과 같은 부가기능을 어떻게 모듈화 할 것인가 연구해온 사람들은, 이 부가기능 모듈화 작업은 기존의 객체지향 설계 패러다임과는 구분되는 새로운 특성이 있다고 생각했다.

 

 그래서 이런 부가기능 모듈을 "애스펙트" 라고 부르기 시작했다.

애스펙트란 그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킨다.

 

애스펙트는 그 단어의 의미대로 애플리케이션을 구성하는 한 가지의 측면이라고 생각할 수 있다.

 

 이렇게 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하고 개발하는 방법을 애스펙트 지향 프로그래밍 또는 약자로 AOP 라고 부른다. AOP는 OOP를 돕는 보조적인 기술이지 OOP를 완전히 대체하는 새로운 개념은 아니다.

 

 AOP는 결국 애플리케이션을 다양한 측면에서 독립적으로 모델링하고, 설계하고, 개발할 수 있도록 만들어주는 것이다. 애플리케이션을 핵심 로직 대신 어느 부가기능의 관점에서 바라보고, 그 부분에 집중해서 설계하고 개발할 수 있게 해주는 것. 이렇게 특정한 관점을 기준으로 바라볼 수 있게 해준다는 의미에서  AOP를 관점지향 프로그래밍이라고 하는 것이다.

 

블로그 이미지

맛간망고소바

,

정리하다 보니 문제가 생겼다.

중요한 문장이라고 생각되면 적으려 하다보니, 책을 옮겨적는 수준이 되가고 있다.

ㅠㅠ

 

 

  

 스프링의 핵심을 담당하는 건 바로 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다.

 

1. 애플리케이션 컨텍스트와 설정정보.

 스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다.

오브젝트 단위의 애플리케이션 컴포넌트를 말한다. 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다.

 

 "스프링 빈은 오브젝트 단위의 애플리케이션 컴포넌트를 말하며, 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다."

 

 스프링에서는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리라고 부른다. 보통 빈 팩터리보다는 이를 좀 더 확장한 애플리케이션 컨텍스트(application context) 를 주로 사용한다. 애플리케이션 컨텍스트는 IoC 방식을 따라 만들어진 일종의 빈 팩토리라고 생각하면 된다.

 

 "IoC, 제어의 역전을 가능케 하는 오브젝트를 빈 팩토리라고 부른다. 대표적으로, 각 오브젝트가 사용하는 다른 오브젝트를 직접 생성하지 않는다. 스프링의 애플리케이션 컨텍스트가 직접 의존성을 설정해준다."

 

 "빈 팩터리라 말할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것이고, 애플리케이션 컨텍스트라고 말할 때는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 좀 더 부각된다고 보면 된다."

 

 애플리케이션 컨텍스트는 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어작업을 총괄한다. 애플리케이션 컨텍스트가 직접 이런 정보를 담고 있는게 아니라, 별도로 설정 정보를 담고 있는 무엇인가를 가져와 이를 활용하는 범용적인 IoC 엔진 같은 것이라고 볼 수 있다.

 

 빈 팩토리 또는 애플리케이션 컨텍스트가 사용하는 설정정보를 만드는 방법은 여러가지가 있다. 

 그 자체로는 애플리케이션 로직을 담당하지는 않지만, IoC 방식을 이용해 애플리케이션 컴포넌트를 생성하고, 사용할 관계를 맺어주는 등의 책임을 담당하는 것이 애플리케이션 컨텍스트 + 설정정보의 역할이다.

 마치 건물이 설계도면을 따라서 만들어지듯, 애플리케이션도 애플리케이션 컨텍스트와 그 설정정보를 따라서 만들어지고 구성된다고 생각할 수 있다.

 

 @Configuration 어노테이션을 해당 클래스에 붙임으로서, 스프링은 해당 클래스가 "스프링 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스" 라고 인식할 수 있다.

 

 "@Configuration: 스프링 빈 팩토리에게, 해당 클래스를 오브젝트 설정을 담당하는 클래스로 인식하게 한다."

 

그리고 오브젝트를 만들어주는 메소드에는 @Bean 이라는 어노테이션을 붙여준다.

 

 "@Bean: 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트 를 만드는 메소드라고 명시한다."

 

 

 

애플리케이션 컨텍스트는 ApplicationContext 타입의 오브젝트이다. @Configuration 이 붙은 자바 코드를 설정정보로 사용하기 위해 AnnotationConfigApplicationContext를 이용할 수 있다.

 

ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);

UserDao userDao = context.getBean("userDao", UserDao.class);

 

getBean() 메소드는 ApplicationContext가 관리하는 오브젝트를 요청하는 메소드이다.

 

 

2. 애플리케이션 컨텍스트의 동작방식

 

2.1 애플리케이션 컨텍스트의 기본 동작 방식

 

 스프링에서는 이 애플리케이션 컨텍스트를 IoC 컨테이너라 하기도 하고, 간단히 스프링 컨테이너라 부르기도 한다.

애플리케이션 컨텍스트는 ApplicationContext 인터페이스를 구현하는데, ApplicationContext는 빈 팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로, 애플리케이션 컨텍스트는 일종의 빈 팩토리인 셈이다.

 

 애플리케이션 컨텍스트는 애플리케이션에서 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정을 담당한다.

 

 ApplicationContext에는 직접 오브젝트를 생성하고 관계를 맺어주는 코드가 없다. 그런 생성정보와 연관관계 정보를 별도의 설정정보를 통해 얻는다.

 @Configuration 이 붙은 클래스는 이 애플리케이션 컨텍스트가 활용하는 IoC 설정 정보이다. 클라이언트가 해당 Bean 을 원할 때, Application Context는 @Configuration 클래스에게 요청해 Bean 을 생성 및 관리하고, 이 Bean을 요청한 클라이언트에게 전달해준다.

 (일단 클라이언트는 해당 Bean 을 사용하는 객체라고 생각하자.)

 

 

 애플리케이션 컨텍스트는 자신의 빈 목록에서 클라이언트가 요청한 이름이 있는지 찾고, 있다면 빈을 생성하는 메소드를 호출해서 오브젝트를 생성시킨 후 클라이언트에게 전달한다.

 

애플리케이션 컨텍스트를 사용할 때 얻는 장점.

 

애플리케이션 컨텍스트를 사용했을 때 얻는 장점은 다음과 같다.

더보기

 

 1. 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.

 클라이언트가 필요한 오브젝트(빈) 을 가져오기 위해 어떤 팩토리 클래스를 사용해야 하는지 알 필요가 사라진다. 실제로 해당 오브젝트를 생성하는 놈이 누군지 알필요가 없다, 스프링 관점에선  @Configuration 클래스가 뭔지 알 필요가 없다는 뜻이다.

 

 UserAddressParser 라는 오브젝트(빈) 이 있고, 이 빈을 생성하는 놈이 UserAddressParserFactory 라고 해보자.

만약 User 정보를 얻어내는 클래스가 있다고 생각해보자. UserInformationSearcher 라는 클래스가 있다.

이 UserInformationSearcher 클래느는 new UserAddressParserFactory(); 라고 하고 userAddressParserFactory.getUserAddressParser() 이런식의 흐름을 갖을 것이다.

 여기서 UserInformationSearcher 클래스가 필요한건 오직 "UserAddressParser" 인스턴스일 뿐이다. 이 걸 누가 만들었는지는 알 필요가 없다. (이런 걸 DL 이라고 부르기도 한다.)

 

2. 애플리케이션 컨텍스트는 "종합" IoC 서비스를 제공해준다.

단순히 오브젝트 생성과 다른 오브젝트와의 관계설정만 해주는 것이 아니다. 오브젝트가 만들어지는 방식, 시점과 전략을 다르게 가져갈 수도 있으며, 이에 부가적으로 자동생성, 오브젝트에 대한 후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 화룡ㅇ할 수 있는 다양한 기능을 제공한다.

 또 빈이 사용할 수 있는 기반기술 서비스나 외부 시스템과의 연동 등을 컨테이너 차원에서 제공해주기도 한다.

 

3. 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

 애플리케이션 컨텍스트의 getBean() 메소드는 빈의 이름을 이요앻 빈을 찾아준다. 타입만으로 빈을 검색하거나 특별한 애노테이션 설정이 되어 있는 빈을 찾을 수도 있다.

 

 

 

**설정정보/ 설정 메타정보 

 

 스프링 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다. 영어로 configuration 이라고 하는데, 이는 구성정보 내지는 형상정보라는 의미이다.

 스프링의 설정정보는 IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝틀르 생성하고 구성할 때 사용된다. 

 

 

** 컨테이너

 컨테이너라는 말 자체가 IoC의 개념을 담고 있다. 따라서 이름이 긴 애플리케이션 컨텍스트 대신에 스프링 컨테이너라고 부르는걸 선호하는 사람들이 있다. (애플리케이션 컨텍스트를 그냥 스프링 컨테이너라고 부르기도 한다.)

 애플리케이션 컨텍스트는 그 자체로 ApplicationContext 인터페이스를 구현한 오브젝트를 가리키기도 한다. 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 보통 여러 개가 만들어져 사용된다. 이를 통틀어서 스프링 컨테이너라고 부를 수 있다. 때로는 이런 스프링 컨테이너를 그냥 스프링이라 부르기도 한다. 따라서 "스프링에 빈을 등록하고...라고 표현할 수도 있다."

 

오브젝트의 동일성과 동등성

 

 자바에서는 두 개의 오브젝트가 완전히 같은 동일한 오브젝트라고 말하는 것과, 동일한 정보를 담고 있는 오브젝트라고 말하는 것은 분명한 차이가 있다. 전자는 동일성 비교라고 하고 후자를 동등성 비교라고 한다.

 

더보기

 두개의 오브젝트가 동일하다면 사실은 하나의 오브젝트만 존재하는 것이고, 두 개의 오브젝트 레퍼런스 변수를 갖고 있을 뿐이다. 두개의 오브젝트가 동일하지 않고 등등하다 할 때는 두 개의 오브젝트는 완전 다른 메모리상에 존재하는 것인데, 오브젝트의 동등성 기준에 따라 두 오브젝트의 정보가 동등하다고 판단하는 것일 뿐이다.

 

 equal 메소드를 따로 구현하지 않으면 Object 의 equals() 메소드를 사용하게 되는데, Object의 equals() 메소드는 두 오브젝트의 동일성을 비교해서 그 결과를 돌려준다.

 

 

2.2 싱글톤 레지스트리로서의 애플리케이션 컨텍스트

 

 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry) 이기도하다.

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

 

 태생적으로 스프링은 엔터프라이즈 시스템을 위해 고안된 기술이기 때문에 서버환경에서 사용될 때 그 가치가 있다. 실제로 스프링은 대부분 서버환경에서 사용된다.

 

 서버 환경에서 사용되기 때문에 매 요청마다 오브젝트를 생성하면 서버가 감당하기 힘들다. 따라서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍부터 사용해왔다. 서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라고 할 수 있다. 서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고 ,사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

 

 

누군가는 싱글톤을 안티패턴이라고 부른다. 흔히들 말하는 싱글톤 패턴을 제대로 구현하면, 인스턴스는 한 애플리케이션에 하나만 존재해야 하며, 이를 위해 생성자에 private 을 붙여 두번의 생성을 막고 있으며... 등등의 제약 조건이 있기때문에,

 결과적으로 여러 문제가 생긴다.

  •  싱글톤 객체가 정말 하나만 생긴 것을 어찌 보장할 것인가?
  •  싱글톤은 전역 상태를 만들 수 있으므로 옳지 못하다.
  •  싱글톤은 상속이 안되므로, 객체지향을 파괴한다.
  •  테스트하기 힘들다..

등등의 문제로 싱글톤 패턴을 싫어하는 사람들이 많다.

 

 자바의 기본적인 싱글톤 패턴의 구현 방식은 이런 단점들이 있기 때문에ㅡ 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리이다.

 스프링은 스태틱 메소드와 private 생성자를 사용해야하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글토으로 활용하게 해준다.

 

 

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

=>

 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다. 따라서 상태 관리에 주의를 기울여야 한다. 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야 한다.

 

 

 

2.3 의존 관계 주입(DI)

 

스프링을 단순히 IoC 컨테이너라고만 하면, 스프링이 서블릿 컨테이너처럼 서버에서 동작하는 서비스 컨테이너라는 뜻인지, 아니면 단순히 IoC개념이 적용된 템플릿 메소드 패턴을 이용해 만들어진 프레임워크인지, 아니면 또 다른 ioC 특징을 지닌 기술이라는 것인지 파악하기 힘들다. 그래서 스프링이 제공하는 IoC 방식의 핵심을 짚어주는 의존관계 주입(DI) 라는 이름을 사용하기 싲가했다.

 이제 DI 컨테이너라고 부른다.

 

 DI는 오브젝트 레퍼런스를 외부로브테 제공받고 이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이 핵심이다. 

 

 

의존관계란 무엇인가? 의존한다는 것은 A -> B 일때 의존대상인 B가 변하면 그것이 A에게 영향을 미친다는 뜻이다.

 

의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.

 

 

2.4 의존관계 검색과 주입

 

스프링이 제공하는 IoC 방법에는 의존관계 주입만 있는 것이 아니다. 코드에서는 구체적인 클래스에 의존하지 않고, 런타임 시에 의존관계를 결정한다는 점에서 의존관계 주입과 비슷하지만, 의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용하기 때문에 의존관계 검색(Dependency Lookup) 이라고 불리는 것도 있다.

 

 물론 자신이 어떤 클래스의 오브젝트를 이용할지 정하는 것은 아니다. 의존관계 검색은 런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성 작업은 외부 컨테이너에게 IoC 로 맡기지만, 이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용한다.

 

 예를들면 다음과 같이...

 

public UserDao() {

 AnnotationConfigApplicationContext context = new AnnotationConfigApplciationContext(DaoFactory.class);

 this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);

}

 

위에서 DaoFactory.class 와 ConnectionMaker.class 를 보고, 사실상 의존관계 다 드러나는 것 아닌가? 하는 생각을 할 수도 있다.

...맞다.

ConnectionMaker.class 는 그냥 인터페이스를 명시할 뿐이다, 실제로 어떤 인스턴스가 생성됐는지는 UserDao는 전혀 알지 못한다.

하지만 AnnotationContext 와 설정 정보인 DaoFactory.class 를 직접 언급하고 있다는 점이 마이너스.

 

의존관계 검색 방법은 코드 안에 오브젝트 팩토리 클래스나 스프링 API 가 나타나므로, 결국 애플리케이션 컴포넌트가 컨테이너와 같이 성격이 다른 오브젝트에 의존하게 되는 것이므로 그다지 바람직하지 않다.

 따라서 대개는 의존관계 주입 방식을 사용하는 편이 낫다.

 

의존관계 주입과 의존관계 검색에 결정적인 차이점은 하나 더 있다.

의존관계 주입과는 달리 의존관계 검색은, "Bean 을 필요로 하는 인스턴스가 Bean 일 필요가 없다."

 

 

 DI 를 원하는 오브젝트는 먼저 자신의 컨테이너가 관리하는 빈이 돼야 한다.

 

 

 

3. XML 을 이용한 설정

 XML을 이용해서도 DI 의존관계 설정정보를 만들 수 있다. XML은 단순한 텍스트 파일이기 때문에 다루기 쉬우며, 쉽게 이해할 수 있고, 컴파일과 같은 별도의 빌드작업이 필요 없다는 것 또한 장점이다. 환경이 달라져서 오브젝트의 관계가 바뀌는 경우에도 빠르게 변경사항을 반영할 수 있다.

 

 

 

4. Datasource 관련

자바에는 DB 커넥션을 가져오는 오브젝트의 기능을 추상화한 DataSource라는 인터페이스가 이미 존재한다. 이미 다양한 방법으로 DB 연결과 풀링(pooling) 기능을 갖춘 많은 Datasource 구현 클래스가 존재하고, 이를 가져다 사용하면 대부분 충분하다.

 

 

블로그 이미지

맛간망고소바

,

2. Conventions

Convention 에는 "관례" 라는 뜻이 있다...

일단 귀찮으니 넘어가자.

 

1) Comparison

첫 번째 문제는 Comparison 이라는 문제다.

여기서 핵심은 

operator 를 overriding 할 수 있다는 개념이다.

 

클래스 내에서 함수를 override 할 때는 operator 키워드를 붙여줄 필요는 없다.

 

Comparable 을 상속해서 , compareTo 함수를 오버라이딩 해주면 끝.

 

이 compareTo 는 operator 에서 <  ==  > 등을 비교하는데 쓰인다.

 

2) In Range

 

이 문제는 보자마자 뭔 개소리지?? 하는 느낌을 주었다.

애초에 자바에 in 같은 키워드가 없기 때문이다. (적어도 난 안쓴다.)

 

쿼리로 따지면 Between 같은 느낌의 연산자이다.

 

"연산자" 이다. < > == 같은 연사자.

 

따라서 연산자 오버라이딩을 하면 된다.

 

Range

A range defines a closed interval in the mathematical sense: it is defined by its two endpoint values which are both included in the range. Ranges are defined for comparable types: having an order, you can define whether an arbitrary instance is in the range between two given instances. The main operation on ranges is contains, which is usually used in the form of in and !in operators.

To create a range for your class, call the rangeTo() function on the range start value and provide the end value as an argument. rangeTo() is often called in its operator form ...

 

class DateRange(val start: MyDate, val endInclusive: MyDate){
    operator fun contains(item: MyDate): Boolean =
            item >= start && item <= endInclusive
}

fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
    return date in DateRange(first, last)
}

이 문제는 이런 식으로 풀면 된다.

 

DateRange 라는 클래스를 만들고, 이 클래스의 main constructor 는 start 와 endinclusive 를 받는다.

그리고 자기가 갖고 있는 start와 endEnclusive 로 넘어오는 MyDate instance 가 범위 안에 속하는지 아닌지를 반환하는 contains 함수를 정의 해준다. 

 

여기서 알 수 있는 것은, "operator" 키워드를 사용해서, 타 클래스의 연산에 관여할 수 있는 클래스를 만들 수 있다는 것이다.

또한 MyDate는 이 전 문제 (1번) 에서 재 정의한 compareTo 함수를 갖고 있다는 것을 명심하자.

 

1) compareTo  로 대소를 배교할 수 있는 클래스(MyDate) 를 만들게 됨.

2) 이제 대소를 비교할 수 있으니 contains 함수도 만들 수 있음. in 연산자를 오버라이딩 한 클래스가 대신 이것을 처리 해 줌.

 

 

3) RangeTo

 

확장 함수로 연산자 오버로딩을 할 수 있다.

이 전 2번 문제에서는 

date in DateRange(first,last) ... 방식으로 범위 안에 속하는지를 검사 했지만.

 

date in first..last 형식으로 이를 바꾸는 것이다.

 

date 도 first 도 last 도 모두 MyDate 클래스의 인스턴스이다.

 

.. 을 사용하기 위해서는 rangeTo operator 를 만들어 줘야 한다.

 

operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

class DateRange(override val start: MyDate, override val endInclusive: MyDate): ClosedRange<MyDate>

 

위와 같이, operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

로 만들면 된다.

 

2단원 부터 갑자기 난이도가 올라가는 면이 있는데, 대부분이 오버라이딩에 관련된 문제인데 함수의 원형을 알기 힘들기 때문이다.

 

일단 문서(docs)를 타고 올라가서 rangeTo 의 틀을 찾아보자.

 

 

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/index.html

 

kotlin.ranges - Kotlin Programming Language

 

kotlinlang.org

여기에 가서 rangeTo 를 찾아보자.

 

ClosedRange 타입의 인스턴스를 반환해야 함을 알 수 있다.

 

DateRange 클래스는 현재 ClosedRange<MyDate> 를 상속하고 있다.

따라서 DateRange 인스턴스를 만들어서 반환하면 되는구나? 라는 것을 생각할 수 있다.

 

여기서 다시 정리해보자.

 

1. compareTo  로 MyDate 클래스의 인스턴스는 대소비교가 가능해짐

2. DateRange 라는 클래스로 contains 를 오버로딩 하면 in 연산자를 사용할 수 있음.

3. rangeTo 를 만들면 first..last 형식으로 쓸 수 있음. rangeTo 의 반환형은 뭐다? -> ClosedRange 다.

 

그럼 쉽게 예상할 수 있을 것이다. ClosedRange 를 상속 받으면 무엇을 오버라이딩 해줘야 할까? -> contains다.

결국 우리가 2번에서 만든 contains 함수를 갖는, DateRange instance를 반환하게 하는 것이 rangeTo 다.

 

따라서..

 

date in DateRange(first,last)  = date in first..last

 

first..last = first.ragneTo(last) = DateRange(first, last)

 

4) For loop

in 은 단순히 대소 비교를 통해, 범위 안에 값이 들어가는지 확인만 하는 것은 아니다. (아닌가?_

 

for 문 안에서도 사용될 수 있다.

 

예를 들면

 

for(date in firstDate..secondDate) {

 

}

와 같이 사용된다.

 

하지만 여기서는 같은 in 키워드여도 다른 것이 필요하다. 단순한 ClosedRange 가 아닌, Iterable 을 상속해야 하며.

Iterabe 인터페이스를 구현하면, Iterator를 반환해야 하고.

 

이 Iterator를 반환하기 위해 새로운 클래스를 만들어야 한다.

 

그냥 Iterator를 anonymous object로 전달하자 무한 루프에 빠져 버렸다. (다른 요인일 수도 있다.)

anonymous object로 만들지 말고, 클래스를 하나 만들고, 인스턴스로 Iterator를 하나 만들어서 뱉어주니 잘 작동했다.

 

 

class DateRange(val start: MyDate, val end: MyDate): Iterable<MyDate>{
    override fun iterator(): Iterator<MyDate> = DateIterator(this)
}

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
    var current: MyDate = dateRange.start
    override fun next(): MyDate {
        val result = current
        current = current.nextDay()
        return result
    }
    override fun hasNext(): Boolean = current <= dateRange.end
}

fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
    for (date in firstDate..secondDate) {
        handler(date)
    }
}

 

이런 식으로 작서 ㅇ되는데...

 

firstDate..secondDate 를 보면 알 수 있듯이, rangeTo 연산자를 오버라이딩 했다.

rangeTo 함수는 이전 3번처럼 DateRange 인스턴스를 반환하다. DateRange 는 Iterable 을 구현한 클래스이다.

 

Iterator 는 next 와 hasNext 함수를 상속해줘야 한다. 살짝 IntelliJ의 자동 완성 기능의 도움을 받으면 쉽게 오버라이딩 할 함수의 틀을 만들 수 있다.

 

5) Operator overloading

 

오버로딩과 오버라이딩의 차이는 잘 알 것이다.

 

보통 부모의 함수를 상속받아, 자기 껄로 대체 하는것을 오버라이딩이라고 하고.

함수 이름은 같은데 매개변수가 다른 여러 함수를 만드는 것을 오버로딩이라고 한다.

 

오버라이딩은 런타임 시에, 오버로딩은 컴파일 시에 정의 된다... 

 

간략히 여기 까지만 설명 하고.

 

연산자 오버라이딩에서 왜 갑자기 연산자 오버로딩이 제목이 된 걸까? 이를 활용하는 것이 이번 문제의 핵심이자. 정말 재밌는 문제다.

 

난 못 풀었다.

 

6) Destructing declarations.

 

자바스크립트에 "구조 분해 할당" 이란 것이 있다.

 

그거랑 비슷 한건데,

fun isLeapDay(date: MyDate): Boolean {

val (year, month, dayOfMonth) = date

// 29 February of a leap year
return year % 4 == 0 && month == 2 && dayOfMonth == 29
}

 

이렇게 있을 때 MyDate 의 필드 값들이 year, month, dayOfMonth 에 분해돼 들어가는 느낌을 주는... 

 

이것 또한 연산자로 지정해 줘야 한단다...

 

다음과 같다.

 

class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) {
    operator fun component1(): Int {
        return year
    }
    operator fun component2(): Int {
        return month
    }
    operator fun component3(): Int {
        return dayOfMonth
    }
}

componentN() 을 정의해주면 된다.

N 에 1~N 까지으 숫자가 들어간다.

 

6) Invokable

 

별 거 없는 문제인데 한 번 기억해두면 좋을 요소가 하나는 있다.

 

class Invokable {
    var numberOfInvocations: Int = 0
        private set
    operator fun invoke(): Invokable {
        numberOfInvocations++
        return this
    }
}

fun invokeTwice(invokable: Invokable) = invokable()()

짜잔.

 

invoke 라는 연산자를 오버라이딩하면..

인스턴스에다가 () 를 붙여서 함수처럼 쓸 수 있다...  위에 invokeTwice 는 말 그대로 invoke 두번 한거다.

 

여기서 private set 부분을 한번 보고 넘어가자.

 

 

Kotlin 에서는 클래스 안에 var a = 3 이런 식으로 "필드" 를 선언해 놓으면 자동으로

get 과 set 함수가 생긴다.

 

자바에서는 아마 @Getter @Setter 등을 붙이거나. (lombok 사용 시에) 직접 만들어야 했을 것이다.

 

만약 상수 선언을 의미하는 val 을 쓴다면? setter 가 생기지 않는다.

 

위에서 필드 선언 부분 아래에서 private set 부분을 확인할 수 있다.

set 함수를 private으로 만든 것이다. var 로 선언했기 때문에 값이 변할 수는 있지만, 이 값을 외부에서 직접 특정 값으로 수정하지는 못하게 한 것이다.

 

이 부분만 집고 넘어가자.

 

2단원은 이렇게 마친다.

 

블로그 이미지

맛간망고소바

,

1. Introduction

1.1 Hello, World!

 

코틀린에서는 function을 다음과 같이 선언할 수 있다.

fun start(): String = "OK"

String 을 반환형으로 사용하는, 파라미터를 받지 않는 start 함수다.

 

https://kotlinlang.org/docs/reference/basic-syntax.html#defining-functions

 

Basic Syntax - Kotlin Programming Language

 

kotlinlang.org

링크가 걸려있다. function 을 여러 방법으로 선언할 수 있다.

 

1.2 Java to Kotlin conversion

자바 코드를 자동으로 코틀린 코드로 바꿔주는 것을 보여주는 예제다.

그냥 JavaCode를 복사해서 붙여넣으면 끝이다. Java to Kotlin Converter의 힘을 감상하기만 하면 끝.

 

1.3 Named arguments

함수를 직접 써보는 예제다. 코틀린은 함수에 파라미터를 넘길 때, 파라미터 이름을 지정해서 넘길 수 있다.

이름을 지정 안하면 순서대로 넘어간다.

 

fun joinOptions(options: Collection<String>) = options.joinToString(", ", "[", "]")

 

코틀린의 Collection은 기본적으로 자바와 같다고 보면 된다. 여기서 더 편리한 기능 확장이 추가돼 있다.

콜렉션이 넘어오면, 인스턴스로 함수를 호출한다.  위와 같이 호출 할 수도 있고,

 

fun joinOptions(options: Collection<String>) = options.joinToString(separator = ", ",
prefix = "[",
postfix = "]")

 

이런 식으로도 호출 할 수 있다.

 

또한 함수를 선언 시에 Default Argument 를 사용할 수 있다.

 

fun 함수이름(a: Int = 1, b: Int = 2) = a + b

이런 식으로 해 놓으면 파라미터에 아무것도 넘어가지 않을 때, 기본 값을 사용한다.

 

"코틀린은 함수 선언시에 기본 값을 지정할 수 있다. 또한 함수 호출 시에 파라미터 이름과 함께 넘길 수 있다."

 

1.4 Default Arguemnts

위에 설명한, 기본 값 지정에 대한 문제이다. 나처럼 풀면 이렇게 된다.

 

fun foo(name: String, number: Int, toUpperCase: Boolean) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun foo(name: String, number: Int) = foo(name, number, false)

fun foo(name: String) = foo(name, 42)

fun foo(name: String, toUpperCase: Boolean) = foo(name, 42, toUpperCase)

 

fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

하지만 코틀린은 "기본 값을 지정" 할 수 있다.

다음과 같이 간단히 작성 할 수 있다.

 

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

 

1.5 Lambdas

람다는 자바에서도 8 버전에서부터 지원하는 것으로 알고 있다.

코틀린 또한 당연히 람다를 지원한다.

 

함수를 파라미터로 받는 함수를, Higher order Function이라고 한다.

이런 함수에, 파라미터를 넘겨줄 때 람다를 쓰면 아주 깨끗한 코드를 만들 수 있다. (사람마다 코드의 미적 기준은 다를 수 있다.)

 

아래가 그 예시이다.

 

// Lambdas are code blocks enclosed in curly braces.
items.fold(0, { 
    // When a lambda has parameters, they go first, followed by '->'
    acc: Int, i: Int -> 
        print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // The last expression in a lambda is considered the return value:
    result
})

 

 

함수 타입은 다음과 같이 나타낸다.

(R, T) -> R

 

코틀린은 변수를 쓸 때  var a: Int = 1  이런 식으로 쓸 수 있다. 

함수 타입이 (R, T) -> R 과 같은 방식이란 말은

 

var sampleFunction: (Int, Int) -> String = {
a: Int, b: Int -> "the result is $a + $b"
}

print(sampleFunction(1, 2))

결과: the result is 3

 

요런 식으로 쓸 수 있다는 말이다.

 

코틀린은 특이한 문법이 있다.

가장 마지막에 들어가는 "함수 인자" 는 parenthesis 밖으로 뺄 수 있다. (paraenthesis = ( or ) )

 

이게 무슨 개소린가 싶기도 한데...

 

fun higherOrderFunctionDefault(a: Int, b: Int, f: (Int, Int) -> Int): Int {
return f(a, b)
}

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

print(result) -> 결과는 3이 나온다.

 

위와 같은 경우에.

 

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

이 부분을

 

var result = higherOrderFunctionDefault(1,2){a: Int, b: Int -> a + b}

 

이렇게 쓸 수 있다는 말이다. 심지어 IntelliJ Edu 에서는 뒷 부분을 더 선호한다. ??? 

 

1.6 Strings

이건 그냥 답 봤다. 도저히 못 찾겠다.

String Template 에 관련된 문제인데... Docs 링크가 걸려있긴 하지만, 전혀 몰랐던 Template 적용 방식이 있었다.

자바 때부터 전해내려온 것인지, Kotlin에만 있는 것인지조차 잘 모르겠다. 이 부분은 좀 약하다.

 

1.7 Data Classes

class 앞에 data 를 붙이면, lomobk 의 @Data 와 비슷한 작동을 한다.

 

equals/hashCode, toString

 

함수를 자동으로 만들어 준다.

 

문제 자체는 간단하지만, 여기서 Kotlin의 클래스 개념을 잡고 넘어가야 한다.

 

오른쪽에 있는 링크를 따라가 차분히 읽어보도록 하자.

많은 것들을 다 적을 순 없고 간단히 특이한 점만 정리해 놓겠다.

 

1. 코틀린의 클래스 안의 필드들은, 반드시 초기화 돼야 한다.

2. main counstructor  라는 놈이 있다.

3. 일단 상속되는 놈이 먼저 생성되는건 자바와 똑같다. 따라서 주의할 점도 똑 같다.

4. 클래스 안에서의 문(Statement) 들은 무조건 순서대로 시작된다. 단 constructor는 예외 케이스가 있다.

   main constructor 가 있다면, 클래스 안쪽에 선언하는 놈들은 다 secondary constructor 인데,

   constructor keyword 를 이용해서 생성자를 추가할 수 있다. 이 때 반드시, main constructor을 this(name) 이런 식으로 호출해 줘야한

   다... 

ex)

class Constructors(var a: Int = 3) {
	constructor(i: Int, b: Int): this(i) {
  		println("Constructor")
	}
	init {
  		println("Init block")
	}
}

따라서 위와 같은 경우 init 이 먼저 시작된다. secondary constructor 보다는 init 이 우선시 된다.

main constructor 가 없다? 없을 수는 없다. 사실 암시적으로 갖고 있는 것이다. 마치 자바의 기본 생성자가 있듯이... 만약 main constructor 가 없다면, 암시적으로 호출하고 있다고 생각해야 한다.

 

그렇다면 순서대로라는 것은 무슨 의미인가?

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

위와 같은 경우 반드시 순서대로 시작된다.  (위의 also 는 kotlin 의 확장 함수 중 하나이다. 자기 자신을 매개변수로 넘기고, 자기 자신을 return 한다.

val person: Person = getPerson().also {
	print(it.name)
	print(it.age)
}

마지막엔 자동으로 자기 자신을 return 한다.

 

5. main constructor 에는 연산을 넣을 수 없다. 따라서 연산을 위해서는 클래스 안에 init 을 사용하도록 해라.

6. class 는 클래스 : 부모클래스() 와 같이 상속. interface  는 클래스: 언터페이스 와 같이 상속한다.

7. 상속 하려면 open 키워드를 붙여야 한다.

 

다른게 무지 많다. 자세히 알아보지도 않고 대충 정리만 해놨다. 무엇보다 1번이 가장 애매한데... 초기화 안하면 컴파일러에서 빽빽돼서 저리 적어 놓았다.

 

1.8 Nullable Type

꽤나 중요한 문제이다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

위가 정답이다. 깔끔하고 멋있다. kotlin 의 ? 의 힘이다.

자료형에 ? 가 붙어있는 것을 볼 수 있는데, null 이 들어갈 수도 있는 자료형을 나타낸다.

인스턴스에 ? 를 붙인뒤, 인스턴스의 자료 혹은 함수에 접근하려고 하면, null 을 자동으로 체크해준다.

만약 접근하려는 놈이 null 일 경우 그냥 null 을 반환한다. 그래서 마지막에 email != null 을 체크하는 것이다.

 

처음에 if 문을 하나만 써서 해결하라는 문구를 보고 나는 다음과 같이 문제를 해결했다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    if(client != null
            && message != null
            && mailer != null
            && client.personalInfo != null
            && client.personalInfo.email != null)
        mailer.sendMessage(client.personalInfo.email!!, message!!)
}

 굳이 이 흉한 코드를 올려놓은 이유는, !! 또한 키워드 중 하나이기 때문이다. ? 자료형의 변수에, !! 를 뒤에 붙여주면.

"이 값은 null 일리가 없다!" 라는 것을 의미한다. 따라서 String? 자료형의 변수는 String 으로 변해서 파라미터로 전달되는 효과가 있다.

 

1.9 smart casts

 

제목 그대로 "똑똑한 형변환" 을 의미한다. 일단 kotlin 에서는 if 문과 when 문을 사용해서 변수에 값을 할당할 수 있다는 것을 알고 넘어가자.

var a = 3;

var b = if(a == 3) 4 else 5 

이런 식으로 쓸 수 있다는 말이다. (많이 신기함..)

 

위에서는 If문과 When 문이라고 했지만.. Kotlin 에서의 If 는 "Expression" 이다.

이게 뭔 의미냐면, 반환형이 있다는 말이다.

If 표현의 마지막 문장은 자동으로 "반환 값" 이 된다.

 

따라서 Kotlin 에는 ? a : b 표현이 없다. If 로 충분하기 때문이다.

 

when 은 Java에서의 Switch 랑 비슷한데 여러모로 다른 점이 있다. 

fun eval(expr: Expr): Int =
        when (expr) {
            is Num -> expr.value
            is Sum -> eval(expr.left) + eval(expr.right)
            else -> throw IllegalArgumentException("Unknown expression")
        }

위를 보면 알 수 있듯이, when 은 is 로 타입을 확인 할 수 있다. 자바로 따지면 instance of 같은 느낌이다.

왜 "똑똑한 형변환" 일까?

위에서 Expr 이 인터페이스라고 하자. 인터페이스를 구현한 클래스 A : Num , B : Sum 이 있다.

그럼 eval 함수에는 A 도, B 도 들어갈 수 있겠지? 

 

이 때 when 의 is 로 타입을 구분해서, 사용할 수 있다는 말이다.

 

잘 보면 expr.value, expr.left expr.right 라고 저녛 다른 것들을 사용 하는 것을 볼 수 있다.

 

interface 가 when 절의 is 를 통과했을 때, "자동으로" is 에서 비교한 타입으로 형변환 해서 넘어간다.

따라서 is Num 을 통과한 녀석은 Num 자료형으로 자동으로 변경돼서 들어간다.

똑똑하다는 말이다.

 

when의 is 는 타입을 비교할 때 쓴다, 보통은 왼쪽에 "조건식" 이 들어간다.

 

when (num) {

  1 -> "하나"

 2 -> "둘"

else -> "둘 초과"

}

이런 식으로 사용할 수 있다.

 

만약 when 에 아무런 인자도 넘어가지 않는다면, 그냥 "순수한 조건식" 만을 이용해서 사용할 수도 있다.

 

var a = 3

 

when {

  a == 3 -> "a는 3이 맞어"

  else -> "a 는 3이 아니여"

}

이런 식으로 말이다.

물론 위와 같이 쓸 필요는 없다. 위와 같으 2개의 분기로 나눠 질때는 if를 쓰면 된다.

 

3가지 이상의 분기로 나눠질 때 when을 사용하도록 하자.

 

1.9 Extension Function

 

확장 함수를 의미한다. 클래스의 확장 함수를 부여 할 수 있다.

그냥 클래스 안에다가 함수 만드는 것은 확장 함수가 아니다.

 

1.10 Object Expression

코틀린에서는 익명 클래스를 어떻게 사용할 수 있을까?

 

object: 인터페이스{

 override fun 함수이름() {}

}

    Collections.sort(arrayList, object: java.util.Comparator<Int> {
        override fun compare(o1: Int?, o2: Int?): Int {
            if(o1!! > o2!!) return -1 else if(o1 < o2) return 1 else return 0
        }
    })

 

https://kotlinlang.org/docs/reference/object-declarations.html

 

Object Expressions, Object Declarations and Companion Objects - Kotlin Programming Language

 

kotlinlang.org

위의 object 표현식을 활용 하도록 하자.

주의 해야할 점은, 이 anonymous object 의 scope 이다.

anonymous object는 private이나 local 에서만 사용 가능하다.

 

public 으로 노출되면, 이 object는 순식간에 Any? 타입으로 바뀌어 버린다.

(Kotlin 에서의 Any 는 Java에서의 Object와 비슷한 맥락이지만. 절대 같지는 않다.)

 

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

    // Public function, so the return type is Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // Works
        val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}

 

1.11 SAM Conversion

 

SAM Conversions

Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method

 

1.12 Extension On Collection

 

문제 자체는 아주 간단하다. 

 

제목 그대로 Collection을 어떻게 확장해서 사용하고 있는지를 알려준다.

 

차트를 보면 3단원에 제대로 Collection을 다룰 예정인듯 하니 기다리도록 하자.

 

 

 

 


 

이렇게 1단원을 끝냈다.

굳이 정리할 필요는 없겠지만, 기본 문법이니 만큼 적어두는 것이 좋다고 생각했다.

 

이 포스트의 내용 만으로는 완벽히 Kotlin을 이해하기 힘들다.

글을 쓰고 있는 사람부터가 이해하고 쓰는게 아니고, 이해하기 위해, 혹은 이해하고 나서 훗날 기억하기 위해 적고 있기  때문이다.

 

화이팅 하자.

블로그 이미지

맛간망고소바

,

Voice Diary 를 만들기 위해, 안드로이드 어플리케이션을 만들어야 한다.

안드로이드 개발 책을 샀긴 했는데, Kotlin 에 대한 설명이 조금 부족해 보였다.

 

물론 어플리케이션을 만드는데는 충분할 정도로 설명이 돼있긴 했으나, 학습이 목적이니만큼 코틀린 부터 익숙해 지고자 한다.

 

일단 IntelliJ Edu 를 설치했고, Kotlin 부분을 학습 중이다.

살짝 어려운 감이 있는 것이, 일단 Java를 알고 있다는 전제 하에 학습이 진행된다.

 

학습은 문제를 제시하고 대뜸 풀라고 하는 형식인데, 나름 옆에 힌트가 있다.

이게 또 제법 퍼즐 푸는 느낌이 있어 재미있기도 하다.

 

IntelliJ Edu에서 제공하고 있는... 교육용 프로젝트

현재 1단원을 다 풀고 2단원을 풀고 있다.

이런 식으로 공부해 본 적은 없어서 색다른 기분이 든다.

 

Kotlin Docs로 공부하려고 했지만, 내용이 방대하여 살짝 거리감이 느껴졌는데, 이렇게 문제를 풀면서 Docs를 읽으니 체계적으로 정리가 되는 기분이다.

 

문제를 풀려면 반드시 Kotlin을 알아야 한다. 문제를 보고, 이 문제를 풀기 위한 Kotlin Docs를 찾아보고, 답을 적어서 Check를 누르면 된다.

도저히 못 풀 것 같으면 일단 틀린 다음에 Peek Solution 을 보는 것도 나쁘지 않다.

 

처음 써보는 언어라 문법 자체가 생소할 수 있다. 부끄럼 없이 Peek Solution 을 누르자. :)

-계속 누르지만 않으면 된다.-

블로그 이미지

맛간망고소바

,