1. library 추가

- version 1.1 이상 필요

- commons-fileupload.jar

- commons-io.jar


2. Bean 주입 ( applicationContext.xml )

-[bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /]

- 이부분에 대한 추가적인 설정이나 설명은 각자 찾아보길 바란다.


3. Source part

// 파일 첨부를 위해 추가 import 구문

import org.springframework.util.FileCopyUtils;

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.multipart.MultipartHttpServletRequest;

import org.springframework.web.multipart.commons.CommonsMultipartResolver;


public class ActionClassName extends MappingDispatchActionSupport {


       private CommonsMultipartResolver cResolver;

       // Multipartrequest를 컨트롤하는 객체를 주입받는다.

       cResolver = (CommonsMultipartResolver) getWebApplicationContext().getBean("multipartResolver");    

       

       public ActionForward adminGoodStoreSave(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {


        MultipartHttpServletRequest mrequest = cResolver.resolveMultipart(request);

        String realPath      = request.getSession().getServletContext().getRealPath("/");    // 실제 서버의 주소는 이런식으로 가져올 수도 있다.

        String uploadPath    = realPath + customPath;


          // Multipartrequest 에서 파일 객체부분을 가저온다.

        Map fileMap = mrequest.getFileMap();


        for (Iterator it=fileMap.entrySet().iterator(); it.hasNext();) {

            Entry entry      = (Entry)it.next();

            MultipartFile mf = (MultipartFile)entry.getValue();


            if (!mf.isEmpty()) {

                if(mf.getSize() > 0){

       

                    // 원본 파일명

                    String originalFilename = mf.getOriginalFilename();

                    // 새로운 파일명은 시간(밀리세컨드까지)으로 변경하고 

                    // 확장자만을 갖는다.

                    String newFilename =  System.currentTimeMillis() + originalFilename.substring(originalFilename.length() -4, originalFilename.length());;


                    File fOri = new File(uploadPath, newFilename);


                    // 실제 파일을 작성한다.

                    InputStream is = mf.getInputStream();

                    OutputStream os = new FileOutputStream(fOri);

                    FileCopyUtils.copy(is, os);


                    /* 

                     * 여기에서 파일 필드를 구분하여 처리를 한다.

                     * mf.getName() 또는 entry.getKey().toString() 으로 

                     * input Tag의 name을 확인할 수 있다.

                     */

                    if"file1".equals(mf.getName()) ) {

                            /* 파일 필드1에 대한 처리 구역 */

                    } else if"file2".equals(mf.getName()) ) {

                            /* 파일 필드2에 대한 처리 구역 */

                    } else if"file3".equals(mf.getName()) ) {

                            /* 파일 필드3에 대한 처리 구역 */

                    }


                    os.close();

                    is.close();


                }

            }

        }

        return mapping.findForward(Constants.SUCCESS_KEY);

    }

       

}

       

       

4. JSP Tag part

<html>

<head>

</head>

<body>

....

<tr>

<td align="center" width="100">첨부1</td>

<td colspan="3"><input type="file" name="file1" id="file1"></td>

</tr>

<tr>

<td align="center" width="100">첨부2</td>

<td colspan="3"><input type="file" name="file2" id="file2"></td>

</tr>

<tr>

<td class="rowtitle" align="center" width="100">첨부3</td>

<td colspan="3"><input type="file" name="file3" id="file3"></td>

</tr>

...

</body>

</html>


프로젝트를 진행하면서 새로운 사실을 발견했다~

java에서 String 타입의 null 값 더하기 String 타입의 null은 String 타입 nullnull이 된다....
찾아본 결과 아래의 글을 찾아볼 수 있었는데...
String 타입의 편의성을 위해 그렇게 된다고 하는데....

자바를 오랜 기간 다루었던 사람들은 알지도 모르는 것이지만 
다룬 기간이 짧은 나로서는 새로우면서도 조금은 쏘킹했다.

오오 널널~

===========================================================================================

[ null String value in Java ] null String problem


자바에서 String 을 다룰때 조심해야할 사항들이 있다.

junit으로 테스트 하면 다음의 테스트 클래스는 성공적으로 실행된다.

public class Test_NullString extends TestCase {
    
    public void testNullString () {
        String nullString = null;
        String nullString2 = null;
        assertNull(nullString); // null 이다.
        nullString += "?";
        assertEquals("null?", nullString);
        assertEquals("nullnull", nullString + nullString );   
}

    
    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }
    public void testSameObject() {
        assertFalse(new TestObj() == new TestObj());
    }
    private String getJane(){ return "Hi";}
    private String getTom() { return "Hi";}
    
    private class TestObj{
        public boolean equals(Object obj) {
            if ( obj instanceof TestObj ) return true;
            else return false;
        }
    }
}

