https://thrift.apache.org/


아파치 쓰리프트 프레임워크는 다양한 언어로 서비스되며 , 효율적인 코드생성엔진을 갖고 소프트웨어 스택을 묶으며 다음과 같은 다양한 언어들을 지원한다  C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and 기타등등.


시작하기 

  • Apache Thrift 다운로드 

    To get started, download a copy of Thrift.

  • Apache Thrift compiler 빌드/인스톨

    You will then need to build the Apache Thrift compiler and install it. See the installing Thrift guide for any help with this step.

  •  .thrift 파일 작성 

    After the Thrift compiler is installed you will need to create a thrift file. This file is an interface definitionmade up of thrift types and Services. The services you define in this file are implemented by the server and are called by any clients. The Thrift compiler is used to generate your Thrift File into source code which is used by the different client libraries and the server you write. To generate the source from a thrift file run

                thrift --gen <language> <Thrift filename>
              

    The sample tutorial.thrift file used for all the client and server tutorials can be found here.


To learn more about Apache Thrift Read the Whitepaper



예제 


아파치 쓰리프트는 데이타타입 및 서비스 인터페이스를  간단한 정의파일안에 제공한다.파일을 통해 입력하면 

컴파일러는 교차언어간에 자연스럽게 통신할수있는  RPC 클라이언트/서버로  쉽게 사용되는 코드를 생성해준다. 

당신이 만든 객체를 이동하거나 직렬화 하기위해 행사코드를 쓰는 수고를 덜어주며, 원격메소드를 호출하여 당신이 

비지니스로직에만 집중할수있게한다.

다음 예제는 웹프런트엔드에 대해  유저객체를 저장하기위한 간단한 서비스를 보여준다.


쓰리프트 정의 파일 

service Calculator extends shared.SharedService { /** * 메소드 정의는 C 코드같다. 리턴타입 과 매개변수 , 예외를 가지고있다. * Note that argument * lists and exception lists are specified using the exact same syntax as * field lists in struct or exception definitions. */ void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), /** * 단방향 콜로써 요청한후에 리턴받는걸 무시한다. */ oneway void zip()


파이썬 클라이언트 

 # Make socket
  transport = TSocket.TSocket('localhost', 9090)

  # Buffering is critical. Raw sockets are very slow
  transport = TTransport.TBufferedTransport(transport)

  # Wrap in a protocol
  protocol = TBinaryProtocol.TBinaryProtocol(transport)

  # Create a client to use the protocol encoder
  client = Calculator.Client(protocol)

  # Connect!
  transport.open()

  client.ping()
  print 'ping()'

  sum = client.add(1,1)
  print '1+1=%d' % (sum)



자바 서버 


Initialize the Server:

    try {
      TServerTransport serverTransport = new TServerSocket(9090);
      TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));

      // Use this for a multithreaded server
      // TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));

      System.out.println("Starting the simple server...");
      server.serve();
    } catch (Exception e) {
      e.printStackTrace();
    }


The CalculatorHandler:

public class CalculatorHandler implements Calculator.Iface {

  private HashMap<Integer,SharedStruct> log;

  public CalculatorHandler() {
    log = new HashMap<Integer, SharedStruct>();
  }

  public void ping() {
    System.out.println("ping()");
  }

  public int add(int n1, int n2) {
    System.out.println("add(" + n1 + "," + n2 + ")");
    return n1 + n2;
  }

  public int calculate(int logid, Work work) throws InvalidOperation {
    System.out.println("calculate(" + logid + ", {" + work.op + "," + work.num1 + "," + work.num2 + "})");
    int val = 0;
    switch (work.op) {
    case ADD:
      val = work.num1 + work.num2;
      break;
    case SUBTRACT:
      val = work.num1 - work.num2;
      break;
    case MULTIPLY:
      val = work.num1 * work.num2;
      break;
    case DIVIDE:
      if (work.num2 == 0) {
        InvalidOperation io = new InvalidOperation();
        io.what = work.op.getValue();
        io.why = "Cannot divide by 0";
        throw io;
      }
      val = work.num1 / work.num2;
      break;
    default:
      InvalidOperation io = new InvalidOperation();
      io.what = work.op.getValue();
      io.why = "Unknown operation";
      throw io;
    }

    SharedStruct entry = new SharedStruct();
    entry.key = logid;
    entry.value = Integer.toString(val);
    log.put(logid, entry);

    return val;
  }

  public SharedStruct getStruct(int key) {
    System.out.println("getStruct(" + key + ")");
    return log.get(key);
  }

  public void zip() {
    System.out.println("zip()");
  }

}

<장점>

- 버전닝 지원
- 여러 언어에서 사용 가능하며 언어에 맞도록 소스가 생성되고, 언어간의 Serialization 가능
- Sync, Async Server API 제공
- XML 설정이 필요 없음
- Layer에 맞는 Interface를 사용 및 Customizing 구축 가능
- RPC 기능 제공 (Google Protocol Buffer에는 없는 기능)
- 서버 기능 좋음
   -- 서블릿 제공(org.apache.thrift.server.TServlet)
   -- 멀티쓰레드 지원 (org.apache.thrift.server.ThreadPoolServer : worker thread 지정)
   -- Async 지원 (org.apache.thrift.server. TNonblockingServer : single threaded)
   -- Multi-thread Half-Sync/Half-Async지원 : org.apache.thrift.server. THsHaServer
   -- Exception을 제공 (Google Protocol Buffer에는 없는 기능)
   -- Set, Map 지원 (Google Protocol Buffer에는 없는 기능)

 


 <단점>

- 자바로 코드가 생성될 때, Slf4j를 기본적으로 가지고 가며, 내부 코드는 모두 thrift lib가 필요함
- C++의 Server threading part는 Boost에 의존을 가짐
- 자바 쪽 이클립스 플러그인 없음
- Thrift lib의 api가 자주 바뀌어서, 버전 업마다 소스를 좀 보고 코딩해야 함. (인터넷 예제가 거의 없음)
   인터넷 예제랑 달라서, 컴파일 에러 안나게 하는게 귀찮음...
- XML Serialization/Deserialization 기능 없음
- 문서가 확실히 적음 (허허~)
- 생성된 코드가 Google Protocol Buffer에 비해서 보기는 편하지는 않음 (특히 C++)


http://knight76.tistory.com/1426  펌 



큐 시스템을 이용한 NPUSH-GW 개선


네이버 NAVER LABS 배상용, 임영완, 조항수, 김민곤, 김종현

