ClassNotFoundException :   

클래스 로더가 클래스 패스에서 해당 클래스를 못 찾으면 발생한다. 이 에러가 발생하면 기본적으로 클래스 패스와 
그 패스에 해당 클래스가 존재하는지 확인해야한다.

NoClassDefFoundError :

이것이 발생하면 이유를 찾기가 꽤나 골치아퍼 지는데, 이것은 컴파일타임때 요구되는 클래스가 존재하지만 
런타임때 클래스들이 바뀌거나, 제거되거나, 클래스의 스태틱 초기화가 예외를 던지면 이것이 발생한다. 
먼 소리냐하면 클래스패스에 클래스가 존재하더라도, 이 클래스에 요구되는 클래스들중 하나에 문제가 생겼다는 
이야기이다. 따라서 이 클래스와 의존관계를 맺는 모든것들을 살펴봐야한다.

예제 :

public class Test1
{
}
public class Test 

{
   public static void main(String[] args)

   {
        Test1 = new Test1();    
    }

}

     
두개의 (Test, Test1)  클래스를 컴파일한후에 , Test1.class 파일을 지우고 RUN 해보면 아래의 예외를 던진다.

Exception in thread "main" java.lang.NoClassDefFoundError: Test
        at Test1.main(Test1.java:5)
Caused by: java.lang.ClassNotFoundException: Test
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more
 

번외 :  JAVA 와 WAS 의 클래스 로딩 차이 

Java에서 클래스로더가 클래스나 리소스를 찾을 땐 부모 클래스로더에게 우선 이를 위임하게 된다. 그래서 부모 클래스로더가 해당 클래스나 리소스를 찾을 수 없게되면 자신이 직접 해당 클래스나 리소스를 찾는 형태로 동작하게 된다. 아래는 이와 관려련하여 Java 7의 API문서에서 발췌한 내용이다.

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself.

클래스로더가 복수개 존재할 때 공통으로 사용되는 클래스나 리소스들을 모두 각자 로딩한다면 해당 자원들을 중복되게 로딩함으로써 JVM의 permanent generation를 낭비하게 된다. 하지만 부모 클래스로더에서 해당 자원들을 먼저 찾는 방식으로 동작할 경우 복수개 클래스로더의 부모가 모두 같을 경우 부모에 로드된 자원을 함께 사용하게 되므로 자원의 낭비를 막을 수 있다. 이러한 방식의 클래스로딩 메커니즘을 parent-first / child-last 방식이라고 명명하도록 하겠다.

하지만 WAS에서는 parent-first / child-last 방식을 사용하지 않는다.

(이미지 출처: Tomcat 클래스로더 How-to)

대표적은 WAS인 Tomcat의 클래스로더 계층구조는 위의 그림과 같다. 만약 WAS에서 Java와 같은 parent-first / child-last 방식의 클래스로딩 메커니즘을 사용할 경우 각각의 웹앱들은 Common 클래스로더에서 로딩된 클래스를 함께 사용하게 된다. 즉, WAS의 lib 폴더에 존재하는 클래스들을 모든 웹앱에서 함께 사용하게 되어버린다.

웹앱 개발 시에 A라는 라이브러리의 최신 버전인 2.0 버전을 사용했다고 하자. 개발과 검증을 모두 완료하고 해당 웹앱을 WAS에 배포했는데 만약 Common 클래스로더에 이미 A라는 라이브러리의 1.0 버전이 로딩되어 있다면 어떻게 될 것인가? 1.0과 2.0의 클래스 명(패키지 경로 포함)이 같고 기능이 바뀌었다면 2.0기반에서 작업된 웹앱은 제대로 동작하지 않을 것이다. 이와 같은 문제가 발생할 수 있기 때문에 WAS에서는 parent-first / child-last 방식의 클래스로딩 메커니즘이 아닌 parent-last / child-first 방식의 클래스로딩 메커니즘을 사용한다.