testNullString() 에서 볼 수 있듯이 null 로 초기화된 string의 경우 이어지는 string 값을 더할 경우 + 연산에 의해서 "null" 이 붙게 된다. null 로 초기화된 두개의 string 을 더할때에도 마찬가지로 그 결과는 "nullnull" 이 된다.

이런 결과는 null 에 대한 보통의 상식과 전혀 다른 결과이다. null 인 스트링과 null 이 아닌 스트링을 더했을때 당연히 null 이 아닌 스트링만 나오기를 기대했었는데 의외의 결과...

왜 일반의 통념과 다르게 
null에 대한 + 연산을 이렇게 구현해야 했을까?

우선 + 연산이 스트링에 대해서 아주 빈번하게 사용된다는 점을 들 수 있는데, + 연산이 구체적으로 어떻게 실행되는지 api 의 코드에는 나와있지 않아서 알 수 없지만 내부적으로 StringBuffer 와 동일한 규칙이 적용되고 있다고 짐작할 따름이다.(StringBuffer의 경우도 append( null ); 이 들이오면 "null" 을 붙이도록 구현되어 있다. 뭐냐 이거.. -_-;;)

만일 일반의 생각대로 ((String)null) + "a" 의 결과가 "a" 라고 하자.

그러면 결과는 "a"가 나올 수 없는데, null 이기 때문에 NullPointerException 이 던져져야 한다. null 로 초기화된 객체는 아무런 연산 행위도 갖지 못한다. 무의식 중에 스트링에 대해서 + 연산을 실행하지만 이런 편의를 위해서 위와같이 null 스트링을 처리하는 것이 아닌가...짐작할 뿐이다.

예를 들어 ((Object)null) + ((Object)null) 을 하면 컴파일 에러가 던져진다. Object 형에 대해서는 + 연산이 정의되어있지 않기 때문... 결국 String 객체에 + 연산을 적용하기 위해서 null에 대해 특별한 처리를 한 것 같다.

두번째 예제도 나름 흥미롭다.

    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }

보통 == 와 equals(Object o) 에 대한 닳고 닳은 설명들과 달리 위 테스트도 모두 성공한다. 첫번째와 달리 두번째, 세번째 테스트가 false로 나오는 것에 주목할 필요가 있다.

자바소스 코드 내에서 "...." 로 둘러싸인 부분들은 모두 ConstantPool에 저장된다. ConstantPool이란 자바 클래스 파일에 상수값들만 따로 뽑아놓은 값들인데 동일한 문자열들은 모두 하나의 스트링 값으로 참조되므로 첫번째 "home" == "home"은 true가 된다.(동일한 물리 주소를 참조)

두번째 세번째 assert 에서 new 로 새로운 스트링객체를 만드는데 이것들은 ConstantPool에 있는 스트링을 통해서 새로 만들어진 객체라는게 중요하다.(서로 다른 물리주소를 참조하므로 false이다.)

상식과 어긋나 보이는 이런 규칙들은 대부분의 경우 편리하고 좋지만 안좋을 때도 있다. 일례로 이 글을 쓰게 된 것도 Servlet 에서 getQueryString() 이 반환하는 null 값이 예상치 못한 행동을 하고(위에 설명한...) 그 이유를 알아보다 위와같은 테스트까지 하게 된 것이다.

어찌보면 null의 문제라기보다는 null을 반환하도록 코딩된 서블릿의 구현이 문제가 있는게 아닌가 싶다. 사실 모든 메소드가 인스턴스를 반환할때 null을 피하는 것이 좋다.특히 + 연산이 허용된 java.lang.String 의 경우는 더욱 그러하다. 하지만 스펙에는 아주 용감하게 "쿼리 스트링이 없을 경우 null 을 반환한다" 라고 쓰여져 있다.

덕분에 두시간 넘게 헤매고 다녔다. -_-;;

웬만하면 스트링을 초기화할때는 null 이 아니라 "" 로 초기화해야한다.

           String query = "":

물론 String을 반환하는 메소드의 경우 ""과 null 이 서로 다른 의미를 갖는게 아니라면 무조건!!! "" 을 반환하도록 하는게 옳다고 생각한다. 