Apple의 APNS(Apple Push Notification Service)나 Google의 GCM(Google Cloud Messaging) 같은 푸시 메시지 플랫폼은 모바일 서비스를 개발하면서 대부분 한 번씩은 사용해 봤을 만한 핵심 기능입니다. 최근에는 운영체제와 플랫폼 사업자별로 푸시 메시지 플랫폼이 끊임없이 개발되고 있습니다. 다양한 플랫폼을 이용해 메시지를 전송하려면 서비스 개발자가 모든 플랫폼의 명세를 이해하고 구현해야 합니다. 이런 불편 사항을 해결하려 네이버는 다양한 푸시 메시지 플랫폼을 하나의 인터페이스로 사용할 수 있도록 푸시 게이트웨이 시스템(이하 NPUSH-GW)을 개발해서 운영하고 있습니다.

이 글에서는 고성능 오픈소스 큐 시스템인 Luxun을 이용해 자체 개발한 NQueue 시스템으로 NPUSH-GW의 안정성을 향상시킨 방법을 공유하겠습니다.

NPUSH-GW란?

NPUSH-GW는 네이버의 모바일 푸시 메시지 플랫폼인 NPUSH의 컴포넌트 중 하나로, 서비스에서 요청한 푸시 메시지를 다양한 외부 푸시 플랫폼으로 전송하는 시스템이다.



3d464cc2e5a4a19ec09c9bd2b73ed8d0.png

그림 1 큐 시스템 도입 전 NPUSH-GW 구성도

2011년 11월에 네이버톡과 연동한 것을 시작으로 LINE, 네이버 앱 등을 포함해 네이버 계열사에서 개발하는 모바일 앱 대부분이 NPUSH-GW를 이용하여 푸시 메시지를 전송한다. 3년 동안 운영하면서 Apple과 Google은 물론 다양한 외부 오픈 푸시 플랫폼에 메시지를 전송하도록 개발했다.

NPUSH-GW 초기 개발 요구 사항 및 구조

초기의 NPUSH-GW는 네이버 서비스에서 요청한 푸시 메시지를 외부 푸시 플랫폼으로 전달할 때까지 소요되는 체류 시간을 최소화하도록 설계하고 구현했다. 되도록 시스템을 단순하게 하고 서버 구성을 간소하게 하도록 외부의 별도 큐 시스템을 사용하지 않고 Java에서 제공하는 LinkedBlockingQueue와 ThreadPoolExecutor의 내부 Worker Queue를 이용해 비동기 메시지를 처리하도록 구현했다. NPUSH-GW 내부의 모든 발송 모듈은 서비스별, 푸시 플랫폼별로 분리되어 있고, 각 모듈이 하나의 서비스 또는 푸시 플랫폼의 발송을 전담하는 구조다.

d3b5a164b4fbed28498e5e4083602d6e.png

그림 2 NPUSH-GW 기본 아키텍처



나머지는 

http://helloworld.naver.com/helloworld/textyle/1022966   가서 읽어보길 


Luxun

https://github.com/bulldog2011/luxun

 

1. serialize(직렬화) 란?  

 -> 메모리안의 어떠한 (추상적) 데이터구조를 연속된  bit  로  외부에 보내기위해 정렬하는것.


 2.  왜 직렬화를 해야하나? 

 -> 물리층 자체가  bit 니깐  /  가장 베이스적인 약속으로서 활용


 3. 위에서 외부란 ?

 -> 파일 / DB / 소켓을 통해 네트워크 외부등

 

4. 직렬화의 종류는 ?

 -> 데이터를 어떤식으로 구조화하냐에 따라서 달라짐. 예) 자바직렬화, 구글 프로토콜버퍼

 -> 프로토콜버퍼등은 데이터를 짧고 효율적이게 하는게 주목적.반면 자바직렬화는 낭비가 엄청남


5. 직렬화를 활용하는곳? 

-> 소켓통신, 자바객체를 파일에 저장 ( 파워포인트등의 내용을  파일(ppt) 로 저장할때도 객체직렬화)


6. 직렬화 예제  

다음은 int 형을 바이트배열로 직렬화하는 코드에요

C++ ) 

       inline static string intDATA(int  nData)
	{
		string value;
		value.clear();
		value.append("i");
		value.append((const char *)&nData, sizeof(nData));

		return value;
	}

        ...
        byte[] b = value.c_str();

nData 라는 int 형을 직렬화하는데, 일단 제일 앞에 1바이트로 "i" 문자열을 넣습니다.

그리고 int 형 데이터를 넣습니다. 나중에 저 string 에서  바이트배열로 뽑아내서 리모트로 보내면 

상대방은  1바이트를 읽어서  "i"  면  다음에 올 배열을  int 형으로 만들수있게 됩니다.  :-)

"d" 라면 다음에 올 바이트배열로 double 형을 만들어야할테지요.


Java )

ObjectOutputStream  out = new ObjectOutputStream(...);
out.writeInt(10);

 out 이라는 객체에 10이라는 int 형을 배열로 담고 있습니다.

 마찬가지로 double 형이라면 writeDouble 으로~


  writeInt 메소드의 내부에는 이렇게 되어있네요.


  public final synchronized void writeInt (int value) throws IOException
  {
      write ((byte) (0xff & (value >> 24)));
      write ((byte) (0xff & (value >> 16)));
      write ((byte) (0xff & (value >>  8)));
      write ((byte) (0xff & value));
  }

7. 직렬화 관련된 GOF 패턴 

현상태에 대한 스냅샷적인 저장  (메멘토패턴)

현 상태를 원격지로의 송신         (프록시패턴  // 프록시패턴하고 직접적인 관련은 없음)


4. 스프링 설정  :   어노테이션  vs   XML


MyBean.java 가  XML/어노테이션/자바 컨피그레이션을 통해   어떻게 객체로 만들어지는지 살펴보도록하자.


( 1 ) XML 을 통한 설정 

Beans:

스프링 컨테이너 안에 만들고싶은 자바 클래스들 ("benas") 이다.  두번째 빈은 첫번째 빈을 DI 받고 있다.

  • MyBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hmkcode.spring.beans;
 
public class MyBean {
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "MyBean [name=" + name + "]";
    }
 
}
  • AnotherBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hmkcode.spring.beans;
 
public class AnotherBean {
 
    private MyBean myBean;
 
    public MyBean getMyBean() {
        return myBean;
    }
 
    public void setMyBean(MyBean myBean) {
        this.myBean = myBean;
    }
 
    @Override
    public String toString() {
        return "AnotherBean [myBean=" + myBean + "]";
    }  
}

XML 설정 :

  • src/main/resources/config/XMLConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 
 
    <bean id="myBean" class="com.hmkcode.spring.beans.MyBean">
        <property name="name" value="xmlBean" />
    </bean>
 
    <bean id="anotherBean" class="com.hmkcode.spring.beans.AnotherBean">
                <property name="myBean" ref="myBean" />
        </bean>
</beans>

위의 설정은 아래와 동등하다.

MyBean myBean = new MyBean(); 
myBean.setName("xmlBean");

AnotherBean anotherBean = new AnotherBean();
anotherBean.setMyBean(mybean);