참고로 Tomcat에서는 아래와 같은 순서로 클래스로더를 사용한다. (엄밀히 말하면 parent-last / child-first가 아니라 Common과 웹앱만 순서가 바뀐 것이다.)

  • Bootstrap
  • System
  • /WEB-INF/classes
  • /WEB-INF/lib/*.jar
  • Common


2014.08.12 추가

Tomcat이 아닌 다른 WAS들의 경우 모두 parent-last / child-first 방식을 디폴트로 따르는 것은 아닌 것 같다. 하지만 그런 WAS들의 경우 어떤 클래스로딩 메커니즘 방식을 사용할지 설정할 수 있다. 자체 서비스를 하는 기업의 경우 이런 설정을 자신들이 변경할 수 있지만 WAR 패키지로 판매하는 제품을 만드는 기업의 경우 WAS의 클래스로딩 메커니즘 설정에 따라 충돌이 발생할 수 있기 때문에 주의해야 한다.

http://epicdevs.com/16

레퍼런스 : http://www.javaroots.com/2013/02/classnotfoundexception-vs.html

 

 

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/


"멀티쓰레드 개발은 언어 무관하게 무지 어렵다. 세계 최고 개발자의 할아버지가 와도 어렵다." 


요즘 "폴리글랏 프로그래밍" 이 유행하고있습니다. 여러개의 언어를 적재적소에 사용해서 생산성 및 품질

을 올리자 뭐 이쯤되겠지요. 설사 주력언어 이외에 다른언어를 전혀 사용하지 않더라도, 다른 언어를

공부하는것은 주력언어에 에 대한 이해의 폭을 상당히 넓혀주므로 틈틈히 다른 언어를 공부하는건 굉장히

바람직한 일이 될것입니다. 따라서 " 7 가지 언어를 7주에 마스터"  같은 책도 인기가 있는것이겠구요.


하지만 언어를 배운다는것은 정말 힘든일이라고 생각되는게,  지금 쓰고있는 "자바언어에서 동기화의

어려움" 를 읽어보면 아시겠지만, 해당 언어에 대한 경험이 많지 않다면 실수하기 쉬운 문제가 도처에

도사리고 있습니다. 그 이슈를 실전에서 대처하려면 ,실수에 의한 경험도 필요하고, 가끔은 소스의 내부를

철저히 조사해봐야하는 수고를 해야하는데, 언어를 배우는것도 힘든데 저런부분까지 신경쓰려면 고난의

행군은 각오해야할거 같습니다. 


필자도 경력의 대부분을 C++  언어와 함께 하였기때문에 , 최근에 자바,파이썬,자바스크립트등으로

개발할때 많은 실수를 하게되는데 그때마다  제대로된 제품하나 만들기가 정말 너무 어렵구나..

남들이 쉽게 생각할지는 몰라도 소프트웨어 개발이라는게 정말 빡이 세구나 하는걸 많이 느끼게 됩니다.


자 아래에 써내려갈 야그들은 아마 자바고수님들이라면 다 알고 계실 오래된 내용일거 같습니다. 

하지만 다시 이렇게 정리해 보는것은 저 처럼 자바에 익숙치 않거나 , 초중급분들에겐 충분히 가치있는 내용이

될거 같아서 중복이겠지만 다시 되풀이해 보는시간을 가지려합니다.  

그리고 아래 Actor / Akka  게시글과 연관성도 있을듯 하네요. (http://okky.kr/article/279263



1. Collections.synchronizedList 이야기 

보통 우리는 Vector 대신해서 ArrayList 를 사용하라는 말을 듣곤합니다.  Vector 는 동기화되어진 함수로 가득차있기때문에 싱글쓰레드 프로그램에서 효율적이지 않다라는거지요. 

따라서 멀티쓰레드 프로그램을 짤때 ArrayList 를 사용할 경우 , 쓰레드문제에 대해서 신경을 써줘야하는데 , 선택할 2가지방법은 ArrayList 의 함수를 사용할때마다 적절한 동기화를 직접처리해주거나 Collections.synchronizedList 과 같은 함수를 사용하는 방법이 있습니다.


Collections.synchronizedList 사용법은 다음과 같습니다.

List list = Collections.synchronizedList(new ArrayList());


일단 아 저렇게 쓰면 쓰레드문제는 이제 신경안써도 되겠군~ 아싸~~!!! 하는 순간에 뒤통수 제대로 맞는거지요. 저렇게 되면 일단 함수하나를 사용할때는 락이 걸려서 중복호출이 되지 않겠지만, 여러함수가 호출될때 문제가 생길수있습니다.

다음과 같이

final List<String> list = Collections.synchronizedList(new ArrayList<String>());

final int nThreads = 2;

ExecutorService es = Executors.newFixedThreadPool(nThreads);

for (int i = 0; i < nThreads; i++) {

    es.execute(new Runnable() {

        public void run() {

            while(true) {

                try {

                    list.clear();

                    list.add("888");

                    list.remove(0);

                } catch(IndexOutOfBoundsException ioobe) {
                    ioobe.printStackTrace();

                }
            }
        }
    });
}

위의 코드를 실행하면 , Thread A 가 remove(0) 을 하는 순간에 Thread B 가 clear() 를 한다면 ,, 꽝~~
remove 할것이 없는데 remove 를 하려니 문제가 생길수밖에요.  이럴땐 

 synchronized (list) {

    list.clear();

    list.add("888");

    list.remove(0);

}

이렇게 함수들을 묶어서 동기화를 시켜줘야합니다. 
따라서 단지 Collections.synchronizedList  를 쓴다고해서 동기화 문제를 회피할수있지 않다는 얘기입니다.


2. Threadsafe Iteration & ConcurrentModificationException 이야기 

컬렉션 객체를 사용할때 우리는 동기화에 대해서 굉장히 불분명할때가 많은데요. 그리고 예상치 못한 문제가 생기기도 합니다. 얼마전에 생긴문제인데요. 

final List<String> list = new ArrayList<String>();

list.add("A");

list.add("B");

list.add("C");

for(String s : list) {

    if(s.equals("A")) {

        list.remove(s);

    }

}

위의 코드를 실행시키면 어떻게 될까요??  
ConcurrentModificationException 예외가 발생합니다. 정확히는 remove 끝내고 다시 위로 올라간후에
list 로 부터 다음것을 할당받는순간에 발생합니다.  (내부에서 next() 함수 호출될때) 

솔직히 저런 코드에서 이런 예외가 발생할수있다는걸 예상하는건  어렵습니다. 너무나 뻔해보이는
코드이고 설마 내부에서 해결해주지 않았겠어 하는 마음때문인데요. 저게 왜 예외가 발생하는지 알려면
소스 내부를 살펴봐야합니다.


내부를 살펴보겠습니다.!!

modCount 를 주의깊게 살펴봐주세요 !!!


ArrayList 의 add 함수 입니다.

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    ....
}

modCount 를 하나 증가시키고 있습니다.  위의 소스에서 add 를 3번하니깐 3이 되었겠네요. size 는 3 이 됩니다.

다음은  list.remove(s); 함수를 살펴보겠습니다.

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

역시나 modCount 를 하나 증가시키고 있습니다. 이때 modeCount 는 4가 됩니다.  size 는 2로 줄어듭니다.

modCount가 늘어나고 size  가 줄어들기전인 처음 for  문이 시작되는 상황으로 돌아가서   for(String s : list)   부분을  살펴봅시다.

list 에서 s 객체를 가져오기 위해서  처음에는 Iterator 객체를 생성합니다.

이때 Iterator 객체는 list 객체의 값 (modeCount) 를 할당받습니다.


public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

 expectedModCount = modCount;  이렇게  두 값은 3이 됨을 알수있습니다. CURSOR = 0  이 됩니다.


다음으로는  hasNext 를 호출하여 순회가능한지 확인합니다.

public boolean hasNext() {
return cursor != size;
}

size 는 3일것이고 CURSOR = 0  이기때문에 순회가능합니다.

이후에 modCount 가 4 가되었고. 다시 위(for  문 시작) 로 올라가봅시다.

자 대망의 next() 함수입니다. 순회가능하기때문에 해당차례의 객체를 얻기위하여 next() 를 호출합니다.

public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

자 여기 checkForComodification() 함수를 잘보시면 modCount != expectedModCount 다르면 예외~ 뿅!! 
위에 remove 함수쪽을 다시 올라가서 보시면 remove 하면서 modeCount 는 4가 되었고, size 는 2가 
되었는데, Iter 객체의 expectedModCount  는 여전히 3입니다. 먼가 수정이 이루어질때 이 변수는 변하지
않는겁니다. 그래서 예외가 발생하는거지요.  여기서  눈치챈분도 있겠지만 remove 뿐만아니라 add 해도 
예외가 발생합니다. 먼가 수정한것이기때문입니다.
그래서 저런 수정이 있을경우 대신해서 다음과 같이 작성합니다.

for(Iterator<String> iter = list.iterator(); iter.hasNext();) {

    String s = iter.next();

    if(s.equals("Test1")) {

        iter.remove();

    }

}

예외가 발생하지 않습니다.  이유는 다음 코드와 같이

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

iter 객체의 remove 에서는 expectedModCount = modCount; 를 같게끔 변경해주기때문입니다.

 

자 여기까지는 싱글쓰레드 얘기였습니다. 

이제 여기에 두개의 쓰레드가 동시에 컬렉션을 건드린다고 생각해봅시다...끔찍합니다. 그래서 akka 니 무상태 지향 코딩이니 같은것들이 나오는건가 봅니다.

설사 아래와 같이 foreach 를 쓰지않고 iterator 를 쓴다고 해도 멀티쓰레드상에서는 컬렉션의 아이템 수정이 가해지면 헬이됩니다.  여러모로 ConcurrentModificationException  예외는 멀티쓰레드 프로그래밍에서 중요한 예외가 될것입니다. 


3. Volatile  실패 이야기 

가끔 ConcurrentModificationException   예외는 의도치 않게 행동을 합니다.

public E next() {
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
      .....
}

위의 소스를 보면 내부 상태가 불일치 할때 ConcurrentModificationException  를 던지기로 되있는데요. 
황당하게 어떤때는  예외를 안던지는 경우가 있습니다.  
A의 쓰레드가 next 를 호출하고 있고, B 쓰레드는 modCount 를 수정하고있습니다. 이때 위에 살펴본것과
마찬가지로 예외가 발생해야하는데 예외가 발생하지 않습니다. B 쓰레드가 수정한 modeCount 가 A 쓰레드
의 메모리공간에서는 변경되지 않았기때문인데요. 이때 떠오르는 생각이 , 최신의 변수만을 적용하고
싶을때 변수에 붙히는 키워드가 멀까요? 

그렇습니다. Volatile 인데요. (http://tutorials.jenkov.com/java-concurrency/volatile.html  참고) 
Volatile 이 붙어있는지 확인했더니. 없습니다. 조슈아 블로흐(이펙티브 자바 저자 & 자바 아키텍트) 가 정신을

딴데 팔고 API 를 개발했던것일까요?  그건 아닙니다.  volatile 은 동기화에 그닥 도움을 줄수없습니다. 
updateness 는 보장해도 atomicity 는 보장하지 않습니다.Volatile 을 붙혀놔도  operator (++) 가 atomic 하지 않습니다.


4. 그래서 어떻게 하면 되냐 OTL 

멀티쓰레드를 짤 때 문제되는 경쟁조건, 메모리가시성,데드락 등을 요리조리 피해서 코딩하는것의 어려움에 봉착되어 있을 때, 우리의 구원자 Dug Lea 가 나타나주셨습니다. akka 라이브러리를 만든사람인데요. 그 분은 

자바에 concurrent collection API 를 추가해 주셨습니다. (꽤 오래전 얘기죠?)  공부해야합니다. :-) 

http://javarevisited.blogspot.kr/2013/02/concurrent-collections-from-jdk-56-java-example-tutorial.html 

그 밖에  이 보다 더 상위 추상을 다루는 동시성 방법론들을 찾으러 출발하세요. 액터모형, STM 같은..


5. 주요 컬렉션 이야기

이 게시물이 너무 길어지기때문에 간단하게 정리합니다.

CopyOnWriteArrayList 

CopyOnWrite 가 말해주는것처럼 read (select) 시는 아무런 동기화 문제가 없기때문에 놔두고 

변경이 일어날경우 객체를 clone 해서 다루자는 전략입니다. 따라서 읽기행위가 많이 일어나는 

곳에서 사용하기 좋습니다. 위의 예제에서도 ArrayList 를 이거로 바꾸면 예외가 발생하지 않습니다.


BlockingQueue 

보통 생산자 - 소비자 패턴에서 활용되는 큐로 많이 사용된다. 사실 이야기는 이 큐는 멀티쓰레드환경에서 

대표할만한 컬렉션이라는 것이다. 전에 Actor / Akka 문서에 말한 큐같은것들이 대부분 이것으로 이루어져있다. 

소비자가 꺼내어 사용할동안 생산자는 멈춰있고, 생산자가 넣을동안 소비자는 멈춰있어야한다.

서로 쟁탈하면 선반은 망가질것이다.


ConcurrentHashMap

ConcurrentHashMap은 Map의 일부에만 Lock을 걸기때문에 HashTable과 synchronized Map 보다 

효율적인게 특징이다.


참고 

http://rayfd.me/2007/11/11/when-a-synchronized-class-isnt-threadsafe/ 

https://www.ibm.com/developerworks/community/blogs/738b7897-cd38-4f24-9f05-48dd69116837/entry/synchronized_collections_vs_concurrent_collections7?lang=en 

자바병렬프로그래밍 http://www.yes24.com/24/goods/3015162?scode=032&OzSrank=1 


자바에서 예외(Exception)은 크게 checked 예외와 unchecked 예외로 나뉘어진다. checked 예외는 코드에서 명시적으로 try-catch-finally 예외 처리를 해야하는 것을 의미하며, unchecked 예외는 그럴 필요가 없는 것을 의미한다. checked 예외에서 try-catch로 예외를 처리하지 않는 경우에는 메소드에 throws 절을 추가해야 한다.

자바에서 checked 예외는 java.lang.Exception 을 상속받는 형태이며, unchecked 예외는 java.lang.RuntimeException을 상속받는 예외이다. checked 예외이든 unchecked 예외이든 두가지 모두 동일한 기능을 수행한다. 따라서, 어느 것이 더 낫다라고 말할 수는 없다. 하지만, 예외 발생시 어떠한 로직을 추가하느냐에 따라서 그 비용적인 측면은 다양할 수 있다.

 또한, 트랜잭션, 원격 호출과 같은 지점에서 주로 런타임 계열의 unchecked 예외를 사용하는 것이 일반적이며, 이는 기반 기술 (J2EE, Spring)들이 런타임 계열의 예외에 대해 자동으로 트랜잭션 처리를 해주고 있다. (Spring에서는 checked 예외에 대해서는 트랜잭션 처리가 가능하지만, 관행과 같이 런타임 계열을 사용하는 방식을 권고한다.)

(눈여겨 볼것은 바로 예외발생시 트랜잭션의 roll-back 여부이다. 기본적으로 Checked Exception은 예외가 발생하면 트랜잭션을 roll-back하지 않고 예외를 던져준다. 하지만 Unchecked Exception은 예외 발생 시 트랜잭션을 roll-back한다는 점에서 차이가 있다. 트랜잭션의 전파방식 즉, 어떻게 묶어놓느냐에 따라서 Checked Exception이냐 Unchecked Exception이냐의 영향도가 크다. roll-back이 되는 범위가 달라지기 때문에 개발자가 이를 인지하지 못하면, 실행결과가 맞지 않거나 예상치 못한 예외가 발생할 수 있다. 그러므로 이를 인지하고 트랜잭션을 적용시킬 때 전파방식(propagation behavior)과 롤백규칙 등을 적절히 사용하면 더욱 효율적인 애플리케이션을 구현할 수 있을 것이다.)

비즈니스 예외의 경우, 내부적으로 특정 예외를 지정하여 사용할 때에는 checked 예외를 사용하기도 하지만, 런타임 계열의 예외를 사용하는 것이 추가 코드가 들어가지 않고, try-catch로 별도 로직을 구성하지 않는 이상 checked 예외를 사용하는 것은 여러가지로 고려해서 신중할 필요가 있다. 보통 예외를 먹는다는 표현과 같이 try-catch 구문 안에서 checked 예외를 처리하고 catch 절에서 아무런 행위를 하지 않으면, 예외 발싱시 문제점을 추적하기가 쉽지 않다.


try {

   // some business logic

} catch (SomeCheckedException e) {

   e.printStackTrace();

   // do nothing


위의 코드가 대표적으로 예외를 먹는 행위이다. 물론, 예외 처리를 하지 않는 명확한 이유가 있고, 그 안에서 대부분의 예외를 처리한다고 하면, 이와 같은 코드는 크게 상관은 없을 수도 있겠지만, 일반적으로 보아서, 예외를 catch하고 이를 호출하는 측에 아무런 반응을 알리지 않는다면 좋지 않은 코드가 될 가능성이 높다.

또한, 예외를 예외로 받지 않고, 예외 발생시 다양한 코드를 받게끔 처리하는 경우도 있는데, 이러한 경우 예외에 대한 모든 경우들을 검토해야 한다. (마치 HTTP로 통신시 예외에 대한 코드를 정의하듯이 예외코드와 정확한 의미를 상세하게 정의해야 한다.)

이러한 다양한 경우들을 처리하는 것에 대한 부담이 있고, 어떻게 처리할지를 잘 모르겠다면 시스템 내부에서의 예외는 가능하다면 모두 Runtime 계열로 처리하고, UI 접점에서 이러한 예외들을 처리하게끔 하는 것이 가장 나은 방법인 것 같다. UI에서 이러한 다양한 예외들을 어떻게 보여줄 것인지에 대한 고민은 사용자의 요구사항을 들을 필요가 있지만, 대부분이 그냥 시스템에 문제가 있다라는 형태의 오류만을 보기를 원할 것이다. 사실 예외 메시지에 대한 대부분은 개발자들이 보고 싶은 데이터이지 사용자에게 의미있는 정보들은 아니다. (물론, 입력 폼 처리에서 어떠한 정보가 누락되었는지 정합성이 문제가 되었는지는 가능하면 상세하게 보여줄 필요가 있지만, DB 오류 코드나, SQL 구문, 내부 시스템 네트워크 문제, 잘못된 코드 등을 사용자에게까지 보여줄 필요는 없을 것이다.)

결국, 예외는 개발/운영하는 사람들을 위해서 수집하도록 하면 되고, 문제 발생시 추적하여 해결할 수 있는 수준에서 예외 처리를 하면 된다. 즉, UI 접점 내의 서버에는 모든 예외가 런타임 계열로 정의하고(물론, 필요한 경우 checked 예외를 선언하여 처리하면 된다. 다만 이 경우는 세심한 주의가 필요할 뿐이다.), UI에서 발생되는 예외를 비동기로 분석하여 추후 문제 해결시 사용하면 된다. 예외는 전체 시스템 차원에서 처리하는 방식과 일정한 형식을 필요로 하며, 문제를 해결할 수 있는 수준에서 적당하게 사용하면 된다. 또한, 예외코드 사용시 상세한 내용을 문서화/명세화시켜서 남겨두어야 한다. 


예외를 처리하는 3가지 방법


1. 예외 복구

[리스트 1] 재시도를 통해 예외를 복구하는 코드

예외복구의 핵심은 예외가 발생하여도 애플리케이션은 정상적인 흐름으로 진행된다는 것이다. 위 [리스트 1]은 재시도를 통해 예외를 복구하는 코드이다. 이 예제는 네트워크가 환경이 좋지 않아서 서버에 접속이 안되는 상황의 시스템에 적용하면 효율 적이다. 예외가 발생하면 그 예외를 잡아서 일정 시간만큼 대기하고 다시 재시도를 반복한다. 그리고 최대 재시도 횟수를 넘기면 예외를 발생시킨다.  재시도를 통해 정상적인 흐름을 타게 한다거나, 예외가 발생하면 이를 미리 예측하여 다른 흐름으로 유도시키도록 구현하면 비록 예외가 발생하였어도 정상적으로 작업을 종료할 수 있을 것이다.

 

2. 예외처리 회피

[리스트 2] 예외처리 회피

위 [리스트 2]는 간단해 보이지만 아주 신중해야하는 로직이다. 예외가 발생하면 throws를 통해 호출한쪽으로 예외를 던지고 그 처리를 회피하는 것이다. 하지만 무책임하게 던지는 것은 위험하다. 호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 해당 메소드에서 이 예외를 던지는 것이 최선의 방법이라는 확신이 있을 때만 사용해야 한다.

 

3. 예외 전환

catch(SQLException e) {

   ...

   throw DuplicateUserIdException();

}

[리스트 3] 예외전환 

예외 전환은 위 [리스트 3]에서 처럼 예외를 잡아서 다른 예외를 던지는 것이다. 호출한 쪽에서 예외를 받아서 처리할 때 좀 더 명확하게 인지할 수 있도록 돕기 위한 방법이다. 어떤 예외인지 분명해야 처리가 수월해지기 때문이다. 예를 들어 Checked Exception 중 복구가 불가능한 예외가 잡혔다면 이를 Unchecked Exception으로 전환하여서 다른 계층에서 일일이 예외를 선언할 필요가 없도록 할 수도 있다.

이상으로 예외를 처리하는 3가지 방법을 알아봤다. 하지만 예외를 처리하는 방법보다도 초급 개발자가 가장 잊지 말아야 할 것은 예외를 잡고 아무런 처리도 하지 않는 것은 정말 위험한 행위라는 것이다. try/catch문으로 예외를 잡아놓고 catch를 비워두면 물론 컴파일 오류는 나지 않겠지만, 예외가 발생했을 때 그 원인을 파악하기가 어려워 개발은 물론 유지보수에 아주 치명적인 민폐를 끼치는 일이라고 생각한다. 따라서 어떤 처리를 해야 하는지 모르더라도 무작정 catch하고 무시하거나, throw해버리는 행위를 할 때는 더욱 신중해야 할 것이다.

 


트랜잭션 (Transaction)


갑자기 "트랜잭션"이라는것이 나와서 뜬금없다 생각할 수도 있겠지만 트랜잭션과 예외처리는 매우 밀접한 관련이 있다. 트랜잭션과 예외처리가 서로 어떤 관련이 있는지 알아보도록 하자.

트랜잭션은 하나의 작업 단위를 뜻한다.

쇼핑몰의 "상품발송"이라는 트랜잭션을 가정 해 보자.

"상품발송"이라는 트랜잭션에는 다음과 같은 작업들이 있을 수 있다.

  • 포장
  • 영수증발행
  • 발송

이 3가지 일들 중 하나라도 실패하면 3가지 모두 취소하고 "상품발송"전 상태로 되돌리고 싶을 것이다. (모두 취소하지 않으면 데이터의 정합성이 크게 흔들리게 된다. 이렇게 모두 취소하는 행위를 보통 전문용어로 롤백(Rollback)이라고 말한다.)

프로그램이 다음과 같이 작성되어 있다고 가정 해 보자. (※ 아래는 실제 코드가 아니라 어떻게 동작하는지를 간략하게 표현한 pseudo 코드1이다.)

상품발송() {
    포장();
    영수증발행();
    발송();
}

포장() {
   ...
}

영수증발행() {
   ...
}

발송() {
   ...
}

쇼핑몰 운영자는 포장, 영수증발행, 발송이라는 세가지 중 1가지라도 실패하면 모두 취소하고 싶어한다. 이런경우 어떻게 예외처리를 하는 것이 좋겠는가? ^^

다음과 같이 포장, 영수증발행, 발송 메서드에서는 예외를 throw하고 상품발송 메서드에서 throw된 예외를 처리하여 모두 취소하는 것이 완벽한 트랜잭션 처리 방법이다.

상품발송() {
    try {
        포장();
        영수증발행();
        발송();
    }catch(예외) {
       모두취소();
    }
}

포장() throws 예외 {
   ...
}

영수증발행() throws 예외 {
   ...
}

발송() throws 예외 {
   ...
}

위와 같이 코드를 작성하면 포장, 영수증발행, 발송이라는 세개의 단위작업 중 하나라도 실패할 경우 "예외"가 발생되어 상품발송이 모두 취소 될 것이다.

만약 위 처럼 "상품발송" 메서드가 아닌 포장, 영수증발행, 발송메소드에 각각 예외처리가 되어 있다고 가정 해 보자.

상품발송() {
    포장();
    영수증발행();
    발송();
}

포장(){
    try {
       ...
    }catch(예외) {
       포장취소();
    }
}

영수증발행() {
    try {
       ...
    }catch(예외) {
       영수증발행취소();
    }
}

발송() {
    try {
       ...
    }catch(예외) {
       발송취소();
    }
}

이렇게 각각의 메소드에 예외가 처리되어 있다면 포장은 되었는데 발송은 안되고 포장도 안되었는데 발송이 되고 이런 뒤죽박죽의 상황이 연출될 것이다.

실제 프로젝트에서도 두번째 경우처럼 트랜잭션관리를 잘못하여 고생하는 경우를 많이 보았는데 이것은 일종의 재앙에 가깝다.

이번 챕터에서는 자바의 예외처리에 대해서 알아보았다. 사실 예외처리는 자바에서 좀 난이도가 있는 부분에 속한다.

보통 프로그래머의 실력을 평가 할 때 이 예외처리를 어떻게 하고 있는지를 보면 그 사람의 실력을 어느정도 가늠해 볼 수 있다고들 말한다. 예외처리는 부분만 알아서는 안되고 전체를 관통하여 모두 알아야만 정확히 할 수 있기 때문이다.




http://homo-ware.tistory.com/161 

http://www.nextree.co.kr/p3239/

https://wikidocs.net/229

 


+ Recent posts