1. *.jsp 파일에서 엑셀로 열거나 저장하기 위해서 <head>부분에 삽입 필요.
2. 열기를 하면 브라우저 창에서 열림.
3. 각 컬럼 색상이나 모양등은 엑셀에서 제공하는 색상만이 지원되며 (웹 색상은 지원되지 않는듯....) 컬럼모양 역시 엑셀 서식을 기준으로 적용된다.
4. *.css파일을 참조할 수 있으며, 엑셀로 내려받는 것에는 문제가 없으나 엑셀파일을 열었을 경우 해당 파일이 없다며 오류가 발생.

=== 추가 부분 ===
<head>
<!-- 엑셀 파일이라는 걸 브라우저에 알려주는 부분 -->
<%@ page contentType="application/vnd.ms-excel; name='My_excel'; text/html; charset=euc-kr"%> 
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr" />
<title>Untitled Document</title>
<%
//해당 파일에 추가 설정
//저장을 눌렀을 때 저장 파일명이 myexcel 이 된다.
//지정해주지 않으면 기본적으로 *.jsp파일 명이 되는 듯.
response.setHeader("Content-Disposition", "attachment; filename=myexcel");
response.setHeader("Content-Description", "JSP Generated Data");   
%> 
</head>
출처 : http://tong.nate.com/reizes/22327742

LOG4J

I. 들어가면서.. 그리고 log4j
log4j는 자바 어플리케이션에서 빠르고 효과적으로 로깅 할 수 있도록 도와주는 오픈 소스 프로젝트입니다. 로깅(logging)은 코드의 가독성을 떨어뜨리는 단점이 있지만 애플리케이션에 문제가 있을 때 개발자가 자세한 상황을 파악할 수 있도록 해 주며 테스팅시 빠질 수 없는 요소입니다. 아마도 여러분들은 여러 어플리케이션이 추가되면서 각 개발자들만의 독특한 로깅방식이 서로 썩이고 얽혀서 화면에 나타나는것을 많이 봤을겁니다 -_-; 즉 로깅방법을 통일할 필요가 있는것이죠. 모든 개발자가 특정 포맷에 맞추어서 로깅 한다면 한결 로깅하기도 편하겠지요. 오픈 소스 프로젝트인 Log4j는 개발자들이 매우 손쉽고 다양한 형태로 로깅을 할 수 있도록 도와줍니다. 성능또한 우수해 더이상 System.out.println을 사용할 필요가 없습니다.

II. 다운로드
다운로드 http://logging.apache.org/log4j/docs/download.html
매뉴얼
http://logging.apache.org/log4j/docs/documentation.html
API spec
http://logging.apache.org/log4j/docs/api/index.html

III. LOG4J 구조
① Logger(Category) : 로깅 메세지를 Appender에 전달합니다.
② Appender : 전달된 로깅 메세지를 파일에다 기록할 것인지, 콘솔에 출력할 것인지 아니면 DB에 저장할 것인지 매개체 역활을 합니다.
③ Layout : Appender가 어디에 출력할 것인지 결정했다면 어떤 형식으로 출력할 것이지 출력 layout을 결졍합니다.

IV. LOG4J 로깅 레벨
① FATAL : 가장 크리티컬한 에러가 일어 났을 때 사용합니다.
② ERROR : 일반 에러가 일어 났을 때 사용합니다.
③ WARN : 에러는 아니지만 주의할 필요가 있을 때 사용합니다.
④ INFO : 일반 정보를 나타낼 때 사용합니다.
⑤ DEBUG : 일반 정보를 상세히 나타낼 때 사용합니다.

만약 로깅 레벨을 WARN 으로 설정하였다면 그 이상 레벨만 로깅하게 됩니다.
즉 WARN, ERROR, FATAL 의 로깅이 됩니다.

V. 샘플코드 1

test.jsp</P><BR><P><%@pagecontentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger = Logger.getLogger("test.jsp");
%>

<%
logger.fatal("fatal!!");

logger.fatal("fatal2!!", new NullPointerException("널"));

logger.error("error!", new NumberFormatException());

logger.error("error!2");

logger.warn("warn");

logger.info("info");

logger.debug("debug");
%>


결과 콘솔화면








static Logger logger = Logger.getLogger("test.jsp");
static 메소드 getLogger를 통해 logger 인스턴스를 가져옵니다. getLogger에는 파라미터로 스트링 혹은 클래스를 사용하는데 jsp에서는 클래스를 파라미터로 주기에는 좀 애매합니다. 그냥 스트링으로 주도록 하지요