Run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hmkcode;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
import com.hmkcode.spring.beans.AnotherBean;
import com.hmkcode.spring.beans.MyBean;
 
public class App
{
    public static void main( String[] args )
    {
        ApplicationContext ctxXML = new ClassPathXmlApplicationContext("config/XMLConfig.xml");
        AnotherBean anotherBean = (AnotherBean) ctxXML.getBean("anotherBean");
 
        System.out.println( anotherBean);
    }
}

Output:

AnotherBean [myBean=MyBean [name=xmlBean]]


( 2 ) 어노테이션 기반 설정 

Beans + Annotation:

  • MyBean.java
  • @Component(value=“myBean2″) : this will enable Spring to detect this bean when scanning and instantiate a bean named “myBean2
  • @Value(value=“AnnotationBean”) : this equivalent to mybean2.setName(“AnnotationBean”);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.hmkcode.spring.beans;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
@Component(value="myBean2")
public class MyBean {
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    @Value(value="AnnotationBean")
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "MyBean [name=" + name + "]";
    }
 
}
  • AnotherBean.java
  • @Component(value=“anotherBean”) : this will enable Spring to detect this bean when scanning and instantiate a bean named “anotherBean”
  • @Autowired @Qalifier(“myBean2″): this equivalent to anotherBean.setMyBean(mybean2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hmkcode.spring.beans;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
 
@Component(value="anotherBean")
public class AnotherBean {
 
    private MyBean myBean;
 
    public MyBean getMyBean() {
        return myBean;
    }
 
    @Autowired
    @Qualifier("myBean2")
    public void setMyBean(MyBean myBean) {
        this.myBean = myBean;
    }
 
    @Override
    public String toString() {
        return "AnotherBean [myBean=" + myBean + "]";
    }
 
}

XML 설정: (annotation 이 사용가능하도록) 

 단지 어노테이션을 활성화 하기위해 XML 이 필요하다. XML 안에 따로 <bean> 같은 태그를 사용하지 않았다.

  • XMLConfig-Annotation.xml
1
2
3
4
5
6
7
8
9
10
11
12
<?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:context="http://www.springframework.org/schema/context"    
        xsi:schemaLocation="http://www.springframework.org/schema/beans
      <context:component-scan base-package="com.hmkcode.spring.beans"/>
      <!-- The use of <context:component-scan> implicitly enables the functionality of <context:annotation-config> -->
</beans>

Run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hmkcode;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.hmkcode.spring.beans.AnotherBean;
 
public class App
{
    public static void main( String[] args )
    {
 
        ApplicationContext ctxAnnotation = new ClassPathXmlApplicationContext("config/XMLConfig-Annotation.xml");
        AnotherBean anotherBean  = (AnotherBean) ctxAnnotation.getBean("anotherBean");
 
        System.out.println( anotherBean);
 
    }
}

Output:

AnotherBean [myBean=MyBean [name=AnnotationBean]]


( 3 ) 자바를 이용한 설정 (XML 제거됨) 

 Beans:

  • MyBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hmkcode.spring.beans;
 
import org.springframework.beans.factory.annotation.Value;
 
public class MyBean {
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    @Value(value="JavaConfigBean")
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "MyBean [name=" + name + "]";
    }
 
}
  • AnotherBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hmkcode.spring.beans;
 
import org.springframework.beans.factory.annotation.Autowired;
 
public class AnotherBean {
 
    private MyBean myBean;
 
    public MyBean getMyBean() {
        return myBean;
    }
 
    @Autowired
    public void setMyBean(MyBean myBean) {
        this.myBean = myBean;
    }
 
    @Override
    public String toString() {
        return "AnotherBean [myBean=" + myBean + "]";
    }
 
}

Cofiguration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hmkcode.spring.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.hmkcode.spring.beans.AnotherBean;
import com.hmkcode.spring.beans.MyBean;
 
@Configuration
public class JavaConfig {
 
    @Bean
    public MyBean myBean(){
        return  new MyBean();
    }
 
    @Bean(name = "anotherBean2")
    public AnotherBean anotherBean(){
        return new AnotherBean();
    }
}

Run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.hmkcode;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.hmkcode.spring.beans.AnotherBean;
import com.hmkcode.spring.config.JavaConfig;
 
public class App
{
    public static void main( String[] args )
    {
 
        ApplicationContext ctxJavaConfig = new AnnotationConfigApplicationContext(JavaConfig.class);
        AnotherBean anotherBean = (AnotherBean) ctxJavaConfig.getBean("anotherBean2");
 
        System.out.println( anotherBean);
 
    }
}

Output:

AnotherBean [myBean=MyBean [name=JavaConfigBean]]




AnnotationConfigApplicationContext 분석 

@Configuration
public class AnnotatedHelloConfig{
    @bean
    public AnnotatedHello annotatedHello(){
        return new AnnotatedHello();

}

}  

....

  ApplicationContext ctx=new AnnotationConfigApplicationContext(AnnotatedHelloConfig.class);

  AnnotatedHello hello =ctx.getBean("annotatedHello", AnnotatedHello .class);

  

위의 코드는 @Configuration 으로 <beans> 를 대신하고 
@bean 으로 자바코드에 의한 빈등록을 하게 해주는 예이다.

 AnnotationConfigApplicationContext  을 이용하여 설정 클래스를 입력받은후에 
 getBean 메소드를 통하여 내부의 특정 객체를 가져오는 소스이다.


AnnotationConfigApplicationContext  인사이드를 보도록 하자.

(분석중..) 


Reference 

http://tutorials.jenkov.com/java-reflection/annotations.html

http://tutorials.jenkov.com/java/annotations.html

http://www.javacodegeeks.com/2014/11/java-annotations-tutorial.html

http://hmkcode.com/spring-configuration-xml-annotation-java/




3. 자바 리플렉션으로 어노테이션 다루기 


자바 리플렉션을 사용함으로서 런타임에 자바클래스에 정의되있는 어노테이션 정보에 접근할수있다.


클래스 어노테이션 


Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

요렇게 접근가능하다:

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}


Method 어노테이션 


public class TheClass {
  @MyAnnotation(name="someName",  value = "Hello World")
  public void doSomething(){}
}

요렇게 접근가능하다:

Method method = ... //obtain method object
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

요렇게도 접근가능하다:

Method method = ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}


Parameter 어노테이션 


public class TheClass {
  public static void doSomethingElse(
        @MyAnnotation(name="aName", value="aValue") String parameter){
  }
}

요렇게 접근가능하다:

Method method = ... //obtain method object
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
  Class parameterType = parameterTypes[i++];

  for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("param: " + parameterType.getName());
        System.out.println("name : " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
  }
}

Method.getParameterAnnotations() 메소드는 2차원 배열을 리턴한다는걸 주목하라.

그것은 메소드파라미터 각각의 어노테이션 배열을 포함한다.


Field 어노테이션 


public class TheClass {

  @MyAnnotation(name="someName",  value = "Hello World")
  public String myField = null;
}

요렇게 접근가능하다:

Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

요렇게도 접근가능하다:

Field field = ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}





Annotation & Reflection 예제 


* 메타 annotation type


1.@documented : 이 어노테이션을 자바독이나 유사한 도구로 문서화되어져야하는데 사용

하는 엘리먼트를 지칭

2.@Target : 어노테이션 타입이 수용할 수 있는 많은 프로그램 엘리먼트를 지칭

3.@Inherited : 자동으로 상속받는 어노테이션 타입을 지칭

4.@Retention : 어노테이션된 타입이 얼마나 지속되는가를 지칭


[메소드에 지정할 어노테이션 정보들을 정의해놓은 인터페이스] 

  1. package com.journaldev.annotations;  
  2.   
  3. import java.lang.annotation.Documented;  
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Inherited;  
  6. import java.lang.annotation.Retention;  
  7. import java.lang.annotation.RetentionPolicy;  
  8. import java.lang.annotation.Target;  
  9.   
  10.   
  11. @Documented  
  12. @Target(ElementType.METHOD)  
  13. @Inherited  
  14. @Retention(RetentionPolicy.RUNTIME)  
  15. public @interface MethodInfo {  
  16.  String author() default "Pankaj";  
  17.  String date();  
  18.  int revision() default 1;  
  19.  String comments();  
  20. }  


[어노테이션을 적용한 메소드들이 정의되어 있는 클래스 예] 

  1. package com.journaldev.annotations;  
  2.   
  3. import java.io.FileNotFoundException;  
  4. import java.util.ArrayList;  
  5. import java.util.List;  
  6.   
  7. public class AnnotationExample {  
  8.  public static void main(String[] arg) {  
  9.  }  
  10.  @Override  
  11.  @MethodInfo(comments = "deprecated method",  
  12.     date = "Nov 17 2012",  
  13.     revision=1)  
  14.  public String toString() {  
  15.   return "Overriden toString method";  
  16.  }  
  17.    
  18.  @Deprecated  
  19.  @MethodInfo(comments = "deprecated method",  
  20.     date = "Nov 17 2012")  
  21.  public static void oldMethod() {  
  22.   System.out.println("old method, don't use it.");  
  23.  }  
  24.    
  25.  @SuppressWarnings({"unchecked""deprecation"})  
  26.  @MethodInfo(author = "Pankaj",  
  27.     comments = "Main method",  
  28.     date = "Nov 17 2012",  
  29.     revision = 10)  
  30.  public static void genericsTest() throws FileNotFoundException {  
  31.   List list = new ArrayList();  
  32.   list.add("abc");  
  33.   oldMethod();  
  34.  }  
  35. }  


[Reflection을 사용하여 어노테이션 정보를 분석하는 클래스 :

테스트 클래스로 클래스의 모든 메소드를 로드하고, 그 메소드 중에 어노테이션 인터페이스

에 정의된 어노테이션을 마킹한 각 메소드에 한해서만 이하 작업을 수행.]

1.해당 메소드의 모든 어노테이션을 출력.

2.해당 메소드의 어노테이션 정보를 새 어노테이션 인터페이스 변수에 담고

그 변수에서 어노테이션 값이 사용자가 지정한 값을 가지고 있다면 해당메소드 출력.

  1. package com.journaldev.annotations;  
  2.   
  3. import java.lang.annotation.Annotation;  
  4. import java.lang.reflect.Method;  
  5.   
  6. public class AnnotationParsing {  
  7.  public static void main(String[] args) {  

  8.   try {  
  9.    for(Method method :    AnnotationParsing.class.getClassLoader()  
  10.                                          .loadClass(("com.journaldev.annotations.AnnotationExample"))  
  11.                                          .getMethods()) {  

  12.      if(method.isAnnotationPresent(com.journaldev.annotations.MethodInfo.class)) {  
  13.       try {  
  14.        //iterates all the annotations available in the method  
  15.        for(Annotation anno : method.getDeclaredAnnotations()) {  
  16.         System.out.println("Annotation in Method" +method+ " : " +anno);  
  17.        }  
  18.        MethodInfo methodAnno = method.getAnnotation(MethodInfo.class);  
  19.        if(methodAnno.revision()==1) {  
  20.         System.out.println("Method with revision no 1 = " +method);  
  21.        }  
  22.          
  23.       } catch (Throwable ex) {  
  24.        ex.printStackTrace();  
  25.       }  
  26.      }  
  27.    }  
  28.   } catch (SecurityException | ClassNotFoundException e) {  
  29.    e.printStackTrace();  
  30.   }  
  31.  }  
  32. }  





Reference 


http://tutorials.jenkov.com/java-reflection/annotations.html

http://tutorials.jenkov.com/java/annotations.html

http://www.javacodegeeks.com/2014/11/java-annotations-tutorial.html

http://hmkcode.com/spring-configuration-xml-annotation-java/



2. 자바 어노테이션 이야기


2-1. 왜 어노테이션인가?


어노테이션은 J2SE 5 에 이미 소개되어졌고 , 중요한 탄생 이유는 프로그래머에게 그들의 코드에 대한 메타데이터를 자신의 코드에 직접적으로 기술할수있는것을 제공하기위함이다. 어노테이션이 만들어지기전에 프로그래머가 자신의 코드를 기술하는 방법은  transient 키워드를 사용한다던가, 주석(comments) 를 통하여, 인터페이스를 이용등등 중구난방이었다.그리고 여러타입의 어플리케이션에서 코드를 기술하는 메커니즘은 주로 XML 이 사용되어졌는데  이것은 그리 좋은 방법은 아닌게  코드와 XML (XML 은 코드가 아니다) 사이에 디커플링이 발생되고  이것은 어플리케이션을 유지보수하기 힘들게 한다.


자바 스펙에서 어노테이션은 다음에서 나타내 지고 있다 :


2-2. 소개 


어노테이션을 나타내는(설명해주는) 가장 좋은 단어는  메타데이터이다. (자신의 정보를 담고있는 데이타) . 
자바 어노테이션은 코드메타데이타이다. 

그것은 코드 그 자신에 대한 정보를 담고있다.어노테이션은 패키지,클래스,메소드,변수,파라미터등에서 사용될수있으며 자바 8 부터는  코드의 거의모든 부분에서 사용될수있다. 이것은 타입 어노테이션으로 불려지는데 아래에 살펴보겠다. 

어노테이션이 적용된 "코드" 는 직접적으로 그들의 "어노테이션" 으로부터 영향받아지지는 않고 단지 서드 시스템에 그것(주석) 에 대한 정보를 제공한다.

 어노테이션은 런타임에 활용할수있기도하며, 단지 소스레벨에서만 사용가능하게도 만들수있다. 


2-3. Consumers (소비자) 