logger.fatal("fatal!!");
[ logger.fatal("fatal2!!", new NullPointerException("널입니다요")); ]
logger에 fatal 레벨의 메세지를 전달합니다. 다음 두가지 메소드를 지원하는군요

fatal(Object message) , fatal(Object message, Throwable t)

각 레벨마다 위처럼 두가지 메소드를 지원합니다.

지원 메쏘드
logger.fatal(Object message) logger.fatal(Object message, Throwable t)
logger.error(Object message) logger.error(Object message, Throwable t)
logger.warn(Object message) logger.warn(Object message, Throwable t)
logger.info(Object message) logger.info(Object message, Throwable t)
logger.debug(Object message) logger.debug(Object message, Throwable t)

VI. 샘플코드 2

TestServlet.java

import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class TestServlet extends HttpServlet {
    static Logger logger = Logger.getLogger(TestServlet.class);

    public void init(ServletConfig config) throws ServletException {
         super.init(config);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         try {
              ...
              logger.info("Hellow World~");
              ...
          } catch (Exception e) {
              logger.error("Error at TestServlet", e);
          }
     }
}



VII. LOG4J 설정
프로그램에서 설정

<TD><%@pagecontentType="text/html;charset=MS949"  import="org.apache.log4j.*,java.io.* " %>
<%!
 static Logger logger = Logger.getLogger("log4j.jsp");
%>

<%
String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
String logfilename = "DailyLog.log";
String datePattern = ".yyyy-MM-dd ";

PatternLayout patternlayout = new PatternLayout(layout);
DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
logger.fatal("fatal!!");
%>


property 파일에 설정
log4j.properties를 만들어 /WEB-INF/classes 밑에 놓으세요

log4j.rootLogger=INFO, stdout, rolling
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
log4j.appender.rolling.File=output.log
log4j.appender.rolling.Append= true
log4j.appender.rolling.MaxFileSize=500KB
log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#최상위 카테고리에 INFO로 레벨 설정 및 appender로 stdout, rolling을 정의
log4j.rootLogger= INFO, stdout, rolling

#stdout 어펜더는 콘솔에 뿌리겠다는 정의
log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#stdout 어펜더는 patternlayout을 사용하겠다는 정의
log4j.appender.stdout.layout= org.apache.log4j.PatternLayout

#페턴은 다음과 같이 포맷팅 하겠다는 것을 정의
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#역시나 rolling 어펜더는 파일로 처리한다라고 정의
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender

#로그 파일 이름은 output.log
log4j.appender.rolling.File=output.log

#true면 톰캣을 내렸다 올려도 파일이 리셋되지 않습니다.
log4j.appender.rolling.Append= true

#파일 최대 사이즈는 500KB로 설정
log4j.appender.rolling.MaxFileSize= 500KB

#파일 포맷은 output.log.2005-03-10 으로 관리하겠다고 정의
log4j.appender.rolling.DatePattern= '.'yyyy-MM-dd

#역시나 rolling 어펜더는 패턴 레이아웃을 사용하겠다고 정의
log4j.appender.rolling.layout= org.apache.log4j.PatternLayout

#rolling 어펜더는 패턴 레이아웃 포맷
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

VIII. 설정 포맷
로그파일명 포맷 (DatePattern)
로그파일명 포맷입니다. 날짜, 시간 및 분단위로까지 로그 파일을 분리할 수 있습니다.

형식 설명
'.'yyyy-MM 매달 첫번째날에 로그파일을 변경합니다
'.'yyyy-ww 매주의 시작시 로그파일을 변경합니다.
'.'yyyy-MM-dd 매일 자정에 로그파일을 변경합니다.
'.'yyyy-MM-dd-a 자정과 정오에 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH 매 시간의 시작마다 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH-mm 매분마다 로그파일을 변경합니다.

PatternLayout 포맷
로그자체를 어떤 포맷으로 남길지 결정합니다.
layout에는 HTMLLayout, PatternLayout, SimpleLayout, XMLLayout등이 있으며 PatternLayout이 일반적으로 가장 많이 쓰입니다.