어노테이션의 목적이 정확히 먼지, 어떻게 사용되는지 이해하는건 쉽지 않다, 그것들은 어떠한 기능적 로직을 가지고 있지 않으며 , 코드에 영향을 미치지 않는다.  어노테이션이 뭘 할까? 

이것을 위한 설명으로 난 어노테이션 컨슈머스라고 말하는게 있는데 ,  이것들은 시스템이나 서드파티 어플리케이션 (스프링같은) 으로서  어노테이션이 적용된  코드를 어노테이션 정보로부터 의존적인 여러가지 행동을 취한다.

예를들어 자바 빌트인 어노테이션경우에는 소비자는 자바가상머신(JVM) 이며 어노테이션이 적용된 코드를 실행한다.  사용자 정의 어노테이션의 경우의  예로는  JUnit, Spring 등이 있다.

그리고 소비자는 코드에서부터 어노테이션을 분석하기위해  자바 리플렉션을 이용한다. 


2-4. 유즈 케이스 

어노테이션은 다양한 목적으로 사용되는데 대표적으로는 

  •  컴파일러를 위한 정보제공 :  Java 8 @FunctionalInterface , @supresswarnings
  •  자동 문서 작성 :  generate reports automatically like Jenkins, Jira or Teamcity.
  •  코드 자동 생성 :  A good example of this is the JAXB library.
  •  런타임 프로세싱 : testing (Junit), dependency injection (Spring), logging (Log4J) ,data access (Hibernate) etc.


2-5. Java 8 에서 어노테이션 


Java 8 은 여러가지 훌륭한 기능을 가지고 왔는데 어노테이션 프레임워크도 발전되었다. 

다음 예예를 통해 확인해 보도록 하자.  

@Repeatable:  

1/**
2 * Container for the {@link CanBeRepeated} Annotation containing a list of values
3*/
4@Retention( RetentionPolicy.RUNTIME )
5@Target( ElementType.TYPE_USE )
6public @interface RepeatedValues
7{
8    CanBeRepeated[] value();
9}

Afterwards, we create the annotation itself and we mark it with the Meta annotation @Repeatable:

1@Retention( RetentionPolicy.RUNTIME )
2@Target( ElementType.TYPE_USE )
3@Repeatable( RepeatedValues.class )
4public @interface CanBeRepeated
5{
6 
7    String value();
8}

Finally we can see how to use it (repeatedly) in a given class:

1@CanBeRepeated"the color is green" )
2@CanBeRepeated"the color is red" )
3@CanBeRepeated"the color is blue" )
4public class RepeatableAnnotated
5{
6 
7}

If we would try to do the same with a non repeatable annotation:

01@Retention( RetentionPolicy.RUNTIME )
02@Target( ElementType.TYPE_USE )
03public @interface CannotBeRepeated
04{
05 
06    String value();
07}
08 
09@CannotBeRepeated"info" )
10/*
11 * if we try repeat the annotation we will get an error: Duplicate annotation of non-repeatable type
12 *
13 * @CannotBeRepeated. Only annotation types marked
14 *
15 * @Repeatable can be used multiple times at one target.
16 */
17// @CannotBeRepeated( "more info" )
18public class RepeatableAnnotatedWrong
19{
20 
21}

We would get an error from the compiler like:

1Duplicate annotation of non-repeatable type
  • Since Java 8 is also possible to use annotations within types. That is anywhere you can use a type, including the new operator, castings, implements and throws clauses. Type Annotations allow improved analysis of Java code and can ensure even stronger type checking. The following examples clarify this point:
01@SuppressWarnings"unused" )
02public static void main( String[] args )
03{
04    // type def
05    @TypeAnnotated
06    String cannotBeEmpty = null;
07 
08    // type
09    List<@TypeAnnotated String> myList = new ArrayList<String>();
10 
11    // values
12    String myString = new @TypeAnnotated String( "this is annotated in java 8" );
13 
14}
15 
16// in method params
17public void methodAnnotated( @TypeAnnotated int parameter )
18{
19    System.out.println( "do nothing" );
20}


All this was not possible until Java 8.

  • @FunctionalInterface: this annotation indicates that the element annotated is going to be a functional interface. A functional interface is an interface that has just one abstract method (not a default one). The compiler will handle the annotated element as a functional interface and will produce an error if the element does not comply with the needed requirements. Here is an example of functional interface annotation:
01// implementing its methods
02@SuppressWarnings( "unused" )
03MyCustomInterface myFuncInterface = new MyCustomInterface()
04{
05 
06    @Override
07    public int doSomething( int param )
08    {
09        return param * 10;
10    }
11};
12 
13// using lambdas
14@SuppressWarnings( "unused" )
15    MyCustomInterface myFuncInterfaceLambdas = ( x ) -> ( x * 10 );
16}
17 
18@FunctionalInterface
19interface MyCustomInterface
20{
21/*
22 * more abstract methods will cause the interface not to be a valid functional interface and
23 * the compiler will thrown an error:Invalid '@FunctionalInterface' annotation;
24 * FunctionalInterfaceAnnotation.MyCustomInterface is not a functional interface
25 */
26    // boolean isFunctionalInterface();
27 
28    int doSomething( int param );
29}

This annotation can be applied to classes, interfaces, enums and annotations and it is retained by the JVM and available in Run time. Here is its declaration:

1@Documented
2 @Retention(value=RUNTIME)
3 @Target(value=TYPE)
4public @interface FunctionalInterface



2-6. Retrieving   어노테이션 


자바 리플렉션 API 는 여러가지 메소드를 포함한다. 그 메소드들은 런타임에 클래스,메소드등으로부터  

어노테이션 정보를 얻을수있게 한다. 그것을 통해서 스프링같은 서드파티 프레임워크는 클래스들을 

조작할수있게 된다.


저런 모든 메소드들을 포함한 인터페이스는 AnnotatedElement 이며 대표적으로 다음과 같다.

  • getAnnotations():  주어진 엘리먼트가 가지고있는 모든 어노테이션을 배열로 가져온다.
  • isAnnotationPresent(annotation):  엘리먼트가 어노테이션을 가지고있는지 체크한다.
  • getAnnotation(class): 파라미터로 넘겨진 엘리먼트에 대한 단일 어노테이션을 가져온다.

This class is implementing by java.lang.Classjava.lang.reflect.Method and java.lang.reflect.Field among others, so can be used basically with any kind of Java element.

Now we are going to see an example of how to read the annotations present in a class or method using the methods listed above:

We write a program that tries to read all the annotations present in a class and its methods (we use for this example the classes defined before):

01public static void main( String[] args ) throws Exception
02{
03 
04    Class<AnnotatedClass> object = AnnotatedClass.class;
05    // 클래스로부터 모든 어노테이션을 가져온다.
06    Annotation[] annotations = object.getAnnotations();
07    for( Annotation annotation : annotations )
08    {
09        System.out.println( annotation );
10    }
11 
12    // 어노테이션이 존재하는지 체크한다.
13    if( object.isAnnotationPresent( CustomAnnotationClass.class ) )
14    {
15 
16        // 원하는 엘리먼트의 어노테이션을 가져온다.
17        Annotation annotation = object.getAnnotation( CustomAnnotationClass.class );
18 
19        System.out.println( annotation );
20 
21    }
22    // 클래스내에 있는 모든 메소드들에 대해 어노테이션을 확인한다.
23    for( Method method : object.getDeclaredMethods() )
24    {
25 
26        if( method.isAnnotationPresent( CustomAnnotationMethod.class ) )
27        {
28 
29            Annotation annotation = method.getAnnotation( CustomAnnotationMethod.class );
30 
31            System.out.println( annotation );
32 
33        }
34 
35    }
36}

The output of this program would be:

1@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05)
2 
3@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05)
4 
5@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=friend of mine, date=2014-06-05, description=annotated method)
6@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=danibuiza, date=2014-06-05, description=annotated method)

In the program above we can see the usage of the method getAnnotations() in order to retrieve all annotations for a given object (a method or a class). We also showed how to check if an specific annotation is present and to retrieve it in positive case using the methods isAnnotationPresent() and getAnnotation().





Reference 

http://tutorials.jenkov.com/java-reflection/annotations.html

http://tutorials.jenkov.com/java/annotations.html

http://www.javacodegeeks.com/2014/11/java-annotations-tutorial.html

http://hmkcode.com/spring-configuration-xml-annotation-java/




선선한 토요일 아침에 뭐 할까 하다가 , 자바 어노테이션에 대해서 간략하게 정리해 놓아야겠다고 생각하고 

참고 싸이트들을 검색해보니, 오래된 기술이다 보니 역시나 좋은 글들이 많아서 .그것들을 번역/정리해 

보다보니 생각한것보다 엄청 길어져버렸다. ㅜㅜ




순서

1. 자바 어노테이션 기본 문법

2. 자바 어노테이션 이야기 

3. 자바 리플렉션으로 어노테이션 다루기 

4. 스프링 설정  어노테이션 vs XML





1. 자바 어노테이션 기본 문법


Annotation 이름 

기본적으로 요렇게 생김

@Entity

@ <-- 요놈은 기본적으로 컴파일러에게 이게 어노테이션이다라고 알린다. 

@ 마크 다음에 오는 문자는  해당 어노테이션의 이름이고 여기서는  "Entity" !!!


Annotation 요소 (Element)

자바 어노테이션은 값을 세팅할수있는 요소들을 가질수있는데  속성이나 파라미터 쯤이라고 볼수있지.
@Entity(tableName = "vehicles")

위에서는 tableName 이라는 이름의 요소를 하나 가지고있다는뜻이야. 값은 "vehicles" 이군. 

물론 아래와 같이 여러개의 요소를 가질수도있어.

@Entity(tableName = "vehicles", primaryKey = "id")

하나만 요소로 가질 경우 다음과 같이 짧게 줄여 쓸수도있지.

@InsertNew("yes")


Annotation 선언위치(Placement)

어노테이션은 클래스, 인터페이스, 메소드, 메소드 파라미터, 필드, 지역변수 위에 위치할수있어.

아래는 클래스 위에 선언되어있는 것이고

@Entity
public class Vehicle {
}

위에 말했다시피 @로 시작하고 이름은 Entity 야.  Entity 어노테이션은 그냥 내가 만든 어노테이션이고 현재 별 의미 없어. :-) 


아래 예는  모든 선언 위치에 어노테이션(주석)을 넣어봤어 함 봐바~  

(뭐 큰 역할을 하는건 아니지만 우리가 항상 사용하는 //  요런  주석으로써의 의미는 있다고 볼수있지  ) 

@Entity
public class Vehicle {

    @Persistent
    protected String vehicleName = null;


    @Getter
    public String getVehicleName() {
        return this.vehicleName;
    }

    public void setVehicleName(@Optional vehicleName) {
        this.vehicleName = vehicleName;
    }

    public List addVehicleNameToList(List names) {

        @Optional
        List localNames = names;

        if(localNames == null) {
            localNames = new ArrayList();
        }
        localNames.add(getVehicleName());

        return localNames;
    }

}


자바에서 기본적으로 제공되는 어노테이션 

  • @Deprecated
  • @Override
  • @SuppressWarnings

@Deprecated

이 어노테이션은 , 해당 클래스/메소드 등은 더이상 지원하지 않으니 or  만들고 나니깐 별로 안좋은 

부분이 많거나, 더 좋은 해결법이 생겼으니 사용하지 마시라는 의미야. 그럼에도 불구하고 네가 그걸 

쓴다면 자바 컴파일러는 귀신같이 알아내서 경고 메세지를 날려줄것이지. 

(@Deprecated 주석때문에 컴파일러가 알아차릴수있는거라는 말

@Deprecated
public class MyComponent {

}

@Override

이 어노테이션은 슈퍼클래스에 대해 오버라이드 되었다는걸 알려줘. 따라서 만약 메서드가 

슈퍼클래스와 매칭되지 않았으면 에러를 날려주지. (컴파일러 만드는 사람들이 에러를 감지하기 

편하겠지?)  아래 예를 봅시다~

public class MySuperClass {

    public void doTheThing() {
        System.out.println("Do the thing");
    }
}


public class MySubClass extends MySuperClass{

    @Override
    public void doTheThing() {
        System.out.println("Do it differently");
    }
}

물론 이 어노테이션을 사용하는게 강제는 아니지만 , 사용하는게 좋을꺼야. 만약 슈퍼클래스가 

변경
되었는데 그걸 모르고 자식클래스에서 오버라이딩한 클래스를 그대로 냅두면, 의도가 

깨져버리고 
우리는 그 사실도 모른채 퇴근을 하겠지~  만약 @Override 를 선언해뒀다면 

바로 에러로 알려주고 네
가 수정한후에 편한마음으로 컴백홈하게 도와줄꺼야

@SuppressWarnings 

이 어노테이션은 해당 메소드에 대해서 compiler suppress warnings (컴파일러 경고 억제?)을 

만들어. 예를들어 어떤 코드가 경고메세지를 수천개씩 쏟아내는데, 그게 보기싫을때 입막음 

시켜버리는거야 조용히~

@SuppressWarnings
public void methodWithWarning() {


}


개인용 어노테이션 만들기 


개인적으로 사용할 어노테이션을 만드는것이  가능하며 어노테이션은 클래스나 인터페이스처럼 

자신의 파일에 정의된다. 다음 예를 보자.

@interface MyAnnotation { String value(); String name(); int age(); String[] newNames(); }

위의 어노테이션은 4개의 요소를 가지고있고 이름은 MyAnnotation  이다.

@interface 키워드를 주목하라  이것은 자바컴파일러에게 이놈이 어노테이션 정의라는걸 알려준다.

요소들은 인터페이스에서의 메서드 정의와 유사하다.  자바 기본요소들을 모두 사용할수있고 , 배열도 사용가능하다. (복잡객체사용은 불가능) 


 위에서 정의한 어노테이션을 사용해 보자.

@MyAnnotation(
    value="123",
    name="Jakob",
    age=37,
    newNames={"Jenkov", "Peterson"}
)
public class MyClass {


}

위에 보다시피 각각의 요소에 나만의 값을 지정했다. 후에 프레임워크에서  이 값을 파싱해서 필요한 정보를 알아낼것이다. 물론 님 스스로  리플렉션을 통해 저 어노테이션을 분석해서 사용할수도 있을테고~

* 저런 주석을 달아놓으면 나중에 클래스별로 그루핑을 할때 사용하거나 어떤 클래스/함수는 어떤것(URL 따위들) 과 매핑 같은것에 사용할수 있을거라는 생각을 잠시 해보자. 함수이름이 달라져도 어노테이션만 같으면 자동 매핑해줄수있지 않겠는가 


요소(Element)디폴트 값

요소에 기본값을 설정할수 있다. 아래 예를 보자.

@interface MyAnnotation {

    String   value() default "";

    String   name();
    int      age();
    String[] newNames();

}

디폴트값을 설정해두면 아래와 같이 사용할때 값을 넣지 않아도 된다. (아예 보이지도 않음) 

@MyAnnotation(
    name="Jakob",
    age=37,
    newNames={"Jenkov", "Peterson"}
)
public class MyClass {


}


@Retention    -  네이버 사전 :  (어떤 것을 잃지 않는) 보유[유지])

얼마나 오랫동안 어노테이션 정보가 유지되는지 설정할수있다. 


SOURCE    : 어노테이션 정보가 컴파일시 사라짐, 바이트코드에서는 존재하지 않음.

                  (example: @Override, @SuppressWarnings)

CLASS      :  클래스 파일에 존재하고 컴파일러에 의해 사용가능, 가상머신(런타임)에서는 사라짐.

RUNTIME  :  실행시 어노테이션 정보가 가상 머신에 의해서 참조 가능. 자바리플렉션에 의해 사용


아래 예를 보자. 

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)