형식 설명
%p debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m 로그내용이 출력됩니다
%d 로깅 이벤트가 발생한 시간을 기록합니다.포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t 로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%% % 표시를 출력하기 위해 사용한다.
%n 플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
%c 카테고리를 표시합니다
예) 카테고리가 a.b.c 처럼 되어있다면 %c{2}는 b.c가 출력됩니다.
%C 클래스명을 포시합니다.
예) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면 %C{2}는 xyz.SomeClass 가 출력됩니다
%F 로깅이 발생한 프로그램 파일명을 나타냅니다.
%l 로깅이 발생한 caller의 정보를 나타냅니다
%L 로깅이 발생한 caller의 라인수를 나타냅니다
%M 로깅이 발생한 method 이름을 나타냅니다.
%r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x 로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를 출력합니다.
%X 로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를 출력합니다.

예시) (같은 색끼리 보시면 됩니다) 위의 test.jsp를 다음 포맷으로 출력해본다면

[%c] [%C] [%d] [%F] [%l] [%L] [%m] [%M] [%n] [%p] [%r] [%t] [%x] [%X]는 다음과 같다

[test.jsp] [org.apache.jsp.test_jsp] [2005-03-10 12:37:23,561] [test_jsp.java] [org.apache.jsp.test_jsp._jspService(test_jsp.java:64)] [64] [fatal!!] [_jspService] [개행] [FATAL] [765567] [http-8080-Processor25] [] []

=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com 
=============================================

읏... struts.xml파일을 classes폴더에 두어야 하는데......
정말 바보처럼 Web-Inf 폴더에 두고 삽질만 2시간...쩝....


튜토리얼을 보니
struts2.x 는 브라우저 주소창에 *.action이라고 적어줘야 action이 일어난다고..
action이라고 다적을라니 struts1.x 의 *.do가 너무 그리웠다...ㅡ.ㅡ;;;;
(라기보다는 action이라는 단어가 귀찮았다...쩝....)

네이버 언냐~나 구글 어빠~ 등을 전전 긍긍하면서 찾아봤더니
역시나 struts.action.extension 이라는 설정항목이 있었다.

struts.properties파일에서 설정해주는 내용들이 대부분이었는데...
설정파일이 너무 많아지는 것같아서,
'한개의 파일에서 처리는 안돼는겨??' 란 오기아닌 오기가...
(설정파일이 너무 많다는건 언제나 불만이었다.)

딱! 맞는 내용은 찾을 수 없었지만... encoding설정 하는 내용에서 힌트를 얻어서
로컬에서 테스트를 해봤다....
하지만 위에있는 실수와 삽질덕에.....

잊어버리기 전에(그렇진 않겠지만...) 적어둔다.
3개중에 1개만 해주면 된다.

=== Web.xml 에서의 설정 ===
....
<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
   <init-param>
      <param-name>struts.i18n.encoding</param-name>
      <param-value>UTF-8</param-value>
   </init-param>
   <init-param>
      <param-name>struts.devMode</param-name>
      <param-value>true</param-value>
   </init-param>
   <init-param>
      <param-name>struts.action.extension</param-name>
      <param-value>action,do</param-value>
   </init-param>
</filter>
...

=== struts.xml 에서의 설정 ===
...
<struts>
    <constant name="struts.i18n.encoding" value="UTF-8" />
    <constant name="struts.devMode" value="true" />
    <constant name="struts.action.extension" value="action,do" />
...
</struts>
...


=== struts.properties 에서의 설정 ===
...
struts.action.extension=action,do
...

Java 어노테이션(Annotation)에 관한 간략한 소개
Java Fundamental 2007/01/14 03:53

원본 : http://bioportal.weizmann.ac.il/course/prog2/tutorial/java/javaOO/annotations.html

JDK 5.0 릴리즈에는 어노테이션(annotation)이라 불리는 메타데이터 기능이 도입되었다. 어노테이션은 코드 조각의 작성자 명이나 컴파일러가 특정 오류를 억제하도록 지시하는 것과 같이 프로그램의 일부가 아닌 프로그램에 관한 데이터를 제공해 준다. 어노테이션은 코드가 어떻게 수행되는 것에는 아무런 영향을 주지 않는다.

어노테이션은 @어노테이션의 형태를 사용하고 클래스, 필드, 메소드 등과 같은 프로그램의 선언부에 적용할 수 있다. 어노테이션은 가장 처음으로 그리고 종종(관례상) 그 줄에 나타나며 임의의 인수를 포함할 수 있다:

        @Author("MyName")

        class myClass() { }

혹은

        @SuppressWarnings("unchecked")

        void MyMethod() { }


여러분만의 어노테이션을 정의하는 것은 여기에 기술되지 않을 고급기법에 속하지만, 모든 자바 프로그래머들이 알아야 하는 3개의 내장 어노테이션이 있다: @Deprecated, @Override, 그리고 @SuppressWarnings가 그것이다. 다음 예제들은 메소드에 적용된 모든 세 가지 타입의 어노테이션을 보여준다:

import java.util.List;


class Food {}

class Hay extends Food {}

class Animal {

    Food getPreferredFood() {

        return null;

    }

    /**

     * @deprecated document why the method was deprecated

     */

    @Deprecated

    static void deprecatedMethod() { }

}

class Horse extends Animal {

    Horse() {

        return;

    }

    @Override

    Hay getPreferredFood() {

        return new Hay();

    }

    @SuppressWarnings("deprecation")

    void useDeprecatedMethod() {

        Animal.deprecateMethod(); //deprecation warning - suppressed

    }

}


@Deprecated

@Deprecated 어노테이션은 표시된 메소드가 더 이상 사용되지 말아야 함을 가리킨다. 컴파일러는 프로그램이 비추천(deprecated) 메소드나 클래스 혹은 변수를 사용할 때마다 경고를 발생시킨다. 한 요소가 비추천되면 그것은 앞의 예제에 나타나 있는 것처럼 해당되는 @deprecated 태그를 사용하여 문서화 되어야 한다. 태그는 소문자 ‘d’로 시작하며 어노테이션은 대문자 ‘D’로 시작함을 주의하라. 일반적으로 여러분은 비추천 메소드의 사용을 피해야 하며 그것 대신 무엇을 사용해야 하는지를 확인해야 한다.


@Override

@Override 어노테이션은 요소가 상위 클래스에서 선언된 한 요소를 오버라이드 할 것임을 컴파일러에게 알려준다. 앞의 예제에서는 오버라이드 어노테이션이 Horse 클래스의 getPreferredFood 메소드가 Animal 클래스에 있는 동일한 메소드를 오버라이드하고 있음을 가리키는 데 사용되었다. @Override로 표시된 한 메소드가 상위 클래스에 있는 메소드를 오버라이드하는 데 실패할 경우 컴파일러는 에러를 발생시킨다.


메소드를 오버라이드 할 때 이 어노테이션을 사용하도록 요구되는 것은 아니기 때문에 특히 메소드가 오버라이드된 메소드의 리턴 타입의 하위 타입을 리턴할 때 명시적으로 그것을 드러내도록 요구하는 것이 유용할 수 있다. 이러한 관례는 “공변 리턴 타입(covariant return type)”이라 불리는데, 이전의 Food 인스턴스를 리턴하는 Animal.getPreferredFood 예제에서 사용되었다. Horse.getPreferredFood (Horse은 Animal의 하위클래스)는 Hay의 인스턴스를 리턴한다. 좀 더 자세한 정보를 위해서는 Overriding and Hiding Methods를 참조하라.


@SuppressWarnings

@SuppressWarnings 어노테이션은 컴파일러가 다르게 발생될 특정 경고를 억제하도록 해준다. 이전의 예제에서 useDeprecatedMethod는 Animal의 비추천 메소드를 호출한다. 보통 컴파일러는 경고를 발생시킬테지만, 이 경우에는 그것이 억제된다.

모든 컴파일러 경고는 한 종류에 속한다. 자바 언어 명세(Java Language Specification)는 “비추천(deprecation)”과 “비확인(unchecked)”의 두 가지 종류를 나열한다. “비확인” 경고는 제네릭이 나오기 전에 작성된 레거시 코드와 인터페이싱할 경우에 발생할 수 있다. 하나 이상의 경고 종류를 억제하려면 다음의 문법을 사용하라.

@SuppressWarnings({"unchecked", "deprecation"})

지원되는 경고 종류들의 목록을 보려면 컴파일러 문서를 확인하라.


어노테이션을 좀 더 고급스럽게 사용하는 것에는 자바 프로그램을 읽어 그것의 어노테이션을 처리할 수 있는 프로그램을 작성하는 것이 포함되어 있다. 이 작업을 돕기 위해, JDK 5.0 릴리즈에는 apt라 불리는 어노테이션 처리도구(annotation processing tool)가 포함되어 있다. 다음의 JDK 릴리즈(코드네임 머스탱)에는 apt 기능이 자바 컴파일러의 표준 요소가 될 것이다. 좀 더 자세한 정보를 보려면 Getting Started with the Annotation Processing Tool 를 참조하라. 머스탱의 진척사항에 관한 더 자세한 정보를 원한다면 Language Model API JSR 269: Pluggable Annotation Processing API 을 참고하라.

+ Recent posts