@interface MyAnnotation {

    String   value() default "";

}


@Target 

자신이 만든 어노테이션이 사용되게될 자바 요소를 지정할수있다. 아래 예를 보자.

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
public @interface MyAnnotation {

    String   value();
}

위처럼 만들면 메소드에서만 사용가능한 어노테이션을 정의할수있다.아래와 같이 타겟을 정할수있다.

  • ElementType.ANNOTATION_TYPE
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE

@Inherited

The @Inherited 어노테이션은 이 어노테이션을 사용한 슈퍼클래스를 상속한 서브클래스에서도
해당 어노테이션을 갖도록 한다.

java.lang.annotation.Inherited

@Inherited
public @interface MyAnnotation {

}
@MyAnnotation
public class MySuperClass { ... }
public class MySubClass extends MySuperClass { ... }

MySubClass 클래스는 @MyAnnotation 을 상속한다. 


@Documented

@Documented 어노테이션이 지정된 대상의 JavaDoc 에 이 어노테이션의 존재를 표기하도록 지정.

java.lang.annotation.Documented

@Documented
public @interface MyAnnotation {

}
@MyAnnotation
public class MySuperClass { ... }






Reference 

http://tutorials.jenkov.com/java-reflection/annotations.html

http://tutorials.jenkov.com/java/annotations.html

http://www.javacodegeeks.com/2014/11/java-annotations-tutorial.html

http://hmkcode.com/spring-configuration-xml-annotation-java/



서버분석 


아래과 같은 라이브러리가 필요합니다.

dependencies {

compile("org.springframework.boot:spring-boot-starter-web")

compile("org.springframework.boot:spring-boot-starter-thymeleaf")

compile("org.springframework.boot:spring-boot-starter-security")

testCompile("org.springframework.boot:spring-boot-starter-test")

}

특이한건 스프링 boot 를 사용했다는건데, 이번 예제를 통해서 저도 처음 boot 를 접해보았는데 설정부분 및 REST 서비스를  굉장히 단순화 시켜 놓았더군요.

 XML  설정파일은 필요없고, 어노테이션을 통해서 설정을 해줍니다. 

(자바 코드를 통한 설정은 스프링 3.1에 거의 완성되었습니다.) 


중요한 2개의 자바 파일을 분석해 보겠습니다.(Application.java 에서 스프링을 실행시켜줍니다.)




1WebSecurityConfiguration 


첫번째 소스를 분석하기전에 기본 지식을 쌓도록 합시다. 

(http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ ) 

WebSecurityConfigurerAdapter 

@EnableWebSecurity 어노테이션과  WebSecurityConfigurerAdapter 은 웹기반 보안을 위해 함께 작동 하며  WebSecurityConfigurerAdapter 을 상속받아서 몇줄만 코딩하면 다음과 같은것을 할수있게된다.

  • 우리의 어플리케이션에 어떤 URL 로 들어오던간에 사용자에게 인증을 요구할수있음.
  • 사용자이름과 비밀번호 및 롤기반으로 사용자를 생성할수있다.
  • HTTP Basic 과 폼기반 인증을 가능하게함.
  • 스프링 시큐리티는 자동적으로 로그인페이지,인증실패 url 등을 제공한다.

@EnableWebSecurity @Configuration public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { auth .inMemoryAuthentication() .withUser("user") // #1 .password("password") .roles("USER") .and() .withUser("admin") // #2 .password("password") .roles("ADMIN","USER"); } @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/resources/**"); // #3 } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeUrls() .antMatchers("/signup","/about").permitAll() // #4 .antMatchers("/admin/**").hasRole("ADMIN") // #6 .anyRequest().authenticated() // 7 .and() .formLogin() // #8 .loginUrl("/login") // #9 .permitAll(); // #5 } }
  • #1  사용자 이름 “user” 에 대해서 유저ROLE로 인메모리 인증을 허락한다.
  • #2  사용자 이름  “admin” 에 대해 관리자ROLE로  인메모리 인증을 부여한다.
  • #3 “/resources/”  경로 호출은 인증 무시한다. XML 설정에서는 다음과 같다. http@security=none  
  • #4 인증없는 사용자를 포함한 누구한테나  “/signup” and “/about”  경로는  허용한다.
  • #5 “/login” and “/login?error” 경로는 누구한테나 허용
  • #6 /admin/ 로 시작되는 경로는 반드시 관리자 ROLE 에게만 허용된다.
  • #7 모든 남은 URLs 는 인증이 성공해야지만 접근가능하다.
  • #8 자바 세팅을 통해 폼기반인증셋업. 인증은  /login 으로 username/password 가 POST 로 날라올때
        수행된다. 
  • #9  명시적으로 GET /login 이 요청되었을때 로그인페이지를 보여지는게 요구되어진다. 

위의 것은 아래와 같은  기존의  XML based configuration 과 비슷하다.


<http security="none" pattern="/resources/**"/> <http use-expressions="true"> <intercept-url pattern="/logout" access="permitAll"/> <intercept-url pattern="/login" access="permitAll"/> <intercept-url pattern="/signup" access="permitAll"/> <intercept-url pattern="/about" access="permitAll"/> <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/> #7 <logout logout-success-url="/login?logout" logout-url="/logout" /> <form-login authentication-failure-url="/login?error" login-page="/login" login-processing-url="/login" password-parameter="password" username-parameter="username" /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="user" #1 password="password" authorities="ROLE_USER"/> <user name="admin" #2 password="password" authorities="ROLE_USER,ROLE_ADMIN"/> </user-service> </authentication-provider> </authentication-manager>

XML 설정과 몇가지 차이점 

  • #1 , #2 에서 보면 자바설정에서는 ROLE_ 이 없다. 자동적으로 추가해주기때문이다.
  • Java configuration has different defaults URLs and parameters. Keep this in mind when creating custom login pages. The result is that our URLs are more RESTful. Additionally, it is not quite so obvious we are using Spring Security which helps to prevent information leaks. For example:

  • /spring_security_login 대신해서 GET /login 호출하면 로그인 페이지를 연다.
  • /j_spring_security_check 대신해서 POST /login 를 호출하면 인증체크를 한다. 
  • 사용자이름 기본 설정은  j_username 대신해서 username  이다.
  • 패스워드 기본 설정은   j_password 대신해서  password 이다.
  • 자바 설정은  여러가지 요청 URL 에 대하여 동일한 롤에 의해 쉽게  매핑할수있게 해준다. #4 참고 
  • 자바설정은 여분의 쓸모없는 코드들을 제거한다 . intercept-url 같은 경우 중복이 심하다.
  • When mapping HTTP requests using the hasRole method as we did in #6, we do not need to specify “ROLE_” as we would in XML. Again, this is so common of a convention that the hasRole method automatically adds “ROLE_” for you. If you did not want to automatically prefix with “ROLE_”, you could use the “access” method.

실제 코드 

package showcase;

// org.springframework.security 를 사용합니다.

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


// 스프링 시큐리티 Java Config 설정 (security-context.xml 로 설정할수도있다.)

// configuration 은 springSecurityFilterChain 로 알려진 서블릿 필터를 생성한다. 

// configuration 은 URLs 보호, ID/PWD 검증 같은 모든 보안에 대해서 책임진다.

@Configuration

@EnableWebSecurity


public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {


@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.inMemoryAuthentication()  // roy , spring 이면 인증 성공, USER 롤 받음

.withUser("roy") 

.password("spring")

.roles("USER");

}


@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests().antMatchers("/**")  // 모든 요청에 대해서 USER 롤을 가지고있는지 확인

.hasRole("USER").and().httpBasic();

}

}





둘째. HomeController 

package showcase;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

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

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;


@Controller

@RequestMapping("/*")

public class HomeController {


private static final Logger logger = LoggerFactory.getLogger(HomeController.class);


@RequestMapping(value = "/", method = RequestMethod.GET)   

public String home() {

logger.info("Spring Android Basic Auth");

return "home";    // home.html 호출 해줌

}


@RequestMapping(value = "/getmessage", method = RequestMethod.GET, produces = "application/json")

public @ResponseBody Message getMessage() {

logger.info("Accessing protected resource");  // USER 롤을 가지고있으면 여기에 들어옴

  // response body 로 Message 객체를 JSON 형식으로 보내줌. 

return new Message(100, "Congratulations!", "You have accessed a Basic Auth protected resource.");

}


}







클라이언트 분석 


아래과 같은 라이브러리가 필요합니다.

dependencies {
compile 'org.springframework.android:spring-android-rest-template:1.0.1.RELEASE'
compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.11'
}


안드로이드 코드를 살펴보기전에 간련 기술을 알아봅시다. (아래 링크 참고)


HTTP basic Authentication

HTTP 기본 인증에서는 서버로 아래와 같이 Request Header 를 보낸다.

1
2
3
4
5
6
7
8
9
10
GET /spring-security-http-basic-authentication/secured/mypage HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cookie: JSESSIONID=896331E26095C95449516FCBF2E0E93C; __atuvc=28%7C31%2C0%7C32%2C0%7C33%2C215%7C34%2C59%7C35
Authorization: Basic c3JjY29kZXM6cGFzc3dvcmQ=
Note : 'c3JjY29kZXM6cGFzc3dvcmQ=' is Base64 encoded version of 'username:password' i.e. 'srccodes:password'.


실제 코드 

package org.springframework.android.basicauth;

import java.util.Collections;

import org.springframework.http.HttpAuthentication;
import org.springframework.http.HttpBasicAuthentication;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
* @author Roy Clarkson
*/
public class MainActivity extends AbstractAsyncActivity {

protected static final String TAG = MainActivity.class.getSimpleName();


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout);

// Initiate the request to the protected service
final Button submitButton = (Button) findViewById(R.id.submit);
submitButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new FetchSecuredResourceTask().execute();
}
});
}

private void displayResponse(Message response) {
Toast.makeText(this, response.getText(), Toast.LENGTH_LONG).show();
}


private class FetchSecuredResourceTask extends AsyncTask<Void, Void, Message> {

private String username;

private String password;

@Override
protected void onPreExecute() {
showLoadingProgressDialog();

// build the message object
EditText editText = (EditText) findViewById(R.id.username);
this.username = editText.getText().toString();

editText = (EditText) findViewById(R.id.password);
this.password = editText.getText().toString();
}

@Override
protected Message doInBackground(Void... params) {
final String url = getString(R.string.base_uri) + "/getmessage";

// HTTP 기본인증헤더를 username , password를 이용하여 생성한다.
HttpAuthentication authHeader = new HttpBasicAuthentication(username, password);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAuthorization(authHeader);
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();

// JSON 형식 컨버터 설정
restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());

try {
// 서버의 URL에 데이터를 요청한다.받은데이터는 Message 객체로 변환
Log.d(TAG, url);
ResponseEntity<Message> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<Object>(requestHeaders), Message.class);
return response.getBody();
} catch (HttpClientErrorException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
return new Message(0, e.getStatusText(), e.getLocalizedMessage());
} catch (ResourceAccessException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
return new Message(0, e.getClass().getSimpleName(), e.getLocalizedMessage());
}
}

@Override
protected void onPostExecute(Message result) {
dismissProgressDialog();
displayResponse(result);
}

}

}




+ Recent posts