[Java] 예외 만들기


예외 만들기


1. 예외 발생시키기

키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.

1) 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음

  Exception e = new Exception("고의로 발생시켰음");


2) 키워드 throw를 이용해서 예외를 발생시킨다.

  throw  e;


class ExceptionEx

{

public static void main(String args[]) 

{

try {

Exception e = new Exception("고의로 발생시켰음.");

throw e; // 예외를 발생시킴

//  throw new Exception("고의로 발생시켰음.");  // 위의 두 줄을 한 줄로 줄여 쓸 수 있다.

} catch (Exception e) {

System.out.println("에러 메시지 : " + e.getMessage());

e.printStackTrace();

}

System.out.println("프로그램이 정상 종료되었음.");

}

}

실행결과)

에러 메시지 : 고의로 발생시켰음.

프로그램이 정상 종료되었음.

java.lang.Exception: 고의로 발생시켰음.

at example.demo.main(demo.java:8)


2. 사용자 정의 예외 만들기

기존의 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다. 보통 Exception클래스로부터 상속받는 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.

class MyException extends Exception{

MyException(String msg){// 문자열을 매개변수로 받는 생성자

super(msg);// 조상인 Exception 클래스의 생성자를 호출한다.

}

}


Exception 클래스로부터 상속받아서 MyException클래스를 만들었다. 필요하다면, 멤버변수나 메서드를 추가할 수 있다. Exception 클래스는  생성 시에 String값을 받아서 메시지로 저장할 수 있다. 사용자 정의 예외 클래스도 메시지를 저장할 수 있으려면, 위에서 보는 것과 같이 String을 매개변수로 받는 생성자를 추가해주어야 한다.

class MyException extends Exception{// 에러 코드 값을 저장하기 위한 필드를 추가 했다.

private final int ERR_CODE;// 생성자를 통해 초기화 한다.

MyException(String msg, int errcode){ //생성자

super(msg);

ERR_CODE=errCode;

}

MyException(String msg){// 생성자

this(msg, 100)l// ERR_CODE를 100(기본값)으로 초기화한다.

}

public int getErrCode(){// 에러 코드를 얻을 수 있는 메서드도 추가한다.

return ERR_CODE;// 이 메서드는 주로 getMessage()와 함께 사용될 것이다.

}

}

이전의 코드를 좀더 개선하여 메시지뿐만 아니라 에러코드 값도 저장할 수 있도록 ERR_CODE와 getErrCode()를 MyException클래스의 멤버로 추가했다.

이렇게 함으로써 MyException이 발생했을 때, catch블럭에서 getMessage()와 getErrCode()를 사용해서 에러코드와 메시지를 모두 얻을 수 있을 것이다.


class NewExceptionTest {

public static void main(String args[]) {

try {

startInstall(); // 프로그램 설치에 필요한 준비를 한다.

copyFiles(); // 파일들을 복사한다. 

} catch (SpaceException e) {

System.out.println("에러 메시지 : " + e.getMessage());

e.printStackTrace();

System.out.println("공간을 확보한 후에 다시 설치하시기 바랍니다.");

} catch (MemoryException me) {

System.out.println("에러 메시지 : " + me.getMessage());

me.printStackTrace();

System.gc(); //  Garbage Collection을 수행하여 메모리를 늘려준다.

System.out.println("다시 설치를 시도하세요.");

} finally {

deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.

} // try의 끝

} // main의 끝


   static void startInstall() throws SpaceException, MemoryException { 

if(!enoughSpace()) // 충분한 설치 공간이 없으면...

throw new SpaceException("설치할 공간이 부족합니다.");

if (!enoughMemory()) // 충분한 메모리가 없으면...

throw new MemoryException("메모리가 부족합니다.");

   } // startInstall메서드의 끝


   static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }

   static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다.*/}

   

   static boolean enoughSpace()   {

// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.

return false;

   }

   static boolean enoughMemory() {

// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.

return true;

   }

} // ExceptionTest클래스의 끝


class SpaceException extends Exception {

SpaceException(String msg) {

  super(msg);

   }


class MemoryException extends Exception {

MemoryException(String msg) {

  super(msg);

   }

}

실행결과)

에러 메시지 : 설치할 공간이 부족합니다.

example.SpaceException: 설치할 공간이 부족합니다.

at example.ExceptionEx18.startInstall(ExceptionEx18.java:24)

at example.ExceptionEx18.main(ExceptionEx18.java:6)

공간을 확보한 후에 다시 설치하시기 바랍니다.

MemoryException과 SpaceException, 이 두개의 사용자정의 예외 클래스를 새로 만들어서 사용했다

Space Exception은 프로그램을 설치하려는 곳에 충분한 공간이 없을 경우에 발생하도록 했으며, MemoryException은 설치작업을 수행하는데 메모리가 충분히 확보되지 않았을 경우에 발생하도록 하였다. 이 두 개의 예외는 startInstall()을 수행하는 동안에 발생할 수 있으며, enoughSpace()와 enoughMemory()의 실행 결과에 따라서 발생하는 예외의 종류가 달라지도록 했다.


'Programing > Java' 카테고리의 다른 글

[Java] Wrapper 클래스  (0) 2015.04.13
[Java] 예외 던지기  (0) 2014.12.25
[Java] 예외 처리 기본  (0) 2014.12.25
[Java] enum  (0) 2014.12.25
[Java] for-each문  (0) 2014.12.23

[Java] 예외 처리 기본


예외 처리 기본


컴파일 에러: 컴파일 할때 발생하는 에러이다.

런타임 에러: 프로그램의 실행도중에 발생하는 에러이다.


소스코드를 컴파일 하면 컴파일러가 소스코드(.java)에 대해 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류가 있는지 알려준다. 컴파일러가 알려 준 에러들은 모두 수정해서 컴파일을 성공적으로 마치고 나면, 클래스 파일(.class)이 생성되고, 생성된 클래스 파일을 실행할 수 있게 되는 것이다.

컴파일러가 소스코드의 기본적인 사항은 컴파일시에 모두 걸러줄 수 있지만, 실행 도중에 발생할 수 있는 잠재적인 오류까지 검사할 수 없기 때문에 컴파일은 잘되었어도 실행 중에 에러에 의해서 잘못된 결과를 얻거나 프로그램이 비정상적으로 종료될 수 있다.


자바에서는 실행 시(runtime) 발생할 수 있는 프로그램 오류를 '에러'와 '예외' 두 가지로 구분하였다.

에러는 메모리 부족이나 스택오버플로우와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 것이다.

-> 에러와 예외는 모두 실행 시(runtime) 발생하는 오류이다.


1. 예외 클래스


RuntimeException 클래스들은 주로 프로그래머의 실수에 의해서 발생할 수 있는 예외들로서 자바의 프로그래밍 요소와 관계가 깊다.

--> 프로그래머의 실수로 발생하는 예외

Exception 클래스들은 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다.

--> 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외

RuntimeException 클래스들과 Exception 클래스들의 중요한 차이점은 컴파일시의 예외처리 체크여부이다. RuntimeException 클래스들 그룹에 속하는 예외가 발생할 가능성이 있는 코드에는 예외 처리를 해주지 않아도 컴파일 시에 문제가 되지 않지만, Exception 클래스들 그룹에 속하는 예외가 발생할 가능성이 있는 예외는 반드시 처리를 해주어야 하며, 그렇지 않으면 컴파일 시에 에러가 발생한다.


class ExceptionEx7 

{

public static void main(String[] args) 

{

throw new Exception(); // Exception을 강제로 발생시킨다.

}

}

이 예제를 작성한 후에 컴파일 하면 컴파일이 완료되지 않는다. 예외처리가 되어야 할 부분에 예외처리가 되어 있지 않다는 에러이다. 위의 결과에서 알 수 있는 것처럼, 위에서 분류한 '그 외의 Exception클래스들'이 발생할 가능성이 있는 문자들에 대해 예외처리를 해주지 않으면 컴파일 조차 되지 않는다.


따라서 위의 예제를 아래와 같이 try-catch 문으로 처리해 주어랴 컴파일이 성공적으로 이루어 질 것이다.

class ExceptionEx8 {

public static void main(String[] args) 

{

try {

throw new Exception();

} catch (Exception e) {

System.out.println("Exception이 발생했습니다.");

}

} // main메서드의 끝

}


※ 주의 할점

class ExceptionEx9 

{

public static void main(String[] args) {

throw new RuntimeException(); // RuntimeException을 강제로 발생시킨다.

}

}

위의 예제를 컴파일 하면, 예외를 처리하지 않았음에도 불구하고 이전의 예제와는 달리 성공적으로 컴파일이 될 것이다. 그러나 실행하면, RumtimeException이 발생하여 비정상적으로 종료될 것이다.

이 예제가 명백히 RuntimeException을 발생시키는 코드를 가지고 있고, 이에 대한 예외처리를 하지 않았음에도 불구하고 성공적으로 컴파일 되었다.

이와 같이 RuntimeException클래스들은 예외처리를 해주지 않아도 컴파일러가 문제삼지 않는 것을 알아야 한다.


2. 예외 처리의 정의와 목적

프로그램의 실행 도중에 발생하는 에러는 어쩔 수 없지만, 예외는 프로그래머가 이에 대한 처리를 미리 해주어야 한다.

예외처리란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 하는 것이다.


정의:  프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것.

목적: 프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지하는 것.


3. try...catch...finally

try...catch는 예외에서 핵심적인 역할을 담당하는 문법적인 요소다. 형식을 살펴보자.

class Calculator{

    int left, right;

    public void setOprands(int left, int right){

        this.left = left;

        this.right = right;

    }

    public void divide(){

        try {

            System.out.print("계산결과는 ");

            System.out.print(this.left/this.right);

            System.out.print(" 입니다.");

        } catch(Exception e){

            System.out.println("\n\ne.getMessage()\n"+e.getMessage());

            System.out.println("\n\ne.toString()\n"+e.toString());

            System.out.println("\n\ne.printStackTrace()");

            e.printStackTrace();

        }

    }

public class CalculatorDemo {

    public static void main(String[] args) {

        Calculator c1 = new Calculator();

        c1.setOprands(10, 0);

        c1.divide();

    }

}

실행결과)
e.getMessage()
/ by zero
 
e.toString()
java.lang.ArithmeticException: / by zero
 
e.printStackTrace()
java.lang.ArithmeticException: / by zero
    at org.opentutorials.javatutorials.exception.Calculator.divide(CalculatorDemo.java:11)
    at org.opentutorials.javatutorials.exception.CalculatorDemo.main(CalculatorDemo.java:25)
1) e.getMessage();
오류에 대한 기본적인 내용을 출력해준다. 상세하지 않다.
2) e.toString()
e.toString()을 호출한 결과는 java.lang.ArithmeticException: / by zero 이다. e.toString()은 e.getMessage()보다 더 자세한 예외 정보를 제공한다. java.lang.ArithmeticException은 발생한 예외가 어떤 예외에 해당하는지에 대한 정보라고 지금을 생각하자. ArithmeticException 수학적인 계산의 과정에서 발생하는 예외상황을 의미한다. (우리는 어떤 숫자를 0으로 나누려고 하고 있다는 것을 상기하자)
3) e.printStackTrace()
메소드 getMessage, toString과는 다르게 printStackTrace는 리턴값이 없다. 이 메소드를 호출하면 메소드가 내부적으로 예외 결과를 화면에 출력한다. printStackTrace는 가장 자세한 예외 정보를 제공한다.


(1) try


try 안에는 예외 상황이 발생할 것으로 예상되는 로직을 위치시킨다. 예를 들어, 사용자가 setOprands의 두 번째 인자로 숫자 0을 입력했을 때 문제가 발생할 수 있음을 예측할 수 있다. 그래서 이 로직을 try 구문으로 감싼 것이다.


(2) catch


catch 안에는 예외가 발생했을 때 뒷수습을 하기 위한 로직이 위치한다. 


(3) finally

class A{

    private int[] arr = new int[3];

    A(){

        arr[0]=0;

        arr[1]=10;

        arr[2]=20;

    }

    public void z(int first, int second){

        try {

            System.out.println(arr[first] / arr[second]);

        } catch(ArrayIndexOutOfBoundsException e){

            System.out.println("ArrayIndexOutOfBoundsException");

        } catch(ArithmeticException e){

            System.out.println("ArithmeticException");

        } catch(Exception e){

            System.out.println("Exception");

        } finally {

            System.out.println("finally");

        }

    }

}

 

public class ExceptionDemo1 {

    public static void main(String[] args) {

        A a = new A();

        a.z(10, 0);

        a.z(1, 0);

        a.z(2, 1);

    }

}

실행결과)

ArrayIndexOutOfBoundsException

finally

ArithmeticException

finally

2

finally


'Programing > Java' 카테고리의 다른 글

[Java] 예외 던지기  (0) 2014.12.25
[Java] 예외 만들기  (0) 2014.12.25
[Java] enum  (0) 2014.12.25
[Java] for-each문  (0) 2014.12.23
[Java] 오토박싱  (0) 2014.12.23

[JSP] HTTP 헤더


HTTP 헤더


1. HTTP 헤더


아래 헤더 정보는 텍스트만 있는 HTML 문서에 대한 요청/응답 헤더임.

HTTP Version 1.1을 기준으로 작성된 헤더 정보임.

(클라이언트 환경은 Windows XP, NET Framework 1.1, IE 6.0)


2. 요청 헤더


(1)  GET /test/test.htm HTTP /1.1

요청 Method와 요청 파일 정보, HTTP 버전을 뜻함.

HTTP 프로토콜은 클라이언트가 서버에게 요청하는 방식에 대한 몇 가지 동작을 정의하고 있음.

즉, 요청 Method 란 클라이언트가 서버로의 요청하는 방법을 명시함.


1) GET

지정된 리소스(URI)를 요청함.

 

2) POST

서버가 클라이언트의 폼 입력 필드 데이터의 수락을 요청함.

클라이언트는 서버로 HTTP Body에 Data 를 전송함.

 

3) HEAD

문서의 헤더 정보만 요청함.

응답 데이터(Body)를 받지 않음.


4) PUT

클라이언트가 전송한 데이터를 지정한 URI로 대체함.

cf.) FTP의 PUT과 동일함.

역시 클라이언트는 서버로 HTTP Body에 Data를 전송함.

 

5) DELETE

클라이언트가 지정한 URI 를 서버에서 삭제함.

 

6) TRACE

 클라이언트가 요청한 자원에 도달하기까지의 경로를 기록하는루프백(loop back) 검사용을 말함.

클라이언트가 요청 자원에 도달하기 까지 거쳐가는 프록시나 게이트웨이의 중간 경로부터 최종 수진 서버까지의 경로를 알아낼 때 사용함.

 

(2) Accept

클라이언트가 허용할 수 있는 파일 형식을 뜻함.(MIME TYPE)

*/* 은 특정 유형이 아닌 모든 파일형식을 다 지원한다는 의미임.


(3) User-Agent

클라이언트 소프트웨어(브라우저, OS 등)의 이름과 버전 등을 뜻함.

위의 정보에서는 MS IE 6.0, 윈도우 XP, .NET Framework 1.1 버전이 클라이언트에 설치되어 있음을 나타냄.


(4) Host

요청을 한 서버의 Host 임.


(5) If-Modified-Since

페이지가 수정되었으면 최신 버전 페이지 요청을 위한 필드임.

만일 요청한 파일이 이 필드에 지정된 시간 이후로 변경되지 않았다면, 서버로부터 데이터를 전송 받지 않음.

단, 이 경우 서버로부터 Not Modified (304) 상태코드를 전송 받음.


위의 헤더 정보는 동일한 파일을 재요청했을 때의 응답 헤더임.

파일을 변경사항이 없으므로 304(수정되지 않음)과 Content-Length : 0(데이터 받지 않음) 응답을 받음.

이렇게 함으로써 HTTP 는 요청의 부하를 줄이고 있음.


(6)  Refer

특정 페이지에서 링크를 클릭하여 요청을 하였을 경우에 나타나는 필드로써 링크를 제공한 페이지를 나타냄.

위의 요청 헤더에는 나와 있지 않지만 이 정보도 헤더에 자주 등장하는 필드임.


(7) Cookie

웹 서버가 클라이언트에 쿠키를 저장해 놓았다면 해당 쿠키의 정보를 이름과 값 쌍으로 웹 서버에게 전송함.

역시 위의 요청에는 없지만 자주 등장하는 필드임.


(8) Accept-Language

클라이언트가 인식할 수 있는 언어를 뜻함.

우선 순위 지정이 가능합니다


(9) Accept-Encoding

클라이언트가 인식할 수 있는 인코딩(압축) 방법을 뜻함.

위의 내용에서는 서버에서 gzip, deflate 로 압축한 리소스를 클라이언트가 해석 할 수 있다는 것을 뜻함.

만일 서버에서 압축을 했으면 응답헤더에 Content-Encoding 헤더에 해당 압축 방법이 명시됨.


3. 응답 헤더

(1)  HTTP /1.1 200 OK

HTTP 버전과 응답 코드를 뜻함.

여기서 200 은 성공을 뜻함.


(2) Server

웹 서버 정보를 나타냄.

위의 정보에서는 Microsoft IIS 5.1 을 뜻함.


(3) Date

현재 날짜를 나타냄.


(4) Content-Type

요청한 파일의 MIME 타입을 나타냄.

text/html 은 text 중 html 파일임을 나타냄.


(5) Last-Modified

요청한 파일의 최종 수정일을 나타냄.


(6) Content-Length

헤더 이후 이어지는 데이터의 길이을 뜻함. (바이트 단위)

이어지는 데이터란 요청한 파일의 데이터라 보시면 됨.


(7) ETag

캐쉬 업데이트 정보를 위한 임의의 식별 숫자을 뜻함.


아래에 링크로 가시면 HTTP 1.1 기준의 Header 의 상세 정보를 보실 수 있음.

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html


'Programing > JSP/Servlet' 카테고리의 다른 글

[JSP] 커넥션 풀  (2) 2014.12.19
[Servlet] 서블릿 이벤트  (0) 2014.12.18
[Servlet] 서블릿 필터  (0) 2014.12.10
[Servlet] 서블릿 기초  (0) 2014.12.10
[Servlet] 데이터 저장 영역  (0) 2014.12.10

[Java] enum


enum


1. enum

enum은 열거형(enumerated type)이라고 부른다. 열거형은 서로 연관된 상수들의 집합(String과 같은 데이터 타입의 일종)이라고 할 수 있다.(배열은 서로 연관된 값들의 집합) 위의 예제에서는 Fruit와 Company가 말하자면 열거인 셈이다. 이러한 패턴을 자바 1.5부터 문법적으로 지원하기 시작했는데 그것이 열거형이다. 이전 코드를 enum으로 바꿔보자.


enum Fruit{

    APPLE, PEACH, BANANA;

}

//class Fruit{

//    public static final Fruit APPLE  = new Fruit();

//    public static final Fruit PEACH  = new Fruit();

//    public static final Fruit BANANA = new Fruit();

//}--> 위의 코드와 같은 의미


enum Company{

    GOOGLE, APPLE, ORACLE;

}

 

public class ConstantDemo {

     

    public static void main(String[] args) {

        /*

        if(Fruit.APPLE == Company.APPLE){

            System.out.println("과일 애플과 회사 애플이 같다.");

        }

        */

        Fruit type = Fruit.APPLE;

        switch(type){

            case APPLE://Fruit.APPLE로 명시할 시 에러(상수만 적도록 약속)

                System.out.println(57+" kcal");

                break;

            case PEACH:

                System.out.println(34+" kcal");

                break;

            case BANANA:

                System.out.println(93+" kcal");

                break;

        }

    }

}


실행결과)

57 kcal

위의 코드를 하나씩 살펴보자.

enum Fruit{

    APPLE, PEACH, BANANA;

}

enum은 class, interface와 동급의 형식을 가지는 단위다. 하지만 enum은 사실상 class이다. 편의를 위해서 enum만을 위한 문법적 형식을 가지고 있기 때문에 구분하기 위해서 enum이라는 키워드를 사용하는 것이다. 


위의 코드는 아래 코드와 사실상 같다.

class Fruit{

    public static final Fruit APPLE  = new Fruit();

    public static final Fruit PEACH  = new Fruit();

    public static final Fruit BANANA = new Fruit();

    private Fruit(){}

}

생성자의 접근 제어자가 private이다. 그것이 클래스 Fruit를 인스턴스로 만들 수 없다는 것을 의미한다. 다른 용도로 사용하는 것을 금지하고 있는 것이다. 이에 대해서는 뒤에서 다시 설명하겠다. enum은 많은 곳에서 사용하던 디자인 패턴을 언어가 채택해서 문법적인 요소로 단순화시킨 것이라고 할 수 있다.


아래 코드는 컴파일 에러가 발생한다.

/*

if(Fruit.APPLE == Company.APPLE){

    System.out.println("과일 애플과 회사 애플이 같다.");

}

*/

enum이 서로 다른 상수 그룹에 대한 비교를 컴파일 시점에서 차단할 수 있다는 것을 의미한다. 상수 그룹 별로 클래스를 만든 것의 효과를 enum도 갖는다는 것을 알 수 있다.


enum을 사용하는 이유를 정리하면 아래와 같다.

- 코드가 단순해진다.

- 인스턴스 생성과 상속을 방지한다.

- 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있다.


2. enum과 생성자

enum은 사실 클래스다. 그렇기 때문에 생성자를 가질 수 있다. 아래와 같이 코드를 수정해보자.


enum Fruit{

    APPLE, PEACH, BANANA;

    Fruit(){

        System.out.println("Call Constructor "+this);//생성자에 대한 정보 출력

    }

}

 

enum Company{

    GOOGLE, APPLE, ORACLE;

}

 

public class ConstantDemo {

     

    public static void main(String[] args) {

        Fruit type = Fruit.APPLE;

        switch(type){

            case APPLE:

                System.out.println(57+" kcal");

                break;

            case PEACH:

                System.out.println(34+" kcal");

                break;

            case BANANA:

                System.out.println(93+" kcal");

                break;

        }

    }

}

실행결과)

Call Constructor APPLE

Call Constructor PEACH

Call Constructor BANANA

57 kcal

Call Constructor가 출력된 것은 생성자 Fruit가 호출되었음을 의미한다. 이것이 3번 호출되었다는 것은 필드의 숫자만큼 호출되었다는 뜻이다. 즉 enum은 생성자를 가질 수 있다.


하지만 코드를 아래와 같이 바꾸면 컴파일 에러가 발생한다.

enum Fruit{

    APPLE, PEACH, BANANA;

    public Fruit(){

        System.out.println("Call Constructor "+this);

    }

}

이것은 enum의 생성자가 접근 제어자 private만을 허용하기 때문이다. 덕분에 Fruit를 직접 생성할 수 없다. 그렇다면 이 생성자의 매개변수를 통해서 필드(APPLE..)의 인스턴스 변수 값을 부여 할 수 있다는 말일까? 있다.


enum Fruit{

    APPLE("red"), PEACH("pink"), BANANA("yellow");

    public String color;

    Fruit(String color){

        System.out.println("Call Constructor "+this);

        this.color = color;

    }

}

 

enum Company{

    GOOGLE, APPLE, ORACLE;

}

 

public class ConstantDemo {

     

    public static void main(String[] args) {

 

        Fruit type = Fruit.APPLE;

        switch(type){

            case APPLE:

                System.out.println(57+" kcal, "+Fruit.APPLE.color);

                break;

            case PEACH:

                System.out.println(34+" kcal"+Fruit.PEACH.color);

                break;

            case BANANA:

                System.out.println(93+" kcal"+Fruit.BANANA.color);

                break;

        }

    }

}

실행결과)

Call Constructor APPLE

Call Constructor PEACH

Call Constructor BANANA

57 kcal, red


아래 코드는 Fruit의 상수를 선언하면서 동시에 생성자를 호출하고 있다.

APPLE("red"), PEACH("pink"), BANANA("yellow");


아래 코드는 생성자다. 생성자의 매개변수로 전달된 값은 this.color를 통해서 5행의 인스턴스 변수의 값으로 할당된다.

Fruit(String color){

    System.out.println("Call Constructor "+this);

    this.color = color;

}


아래처럼 호출하면 APPLE에 할당된 Fruit 인스턴스의 color 필드를 반환하게 된다.

System.out.println(57+" kcal, "+Fruit.APPLE.color);


열거형은 메소드를 가질수도 있다. 아래 코드는 이전 예제와 동일한 결과를 출력한다.

enum Fruit{

    APPLE("red"), PEACH("pink"), BANANA("yellow");

    private String color;

    Fruit(String color){

        System.out.println("Call Constructor "+this);

        this.color = color;

    }

    String getColor(){

        return this.color;

    }

}

 

enum Company{

    GOOGLE, APPLE, ORACLE;

}

 

public class ConstantDemo {

     

    public static void main(String[] args) {

        Fruit type = Fruit.APPLE;

        switch(type){

            case APPLE:

                System.out.println(57+" kcal, "+Fruit.APPLE.getColor());

                break;

            case PEACH:

                System.out.println(34+" kcal"+Fruit.PEACH.getColor());

                break;

            case BANANA:

                System.out.println(93+" kcal"+Fruit.BANANA.getColor());

                break;

        }

    }

}


enum은 맴버 전체를 열거 할 수 있는 기능도 제공한다.

enum Fruit{

    APPLE("red"), PEACH("pink"), BANANA("yellow");

    private String color;

    Fruit(String color){

        System.out.println("Call Constructor "+this);

        this.color = color;

    }

    String getColor(){

        return this.color;

    }

}

 

enum Company{

    GOOGLE, APPLE, ORACLE;

}

 

public class ConstantDemo {

     

    public static void main(String[] args) {

        for(Fruit f : Fruit.values()){

            System.out.println(f+", "+f.getColor());

        }

    }

}

열거형의 특성을 정리해보자. 열거형은 연관된 값들을 저장한다. 또 그 값들이 변경되지 않도록 보장한다. 뿐만 아니라 열거형 자체가 클래스이기 때문에 열거형 내부에 생성자, 필드, 메소드를 가질 수 있어서 단순히 상수가 아니라 더 많은 역할을 할 수 있다.


'Programing > Java' 카테고리의 다른 글

[Java] 예외 만들기  (0) 2014.12.25
[Java] 예외 처리 기본  (0) 2014.12.25
[Java] for-each문  (0) 2014.12.23
[Java] 오토박싱  (0) 2014.12.23
[Java] 익명클래스  (0) 2014.12.23

[Java] for-each문


for-each문


배열과 컬렉션에 저장된 요소에 접근하기 할 때 기존보다 편리한 방법으로 처리할 수 있도록 for문의 새로운 문법이 추가되었다.


1. 배열

for(배열의 타입 변수명 : 배열){

// 반복할 문장

}


2. 컬렉션

for(컬렉션에 저장된 요소의 타입 변수명 : 컬렉션){

// 반복할 문장

}


3. 배열 예제

아래의 두 for문은 서로 동일하다.

int [] arr = {10, 20, 30, 40 50};


for(int i = 0; i<arr.length; i++;){

System.out.println(arr[i]);

}


for(int i : arr){// arr[i]가 아닌 i라는 것에 유의

System.out.println(i);

}

코드 해석: 반복문이 한 번 반복될 때마다 변수 e에 컬렉션 arr의 인덱스가 하나씩 저장되며, e에 저장된 원소와 한 칸의 공백을 출력한다. 반복은 배열의 인덱스 0부터 n-1까지 이루어진다. (배열 자체는 참조만 되기 때문에 변경되지 않음)



4. 컬렉션 예제

이제 ArrayList에 저장된 요소들에 접근하기 위해 for문을 사용한다면 다음과 같을 것이다.

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(new Integer(10));

list.add(new Integer(20));

list.add(new Integer(30));

list.add(new Integer(40));

Iterator it = list.iterator();


for(it.Iterator();){

System.out.println(it.next());

}


for(Integer i : list){

System.out.println(i);

}


'Programing > Java' 카테고리의 다른 글

[Java] 예외 처리 기본  (0) 2014.12.25
[Java] enum  (0) 2014.12.25
[Java] 오토박싱  (0) 2014.12.23
[Java] 익명클래스  (0) 2014.12.23
[Java] public static void main(String [] args)  (0) 2014.12.23

[Java] 오토박싱


오토박싱


컬렉션에는 객체로 저장해야하기 때문에 기본형 값을 저장하기 위해서는 Integer나 Long과 같은 Wrapper클래스를 사용해야 했다.

그러나 이제부터는 기본형 값을 직접 컬렉션에 저장할 수 있다. 컴파일러에 의해서 자동적으로 Wrapper클래스로 변환되어 저장되는데 이것을 오토박싱이라고 한다.

뿐만 아니라 저장된 값을 꺼낼 때도 변환과정을 거치지 않고도 기본형 형태의 값을 바로 얻을 수 있는데 이것을 언박싱이라고 한다.


ArrayList list = new ArrayList();

list.add(new Integer(10));

list.add(new Integer(20));

list.add(new Integer(30));

Integer i =(Integer)list.get(0);

int value = i.intValue();


이전에는 위와 같은 코드를 사용했지만 이제는 아래와 같이 코드를 간략히 할 수 있다.

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(10);// 오토박싱

list.add(20);// 오토박싱

list.add(30);// 오토박싱

int value = list.get(0);// 언박싱


'Programing > Java' 카테고리의 다른 글

[Java] enum  (0) 2014.12.25
[Java] for-each문  (0) 2014.12.23
[Java] 익명클래스  (0) 2014.12.23
[Java] public static void main(String [] args)  (0) 2014.12.23
[Java] 인터페이스  (0) 2014.12.23

[Java] 익명클래스


익명 클래스


익명 클래스는 특이하게도 다른 내부 클래스들과 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다. 이름이 없기 때문에 생성자도 가질수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.


class InnerEx6 {

Object iv = new Object(){ void method(){} }; // 익명클래스

static Object cv = new Object(){ void method(){} }; // 익명클래스


void myMethod() {

Object lv = new Object(){ void method(){} }; // 익명클래스

}

}

실행결과)

InnerEX6.class

InnerEX6$1.class(익명 클래스)

InnerEX6$2.class(익명 클래스)

InnerEX6$3.class(익명 클래스)

익명클래스는 이름이 없기 때문에 '외부클래스명$숫자1.class' 형식으로 클래스파일명이 결정된다.


import java.awt.*;

import java.awt.event.*;


class InnerEx7

{

public static void main(String[] args) 

{

Button b = new Button("Start");

b.addActionListener(new EventHandler());

}

}


class EventHandler implements ActionListener

{

public void actionPerformed(ActionEvent e) {

System.out.println("ActionEvent occurred!!!");

}

}


import java.awt.*;

import java.awt.event.*;


class InnerEx8

{

public static void main(String[] args) 

{

Button b = new Button("Start");

b.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

System.out.println("ActionEvent occurred!!!");

}

} // 익명 클래스의 끝

);

} // main메서드의 끝

} // InnerEx8클래스의 끝

앞의 예제에서 익명클래스를 이용해서 변경한 것이 바로 밑의 예제이다. 먼저 두 개의 독립된 클래스를 작성항 다음에, 다시 익명 클래스를 이용하여 변경하면 보다 쉽게 코드를 작성할 수 있다.


'Programing > Java' 카테고리의 다른 글

[Java] for-each문  (0) 2014.12.23
[Java] 오토박싱  (0) 2014.12.23
[Java] public static void main(String [] args)  (0) 2014.12.23
[Java] 인터페이스  (0) 2014.12.23
[Java] 인터페이스와 다형성  (1) 2014.12.21

[Java] public static void main(String [] args)


public static void main(String[] args)


- 메인 메서드는 진입점(Entry Point)을 뜻한다. 그러므로 메인 메서드의 접근자는 항상 public 이어야 한다. --> public


- 메인 메서드는 항상 정적이어야 한다. 클래스는 메모리에 로딩된 다음에 사용이 가능하다. static이 붙은 클래스나 메서드, 변수는 컴파일시 자동으로 로딩된다. 메인 메서드는 클래스 로딩 없이 호출할 수 있어야 한다. 그렇기 때문에 static을 사용한다. --> static


- void는 리턴타입이 없다는 뜻이다. 메인 메서드는 Entry Point이면서 프로그램의 끝이기도 하다. 메인으로 시작해서 메인이 끝나면 그 프로그램도 끝이다. 그러므로 리턴하는 값 자체가 불필요하다. 프로그램이 끝났는데 마지막에 어떤 값을 리턴해봤자 아무 의미가 없기 때문이다. --> void

 

- String[] args 는 프로그램 실행시 매개변수를 보내서 실행할 수 있다는 것을 뜻한다. 1개를 사용할수도 있고 여러개를 사용할 수도 있기 때문에 배열을 사용한다.


cf.) public과 static은 순서가 바뀌어도 상관없다. 하지만 void는 리턴타입이므로 반드시 메서드 명 앞에 와야 한다.

 public static void main(String[] args){}

 static public void main(String args[]){}


'Programing > Java' 카테고리의 다른 글

[Java] 오토박싱  (0) 2014.12.23
[Java] 익명클래스  (0) 2014.12.23
[Java] 인터페이스  (0) 2014.12.23
[Java] 인터페이스와 다형성  (1) 2014.12.21
[Java] 다형성  (0) 2014.12.21

[Java] 인터페이스


인터페이스


1. 인터페이스란?

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.

오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다. 인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.


cp.) 인터페이스란 무엇인가

사전적 의미는 어떤 객체와 객체의 중간에 놓이는 것(객체와 객체의 중간에 놓이는 통신 채널)


1) 인터페이스는 뭐 할 때 쓰는 건가?

둘이서 서로 다른 클래스를 만들 때 서로 ‘이렇게 만들자, 이렇게 만들어 드릴게요’라면서 약속할 때 쓰이는 것이 인터페이스이다. 즉 클래스를 만들기 전에 앞으로 이런 기능을 만들겠다. 이런 기능이 있었으면 좋겠다고 먼저 합의해놓은 것이 인터페이스이다.


2) 인터페이스는 어떻게 쓰는 것인가?

먼저 필요한 약속을 먼저 잡고, 한쪽에서 약속대로 호출하고, 한쪽에서는 약속대로 구현대로 사용한다. 인터페이스를 구현하는 입장에서는 약속한 대로 기능(메소드)을 만들어주고, 사용하는 입장에서는 실제 객체가 어떻든 간에 약속을 이행할 수 있는 객체이기만 한다면 마음대로 약속된 기능을 호출하는 방식이다.


3) 인터페이스는 새로운 기능인가?

프로그램을 설계하고 조금 더 유연한 프로그램을 만드는 기법이다. 인터페이스는 상속과 더불어서 다형성이라는 객체지향 프로그래밍의 특징을 구현하는 방식이다.


2. 인터페이스를 사용하는 상황

(1) 당신과 친구가 서로 약속을 한다면: 객체의 스펙

A가 사용할 객체를 만들어준다면 아마도 B는 A가 프로그래밍이 다 완료될 때까지 기다리고 있어야만 한다. 처음부터 A가 어떤 메소드를 만들 것인지를 먼저 정하는 것이다. 어떤 메소드에는 어떤 파라미터를 받게 할 것이고, 어떤 메소드에는 어떤 리턴값을 이용할 것이다 등 이런 논의를 먼저하게 되면 B는 적어도 A가 만든 클래스와 객체가 어떤 메소드를 가졌는지 알게 되고, 바로 당장은 아니더라도 프로그램을 개발하는데 도움이 될 것이다.

우리가 해야 하는 일은 정확하게 구분하고, 상대방에 해야 할 일을 명시해주는 작업을 해야 한다면 인터페이스를 이용해야 한다고 생각하면 된다. 이러한 의미에서 인터페이스는 일종의 객체의 계약서(스펙)라는 의미가 있다.


(2) 오늘 점심 뭐 먹지?: 스펙을 만족하는 객체

여러 객체 중에서 여러분이 원하는 기능을 가진 객체의 스펙을 만족하게 한다는 객체를 만들어주고자 하는 것이 인터페이스이다. 즉 어떤 객체가 여러분이 선택한 기준의 기능들을 다 구현하고 있다고 생각하면 된다.

따라서 인터페이스를 구현한다는 의미는 어떤 객체가 어떤 기준을 만족하게 한다는 의미로 해석할 수 있다.

(입사 기준에 만족하는 사람은 여러 명이 있을 수 있다. 실행활에서 어떤 기준을 만족하듯이 객체들이 어떤 기준을 만족하게 하는 장치가 바로 인터페이스의 용도 중의 하나이다.)


(3) 꿩 대신 닭: 현재 객체의 대체물

프로그래밍에서는 어떤 기준에 만족한 객체를 이용해서 프로그래밍을 만들다가 새로운 버전이나 다른 객체로 변경하는 일도 자주 일어난다. 이럴 때 기존의 메소드를 전면 수정하게 되면 결국은 모든 코드의 내용을 수정해야 하는 일이 발생한다.

인퍼페이스를 이용한다는 것은 인터페이스를 구현한 객체들을 하나의 부속품처럼 마음대로 변경해서 사용하는 것이 가능하다는 것이다.

자동차의 순정부품이 있긴 하지만, 때로는 다른 부품을 결합하기도 하는 것처럼, 인터페이스는 시스템이 확장되면서 발생하는 잦은 변경의 문제를 해결하는 데 사용한다.

--> 인터페이스를 코드에 적용하게 되면 실제 객체의 클래스를 몰라도 프로그램을 코딩할 수 있다. 따라서 더 유연한 프로그램을 설계할 수 있다.


(4) 호텔 떡볶이와 길거리 떡볶이: 전혀 다른 객체의 같은 기능

실제로 시내의 모 호텔에 갔더니 떡볶이를 파는 상황을 그려보자.

떡볶이는 길거리 음식이라고 생각했는데 버젓이 호텔의 메뉴판에 있는 것이다. 호텔 요리사와 노점에서 장사하는 사람, 이 두사람은 서로 다른 객체이지만 같은 기능을 할 수 있는 존재들이다. 즉 필요하다면 위에서 말한 하나의 부속품처럼 두 객체를 시스템에서 마음대로 사용할 수 있어야만 한다.

인터페이스는 하나의 기능에 대한 약속이기 때문에 중요한 점은 어떤 객체이든 간에 그 약속을 지키기만 한다면 필요한 곳에서 사용할 수 있게 한다는 것을 의미한다.


3. 문법으로 알아보는 인터페이스

(1) 인터페이스는 실제 객체를 의미하지 않는다.

인터페이스는 그 자체가 객체를 의미하지 않는다. 인터페이스라는 것은 결국은 어떤 객체가 할 수 있는 기능 자체를 의미하고, 그 기능을 하나의 스펙으로 모은 것에 불과하다. 따라서 인터페이스가 실제 기능을 가지는 것이 아니다. 즉 실제로 구현된 코드를 가지지 않았다는 것이다. 인터페이스는 실제로 구현된 코드 대신에 오로지 추상 메소드와 상수만을 가지고 있게 된다.


(2) 인터페이스의 상수는 private으로 만들 수 없다.

인터페이스는 실제 객체는 아니지만 서로 간의 약속으로 사용된다. 정해진 약속을 한 쪽에서 일방적으로 수정하게 되면 문제가 발생할 수 있다. 인터페이스는 객체와 객체의 중간에 놓이기 때문에 인터페이스에 선언하는 변수는 자동으로 'public static final'이 된다. 즉 완벽한 상수로 정의된다. 반면에 private는 객체를 만들 때 외부 클래스에서 접근할 수 없게 하려고 사용하기 때문에 외부로 공개되기 위해서 사용하는 인터페이스에는 맞지 않는다. 그래서 실제로 인터페이스에 private으로 시작하는 변수는 선언할 수 없다.


(3) 인터페이스에는 추상 메소드만 존재한다.

인터페이스는 실제 객체로 만들어지지 않는다. 즉 우리가 원하는 어떤 기능들을 모아서 하나의 인터페이스로 선언하는 것이 가장 일반적으로 사용되는 용도이기 때문에 인터페이스는 ‘기능의 묶음’아고 해석하는 것이 편리하다. 상속을 영어로 해석할 때 ‘is a relation'이라고 한다면, 인터페이스는 ’has a relation'으로 해석된다. 즉 어떤 객체가 어떤 인터페이스를 구현했다는 것은 인터페이스에서 정의한 기능들을 그 객체가 모두 구현해두었다는 것을 의미한다. 따라서 실제 객체는 모든 메소드를 구현했겠지만, 인터페이스 자체에는 메소드의 선언만 들어 있게 된다.


(4) 인터페이스는 객체의 타입으로만 사용된다.

인터페이스는 실제 객체로 사용되지는 않지만, 객체의 타입으로는 사용된다. 이 말은 상속에서와 같이 변수 선언 시에 객체의 타입으로 인터페이스를 사용할 수 있다는 것을 의미한다.

--> 인터페이스 a = new 인터페이스 구현 객체( );

이 경우에 컴파일러는 실제로 변수를 사용할 때 변수의 타입만을 보기 때문에 a라는 변수를 이용해서 객체의 메소드를 호출하는 작업을 실행하면 컴파일러는 인터페이스에 호출하려는 메소드가 있는지만을 따지게 된다.

상속에서 타입이 부모 클래스일 때 컴파일러가 부모 클래스에 선언된 메소드만 따지는 것과 같은 방식이라고 생각하자.

인터페이스 역시 객체의 타입으로 선언될 수 있기 때문에 컴파일러는 변수의 타입에 메소드가 존재하는지만 따지게 되고, 실제로 호출되는 것은 실행되고 있는 객체의 메소드가 호출된다.

cf.) implements은 어떤 기준을 구현한다고 보자


4. 인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

interface 인터페이스 이름{ // 인터페이스의 선언

public static final 타입 상수이름 = 값; // 상수 선언부

public abstract 메서드 이름(매개변수 목록); // 추상 메소드 선언부

}


일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항을 가지고 있다.

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

cf.) 인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.


interface PlayingCard{

public static final int SPADE =4;

final int DIAMOND = 3; // public static final int DIAMOND = 3;

static int HEART =2; // public static int HEART =2;

int CLOVER =1; // public static int CLOVER =1;

public abstract String getCardNumber();

String getCardKind(); // public abstract String getCardKind();

}


5. 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

cf.) 인터페이스는 클래스와는 달리 Object클래스와 같은 최고 조상은 없다.

interface Movable{

void move(int x, int y);// 지정된 위치(x ,y)로 이동하는 기능의 메서드

}

inferface Attackable{

void attack(Unit u);// 지정된 대상(u)을 공격하는 기능의 메서드

}

interface Fightable extends Movable, Attackable{   }

클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다. 그래서 Fightable 자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로 부터 상속받은 두 개의 추상메서드 move(int x, int y)와 attack(Unit u)을 멤버로 갖게된다.


6. 인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다. 다만 클래스는 확장한다는 의미의 키워드 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 키워드 implements'를 사용할 뿐이다.

cf.) 만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, 추상클래스로 선언되어야 한다.


class FighterTest {

public static void main(String[] args) {

Fighter f = new Fighter();


if (f instanceof Unit) {

System.out.println("f는 Unit클래스의 자손입니다.");

}


if (f instanceof Fightable) {

System.out.println("f는 Fightable인터페이스를 구현했습니다.");

}


if (f instanceof Movable) {

System.out.println("f는 Movable인터페이스를 구현했습니다.");

}


if (f instanceof Attackable) {

System.out.println("f는 Attackable인터페이스를 구현했습니다.");

}


if (f instanceof Object) {

System.out.println("f는 Object클래스의 자손입니다.");

}

}

}


class Fighter extends Unit implements Fightable {

public void move(int x, int y) { /* 내용 생략 */ }

public void attack(Unit u) { /* 내용 생략 */ }

}


class Unit {

int currentHP; // 유닛의 체력

int x; // 유닛의 위치(x좌표)

int y; // 유닛의 위치(y좌표)

}


interface Fightable extends Movable, Attackable { }

interface Movable { void move(int x, int y); }

interface Attackable { void attack(Unit u); }

실행결과)

f는 Unit클래스의 자손입니다.

f는 Fightable인터페이스를 구현했습니다.

f는 Movable인터페이스를 구현했습니다.

f는 Attackable인터페이스를 구현했습니다.

f는 Object클래스의 자손입니다.

인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수  있다.여기서 주의 깊게 봐두어야 할 것은 movable인터페이스에 정의된 void moveful(int x, int y)를 Fighter클래스에서 구현할 때 접근 제어자를 public으로 했다는 것이다.


7. 인터페이스 관련 주제


(1) 다중 구현은 다중 타입으로 선언할 수 있다.

클래스의 선언에 implements 인터페이스 구문이 나온다는 얘기는 해당 클래스가 인터페이스의 메소드들을 실제로 구현하고 있다는 선언이다.

클래스가 여러 개의 인터페이스를 구현한다는 것은 또 다른 의미로는 하나의 객체를 여러 가지의 타입으로 선언하는 것이 가능하다는 것을 의미한다.

즉 인터페이스도 타입으로 사용된다. ‘MyMp3 mp3 = new TonyMp3( );'에서처럼 어떤 클래스가 인터페이스를 구현하게 되면 변수 선언 시에 인터페이스를 타입으로 사용할 수 있다는 얘기이다. 클래스가 여러 개의 인터페이스를 구현하게 되면 결과적으로 변수의 타입으로도 다양하게 쓰일 수 있다는 것을 의미하게 된다.

ex.) Phone3G phone = new Phone3G( );

      VisualCall v1 = new VisualCall( );

      VoiceCall v2= new VoiceCall( );

--> 변수의 타입으로 보면 실제로 만들어지는 객체는 마찬가지로 Phone3G 이지만 VoiceCall 타입으로도, VisualCall 타입으로도 사용되는 것을 보실 수 있다.


(2) 상속과 인터페이스 차이(다중상속 관련)

상속은 구체적으로 구현된 메소드를 물려주기 때문에 다중 상속을 하게 되면 문제가 생긴다. 하지만, 인터페이스는 스펙(추상 메소드)만을 물려주기 때문에 여러 개의 인터페이스의 같은 메소드를 물려받아도 구현은 실제 구현 클래스 한 곳에서만 한다.

상속과 달리 인터페이스는 하나의 기준이면서 ‘~를 할 수 있는 기능을 가진 객체’의 의미로 해석될 수 있다. 인터페이스 자체가 하나의 클래스가 아니고, 그저 객체를 판단하는 기준이기 때문에 하나의 객체가 여러 가지 기능을 가질 때에는 클래스의 선언 뒤에 여러 개의 인터페이스를 ‘,’를 이용해서 사용할 수 있다.


(3) 인터페이스가 타입으로 쓰일 수 있기에 가능한 일들

인터페이스는 하나의 타입으로 선언되기 때문에 변수의 선언으로 가능한 모든 작업이 고스란히 가능해진다. 우리가 일반적으로 변수를 쓰는 경우는 다음과 같다.

1) 객체를 나중에 다시 사용하기 위해서 변수로 선언하는 경우

cf.) 인터페이스를 변수로 선언하는 경우

인터페이스로 변수를 선언하게 되면 사용하는 입장에서는 보이는 메소드는 인터페이스의 메소드들만 보이게 된다.

인터페이스 변수를 선언하게 되면 뒤에 오는 모든 객체는 간단히 인터페이스만 구현한 객체이면되기 때문에 실제로 new 뒤에 올 수 있는 클래스에 대한 제한이 없게 된다. 따라서 좀 더 시스템이 유연해지는 계기를 마련하게 된다.


2) 메소드의 파라미터나 메소드의 리턴 타입으로 사용하는 경우

메소드의 파라미터나 메소드의 리턴 타입으로 사용되는 경우에는 추상 클래스나 상속을 이용하는 것과 같아진다. 역시 이런 경우에는 상속과는 달리 전혀 무관하지만, 인터페이스에 선언된 기능을 가진 객체이면 되기 때문에 좀 더 확장성 있는 구조가 된다.


3) 배열이나 자료구조를 선언하기 위해서 사용하는 경우

배열이나 자료구조에서 인터페이스 타입으로 선언되는 경우에는 상속의 단점을 보완하는 방식의 설계가 가능해진다. 

--> 인터페이스를 이용하면 이런 작업들이 모두 가능해진다.

- 인터페이스는 전혀 다른 객체를 하나의 타입으로 인식할 수 있게 한다.


cr.)

1) 인터페이스가 실제 객체를 의미하는 것이 아니라 원하는 객체의 스펙을 의미하는 것이다.

2) 어떤 객체가 인터페이스를 구현함에 따라 여러 가지 타입으로 선언될 수 있다는 점이다.

--> 인터페이스 자체가 의미하는 것은 어떤 기능만을 의미하기 때문에 우리가 만든 여러 가지의 클래스에 원하는 인터페이스를 붙여주면 전혀 다른 객체이지만 같은 타입으로 인식할 수 있게 된다.


cp.) 개발하다 예상치 못한 새로운 기능이 추가되는 경우가 있다. 이렇게 전혀 다른 객체들의 공통적인 문제를 해결하는 데 있어서 인터페이스가 도움을 줄 수 있다.

-->  인터페이스는 하나의 타입으로 이전에 전혀 관계가 없는 클래스를 하나의 타입으로 볼 수 있게 한다. 따라서 전혀 엉뚱한 데이터를 가진 객체들을 하나의 자료구조와 같은 타입으로 묶어줄 수 있다.

1) 공통적인 기능만을 인터페이스로 정의해버린다.

가장 먼저 해야 할 일은 공통적인 기능을 인터페이스로 정의하는 것이다. 예를 들어 4가지의 클래스가 결국 매달 지급해야 하는 돈을 계산해주는 기능이 필요하다고 판단된다. 따라서 Ipayable

이라는 인터페이스를 정의하도록 한다.(4가지 클래스가 공통적인 기능을 갖는 인터페이스)

2) 공통 기능을 필요한 클래스가 정의하도록 클래스의 선언을 수정한다.

이제 필요한 클래스가 공통 기능을 할 수 있도록 수정해주어야 한다. 즉 전혀 관계없는 객체들을 공통된 기능을 구현한 객체로 볼 수 있게 한다는 것이다.

3) 인터페이스 타입으로 여러 종류의 객체를 처리할 수 있다.

4가지의 클래스가 동일한 인터페이스를 구현 했다면 전혀 관계가 없는 4가지 클래스의 객체들을 하나의 타입으로 볼 수 있도록 하는 중간 적인 역할이 필요할 때 사용된다.

ex.) IPayable[] arr = new Ipayable( );

    arr[0] = new RegularWorker( );

    arr[1] = new ContractWorker( );

    arr[2] = new PartTimer( );

    arr[3] = new RentalPay( );


(4) 인터페이스는 일종의 접근 제한 역할도 할 수 있다.

예를 들어 실제 MP3가 가진 기능은 playMovie( ), viewImage( ) 기능이지만, 외부에 인터페이스만 호출하게 되면 호출하는 쪽에서는 이 객체를 어떤 타입으로 보는지에 따라서 사용할 수 있는 메소드가 제한되나. 즉, PlayMovie m = new MP3(); 로 보면 실제로 MP3 객체가 가진 기능은 이미지를 보는 기능 viewImage( ) 와 동영상을 보는 기능인 viewImage( )이지만 변수의 선언에 의해서 사용할 수 있는 메소드는 playMovie( ) 만 보이게 된다. 이런 이유 때문에 클래스에 여러 가지 메소드를 만들어 둔 다음 인터페이스로 분리하는 작업을 진행하는 경우가 가끔 있다.

-->  A,B,C라는 인터페이스를 구현한 클래스를 반환할 때 A타입으로 변환하게 되면 외부에서는 A

인터페이스의 메소드만 보이게 된다. 따라서 별도의 접근 제한을 이용하지 않고도 접근 제한과 마찬가지 효과를 보게 하는 방법이다.


- 인터페이스는 다중 상속 문법도 된다.

인터페이스는 다중 구현을 통해서 하나의 객체가 여러 가지의 인터페이스를 구현할 수 있도록 하고 있다.

-->  인터페이스의 다중 상속은 여러 개의 스펙을 모아서 하나의 스펙을 만들어내는 일종의 조합방식이다.

-->  인터페이스의 다중 상속은 ‘스펙+스펙=새로운 스펙’으로 이해하자

만약 각각 한 가지씩의 기능을 가진 인터페이스를 하나의 제품의 스펙으로 규정하길 원할 때 다중상속을 사용한다.


ex.) interface PerfectPhone extends Camera, Mp3, DMB, Widget 

-->  이렇듯 새로운 규격을 만들어 주었다면 이제 PerfectPhone 하나만 물려받으면 끝난다.( 굳이 4가지의 인터페이스를 다 extends할 필요 없다.)


cf.) 인터페이스는 하나의 타입이나 규격일 뿐이지 그 자체가 하나의 객체가 되는 것이 아니다. 따라서 인터페이스의 상속은 클래스의 상속처럼 부모의 속성과 동작을 물려받는 것이 아니다. 인터페이스의 상속은 규격이나 스펙 자체 혹은 기능 자체의 선언을 물려받은 것이다. 규격이나 스펙을 물려받아서 새로운 스펙을 만든다면 기존 여러 개의 스펙을 조합해서 하나로 묶거나 기존의 스펙을 고스란히 물려받은 후에 다시 추가적인 기능을 가지게 하는 것이다.(extends는 상속이 아니다.)


cf.) 인터페이스에서 가장 중요한 사실은 인터페이스를 통해서 객체와 객체 간의 메시지를 주고받는다는 것이다.


(5) 인터페이스는 하나의 클래스에 여러 개를 붙여줄 수 있다.

인터페이스는 객테와 관련된 것이 아니라 객체가 할 수 있는 기능에 대한 정의이기 때문에 상속과는 달리 하나의 객체가 여러 가지의 기능의 스펙을 구현하는 것도 이상한 일이 아니다.

즉 java에서는 단일 상속만을 지원 했던 것과는 달리 하나의 객체는 여러 가지 종류의 기능 스펙인 인터페이스를 구현 할 수 있다.

ex.) 복합기가 프린트도 되고, 팩스도 되고, 복사도 되는 모습(하나의 객체가 여러 개의 기능 스펙을 구현)

복사기 a = new A기계( );

팩스 a = new A기계( );

프린트 a = new A기계( );

-->  하나의 객체를 여러 타입(모습)으로 보는 것이 다형성의 핵심이다.

-->  A 기계(객체)가 여러 가지의 인터페이스를 구현하는 형태이다.

-->  인터페이스 위주로 프로그램을 설계한다는 것은 만들어야 하는 기능에 대한 기준을 미리 잡는다는 것과 유사어 정도라고 생각된다.


'Programing > Java' 카테고리의 다른 글

[Java] 익명클래스  (0) 2014.12.23
[Java] public static void main(String [] args)  (0) 2014.12.23
[Java] 인터페이스와 다형성  (1) 2014.12.21
[Java] 다형성  (0) 2014.12.21
[Java] 쓰레드의 동기화  (0) 2014.12.21

[Spring] XML 스키마 기반의 POJO 클래스를 이용한 AOP 구현

XML 스키마 기반의 POJO 클래스를 이용한 AOP 구현


1. XML 스키마 기반 AOP 


XML 스키마를 이용해서 AOP를 구현하는 과정은 다음과 같다.

1 관련 .jar파일을 클래스패스에 추가한다.

2 공통 기능을 제공하는 Advice 클래스를 구현한다.

3 XML 설정 파일에서 <aop:config>를 이용해서 Aspect를 설정한다. Advice를 이떤 Pointcut에 적용할지를 지정하게 된다.


(1) 공통 기능을 제공한 Advice 클래스를 작성

ProfilingAdvice.java

import org.aspectj.lang.ProceedingJoinPoint; 

public class ProfilingAdvice {

// ProfilingAdvice 클래스는 Around Advice를 구현한 클래스이다.

public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {

// trace() 메서드는 ProceedingJoinPoint 타입의 joinpoint 파라미터를 전달받는데, 이 파라미터를 이용해서 Around Advice를 구현할 수 있게 된다.

String signatureString = joinPoint.getSignature().toShortString();

System.out.println(signatureString + " 시작");

long start = System.currentTimeMillis();

try { // Advice가 적용될 대상 객체를 호출 하기 전의 시간을 구해서 대상 객체를 메서드 호출 실행 시간을 출력

Object result = joinPoint.proceed();

return result;

} finally {

long finish = System.currentTimeMillis();

System.out.println(signatureString + " 종료");

// Advice가 적용될 대상 객체를 호출한 후에 시간을 구해서 대상 객체를 메서드 호출 실행 시간을 출력

System.out.println(signatureString + " 실행 시간 : " + (finish - start) + "ms");

}

}

}

ProfilingAdvice 클래스는 Around Advice를 구현한 클래스이며, trace() 메서드는 Advice가 적용될 대상 객체를 호출하기 전과 후에 시간을 구해서 대상 객체의 메서드 호출 실행 시간을 출력한다.


(2) XML 파일에 Advice를 빈으로 등록하고 Aspect를 설정

acQuickStart.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:aop="http://www.springframework.org/schema/aop" 

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

            http://www.springframework.org/schema/aop

            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

--> XML 스키마를 이용해서 AOP를 구현하려면 aop 네임스페이스를 추가해주어야 한다.


<!-- Advice 클래스를 빈으로 등록 -->

<bean id="performanceTraceAdvice" class="madvirus.spring.chap05.aop.pojo.ProfilingAdvice" />

<!-- Aspect 설정: Advice를 어떤 Pointcut에 적용할 지 설정 -->

<aop:config>

<aop:aspect id="traceAspect" ref="performanceTraceAdvice">

<aop:pointcut id="publicMethodexpression="execution(public * madvirus.spring.chap05..*(..))" />

<!-- excution 명시자는 Advice를 적용할 패키지, 클래스 그리고 메서드를 표현할 때 사용된다. -->

<aop:around pointcut-ref="publicMethod" method="trace" />

</aop:aspect>

</aop:config>

<!-- <aop:config>, <aop:aspect>, <aop:pointcut>, <aop:around> 태그를 이용해서 AOP 설정을 할 수 있다.(aop 네임스페이스를 추가) 

madvirus.spring.chap05 패키지 및 그 하위 패키지에 있는 모든 public 메서드를 Pointcut으로 설정하고, 이들 Pointcut에 Around Advice로 performTraceAdvice 빈 객체의 trace() 메서드를 적용한다. 따라서 writeArticeService 빈의 public 메서드나 articleDao 의 public 메서드가 호출되면 ProfilingAdvice 클래스의 trace() 메서드가 Around Advice로 적용된다.--> 


<bean id="writeArticleService" class="madvirus.spring.chap05.board.service.WriteArticleServiceImpl">

<constructor-arg>

<ref bean="articleDao" />

</constructor-arg>

</bean>


<bean id="articleDao" class="madvirus.spring.chap05.board.dao.MySQLArticleDao" />


<bean id="memberService" class="madvirus.spring.chap05.member.service.MemberServiceImpl" />

</beans>

Advice 클래스를 적용하려면 일단 XML 설정 파일에 Advice 클래스를 빈으로 등록해주어야 한다.


(3) Advice가 Pointcut으로 지정한 메서드에 적용되는 지의 여부를 확인하기 위한 클래스

import madvirus.spring.chap05.board.Article;

import madvirus.spring.chap05.board.service.WriteArticleService;

import madvirus.spring.chap05.member.Member;

import madvirus.spring.chap05.member.service.MemberService;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;


public class MainQuickStart {


public static void main(String[] args) {

String[] configLocations = new String[] { "acQuickStart.xml" };

ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);


WriteArticleService articleService = (WriteArticleService) context.getBean("writeArticleService");

articleService.write(new Article());


MemberService memberService = context.getBean("memberService", MemberService.class);

memberService.regist(new Member());

}

}

MainQuickStart 클래스는 acQuickStart.xml 파일을 이용해서 ApplicationContext를 생성한 뒤 writeArticleService 빈의 write() 메서드를 호출하고, memberService 빈의 regist() 메서드를 호출하고 있다.

앞서 acQuickStart.xml 설정 파일을 설명할 때 madvirus.spring.chap05 패키지 및 그 하위 패키지에 있는 빈 객체의 public 메서드를 호출하면 ProfilingAdvice 클래스의 trace() 메서드가 Around Advice로 사용된다고 했는데 실제로 실행해보면 다음과 같은 결과가 출력된다.


WriteArticleService.write() 시작

WriteArticleServiceImpl.write() 메서드 실행

ArticleDao.insert() 시작

MySQLArticleDao.insert() 실행

ArticleDao.insert() 종료

ArticleDao.insert(..) 실행시간:0ms

WriteArticleService.write(..): 종료

WriteArticleService.write(..): 실행 시간:0ms

MemberService.regist(..): 시작

MemberServiceImpl.regist(): 메서드 실행

MemberService.regist(..): 종료

MemberService.regist(..): 실행 시간:0ms


위 코드에서 굵게 표시한 부분이 ProfilingAdvice 클래스의 trace() 메서드에서 출력한 내용이다. 실행 결과를 보면 실제 빈 객체의 메서드가 호출되기 전/후로 trace() 메서드에서 실행한 내용이 출력되는 것을 확인할 수 있다. 위 실행결과를 통해서 눈 여겨 볼 부분은 WriteArticleServiceImpl,MySQLServiceImpl 등 클래스를 변경하지 않고(즉, 핵심 로직 코드의 변경 없이) 공통 기능을(즉, 메서드 실행 시간을 출력해주는 기능을) 추가했다는 점이다. 이는 설정 파일만 변경하면 기존 코드의 변경 없이 공통 기능을 추가하거나 변경할 수 있는 AOP의 장점을 잘 보여주고 있다.


2. XML 스키마를 이용한 AOP 설정


AOP를 설정하기 위한 aop 네임스페이스 및 aop 네임스페이스와 관련된 XML 스키마가 추가되었다. aop 네임스페이스와 관련된 XML 스키마는 다음과 같이 <beans> 태그에 명시할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:aop="http://www.springframework.org/schema/aop" 

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

            http://www.springframework.org/schema/aop

            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

...

<beans>


aop 네임스페이스와 관련된 XML 스키마를 지정한 뒤에, 다음과 같이 <aop:config> 태그를 사용하여 AOP 관련 정보를 설정할 수 있다.

<bean id="performanceTraceAdvice" class="madvirus.spring.chap05.aop.pojo.ProfilingAdvice" />


<aop:config>

<aop:aspect id="traceAspect1" ref="performanceTraceAdvice">

<aop:pointcut id="publicMethod"

expression="execution(public * madvirus.spring.chap05.board..*(..))" />

<aop:around pointcut-ref="publicMethod" method="trace" />

</aop:aspect>


<bean id="writeArticleService" class="madvirus.spring.chap05.board.service.WriteArticleServiceImpl"/>

위 코드에서 <aop:*>에 해당하는 태그는 다음과 같은 정보를 설정한다.


<aop:config>: AOP 설정 정보임을 나타낸다.

<aop:aspect>: Aspect를 설정한다.

<aop:pointcut>: Pointcut을 설정한다.

<aop:around>: Around Advice를 설정한다. 이외에도 다양한 Advice를 설정할 수 있다.


<aop:aspect> 태그의 ref 속성은 Aspect로서 기능을 제공할 빈을 설정할 때 사용된다. 위 코드의 경우 "performanceTraceAdvice" 빈이 Aspect 기능을 제공한다고 명시하고 있다.

위 코드를 보면 "traceAspect"가 MemberServiceImpl 클래스가 구현한 모든 인터페이스의 public 메서드에 Around Advice로 적용되며, 이때 Aspect의 구현 클래스인 ProfilingAdvice의 trace() 메서드가 

호출된다는 것을 쉽게 유추할 수 있다. XML 스키마 기반의 AOP 설정은 이렇게 설정 파일만 보더라도 어렵지 않게 어떤 코드에 어떤 Aspect가 어떤 타입의 Advice로 적용되는지를 파악할 수 있다.


cp.) Aspect 설정

Aspect 설정에서 <aop:aspect> 태그는 한 개의 Aspect를 설정한다. <aop:aspect> 태그의 ref: 속성에는 공통 기능을 구현하고 있는 빈을 전달한다.

<aop:config>

<aop:aspect id="traceAspect1" ref="performanceTraceAdvice">

<aop:pointcut id="publicMethod" expression="execution(public * madvirus.spring.chap05.board..*(..))" />

<aop:around pointcut-ref="publicMethod" method="trace" />

</aop:aspect>

<aop:aspect id="traceAspect2" ref="performanceTraceAdvice">

<aop:around pointcut="execution(public * madvirus.spring.chap05.member..*(..))" method="trace" />

</aop:aspect>

</aop:config>

Advice를 적용할 Pointcut은 <aop:pointcut> 태그를 이용하여 설정한다. <aop:pointcut> 태그의 id 속성은 Pointcut을 정의하는 AspectJ의 표현식을 입력 받는다.

Advice를 표현하는 태그에는 <aop:around>를 비롯하여 각 타입의 Advice를 정의하기 위해 다양한 태그를 제공하고 있다.


 태 그

 설 명

 <aop:before>

 메서드 실행 전에 적용되는 Advice를 정의한다.

 <aop:returning>

 메서드가 정상적으로 실행된 후에 적용되는 Advice를 정의한다.

 <aop:after-throwing>

 메서드가 예외를 발생시킬 때 적용되는 Advice를 정의한다.

 try-catch 블록에서 catch와 유사하다.

 <aop:after>

 메서드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관  없이 적용되는 Advice를 정의한다. try-catch-finally에서 finally 불록과 유사하다.

 <aop:around>

 메서드 호출 이전, 이후, 예외 발생 등 모든 시점에 적용 가능한 Advice를 정의한다.


각 태그는 pointcut 속성 또는 pointcut-ref 속성을 사용하여 Advice가 적용될 Pointcut을 지정한다. pointcut-ref 속성은 <aop:config> 태그를 이용하여 설정한 Pointcut을 참조할 때 사용되며, pointcut 속성은 직접 AspectJ 표현식을 이용하여 Pointcut을 지정할 때에 사용된다. 

Advice의 각 태그는 Pointcut에 포함되는 대상 객체의 메서드가 호출될 때, <aop:aspect> 태그의 ref 속성으로 지정한 빈 객체에서 어떤 메서드를 실행할 지를 지정한다. 예를 들어, 위 코드의 경우 madvirus.spring.chap05.member 패키지 및 그 하위 패키지의 public 메서드가 호출될 때 performTraceAdvice 빈의 trace 메서드가 호출되도록 설정하고 있다.


3. Advice 타입 별 클래스 작성


(1) Before Advice

Before Advice를 사용하려면 <aop:before> 태그를 이용하여 Before Advice를 설정하면 된다.

<aop:config>

...

<aop:aspect id ="loggingAspect" ref="logging">

<aop:before pointcut-ref="publicMethod" method="before" />

</aop:aspect>

</aop:config>


Aspect로 사용될 빈 클래스는 다음과 같이 <aop:before> 태그의 method 속성에 명시한 메서드를 구현해주면 된다.

public void before(){

//대상 객체의 메서드 실행 이전에 적용할 기능 구현

...

}


대상 객체 및 호출되는 메서드에 대한 정보나 전달되는 파라미터에 대한 정보가 필요한 경우 org.aspectj.lang.JoinPoint 타입의 파라미터를 메서드에 전달한다.

public void before(JoinPoint joinPoint){

// 대상 객체의 메서드 실행 이전에 적용할 기능 구현

...

}


Before Advice를 구현한 메서드는 일반적으로 리턴 타입이 void인데, 그 이유는 리턴 값을 갖더라도 실제 Advice의 적용 과정에 아무런 영향이 없기 때문이다.

Before Advice를 위한 메서드 구현 시 주의해야 할 점은 메서드에서 예외를 발생시킬 경우 대상 객체의 메서드가 호출되지 않게 된다는 점이다.

cp.) Before Advice에서 예외를 발생시키면 대상 객체의 메서드가 호출되지 않기 때문에, 메서드를 실행 하기 전에 접근 권한을 검사해서 접근 권한이 없을 경우 예외를 발생시키는 기능을 구현하는 데 Before Advice가 적합하다.


(2) After Returning Advice
After Returning Advice는 대상 객체의 메서드가 정상적으로 실행된 후에 공통 기능을 적용하고 싶을 때 사용되는 Advice로서, 다음과 같이 <aop:after-returning> 태그를 이용하여 After Returning Advice를 설정한다.
<aop:config>
...
<aop:aspect id ="loggingAspect" ref="logging">
<aop:after-returning pointcut-ref="publicMethod" method="afterReturning" />
</aop:aspect>
</aop:config>

Aspect로 사용될 빈 클래스는 아래 코드처럼 <aop:after-returning> 태그에 명시한 메서드를 구현한다.
public void afterReturning(){
// 대상 객체의 메서드가 정상적으로 실행된 이후에 적용할 기능 구현
...
}

Advice를 구현한 메서드에서 리턴 값을 사용하고 싶다면 returning 속성을 사용하여 리턴 값을 전달 받을 파라미터의 이름을 명시해 주면 된다.
<aop:after-returning pointcut-ref="publicMethod" method="afterReturning" returning="ret"/>

Advice를 구현한 메서드는 returning 속성에 명시한 이름을 갖는 파라미터를 이용해서 리턴 값을 전달 받게 된다.
public void afterReturning(Object ret){
// 대상 객체의 메서드가 정상적으로 실행된 이후에 적용할 기능 구현
...
}

만약 리턴 된 객체가 특정 타입인 경우에 한해서 메서드를 실행하고 싶다면 다음과 같이 한정하고 싶은 타입의 파라미터를 사용하면 된다.
pulbic void afterReturning(Article ret){
// 대상 객체의 메서드가 정상적으로 실행된 이후에 적용할 기능 구현
...
}

대상 객체 및 호출되는 매서드에 대한 정보나 전달되는 파라미터에 대한 정보가 필요한 경우 다음과 같이 org.aspectj.lang.JoinPoint를 파라미터로 추가한다.
pulbic void afterReturning(Article ret, Object ret){
// 대상 객체의 메서드가 정상적으로 실행된 이후에 적용할 기능 구현
...
}

cp.) 스프링 AOP <aop:after-returning> 태그의 ret 속성에 명시한 파라미터 이름과 실제 Advice 구현 메서드의 파라미터 이름이 일치하는 지 확인하기 위해 클래스의 디버그 정보를 사용한다.
만약 디버그 모드로 컴파일하지 않았다면 Advice 구현 메서드의 파라미터 개수가 한 개인 경우 해당 파라미터 ret 속성에 명시한 파라미터라고 가정한다.

(3) After Throwing Advice

After Throwing Advice는 대상 객체의 메서드가 예외를 발생시킨 경우에 적용되는 Advice로서 다음과 같이 <aop:config-throwing> 태그를 이용하여 설정한다.

<aop:config>

...

<aop:aspect id ="loggingAspect" ref="logging">

<aop:after-throwing pointcut-ref="publicMethod" method="afterTrowing" />

</aop:aspect>

</aop:config>


Advice 구현 클래스는 다음과 같이 <aop:after-returning> 태그에 명시한 메서드를 구현한다.

public void afterThrowing(){

// 대상 객체의 메서드가 예외를 발생시킨 경우에 적용할 기능 구현

...

}


대상 객체의 메서드가 발생시킨 예외 객체가 필요한 경우 throwing 속성에 예외 객체를 전달받을 파라미터의 이름을 명시하면 된다.

<aop:after-throwing pointcut-ref="publicMethod" method="afterTrowing" throwing="ex"/>


Advice 구현 메서드에서 발생된 예외를 사용하려면 <aop:after-throwing> 태그의 throwing 속성에 명시한 이름을 갖는 파라미터를 추가하면 된다.

public void afterThrowing(Trowable ex){

// 대상 객체의 메서드가 예외를 발생시킨 경우에 적용할 기능 구현

...

}


만약 특정 타입의 예외에 대해서만 처리하고 싶다면, Throwable이나 Exception이 아니라 처리하고 싶은 예외 타입을 파라미터로 지정하면 된다. 예를 들어, 아래 코드는 발생된 예외가 ArticleNotFoundException인 경우에만 호출된다.

public void afterThrowing(AtricleNotFoundException ex){

// 대상 객체의 메서드가 예외를 발생시킨 경우에 적용할 기능 구현

...

}


대상 객체 및 호출되는 메서드에 대한 정보나 전달되는 파라미터에 대한 정보가 필요한 경우 다음과 같이 org.aspectj.lang.JoinPoint를 파라미터로 추가한다.

public void afterThrowing(JoinPoint joinPoint, Exception ex){

// 대상 객체의 메서드가 예외를 발생시킨 경우에 적용할 기능 구현

...

}


(4) After Advice

After Advice는 대상 객체의 메서드가 정상적으로 실행되었는지 아니면 예외를 발생시켰는지의 여부에 상관없이 메서드 실행이 종료된 이후에 적용되는 Advice로서 try-catch-fanally

블록에서 finally 블록에서 finally 블록과 비슷한 기능을 수행한다. 다음과 같이 <aop:after> 태그를 이용하여 After Advice를 설정한다.


<aop:config>

...

<aop:aspect id ="loggingAspect" ref="logging">

<aop:after pointcut-ref="publicMethod" method="afterFinally" />

</aop:aspect>

</aop:config>


Aspect로 사용될 빈 클래스는 다음과 같이 <aop:after> 태그에 명시한 메서드를 구현해주면 된다. 이 때 메서드는 파라미터를 갖지 않는다.

public void afterFinally(){

...

}


대상 객체 및 호출되는 메서드에 대한 정보나 전달되는 파라미터에 대한 정보가 필요한 경우 다음과 같이  org.aspectj.lang.JoinPoint를 파라미터로 명시하면 된다.

public void afterFinally(JoinPoint joinPoint){

...

}


(5) Around Advice

Around Advice는 앞서 살펴 본 Before, After Returning, After Throwing, After Advice를 모두 구현할 수있는 Advice로서, 다음과 같이 <aop:around> 태그를 이용하여 Around Advice를 설정한다.

<bean id ="cache" class="madvirus.spring.chap05.aop.pojo.ArticleCacheAdvice"/>

<aop:config>

...

<aop:aspect id ="cacheAdpect" ref="cache">

<aop:around method="cache" pointcut="execution(public * *..ReadArticleService.*(..))" />

</aop:aspect>

</aop:config>


Around Advice를 구현한 메서드는 org.aspectj.lang.ProceedingJoinPoint를 반드시 첫 번째 파라미터로 지정해야 한다. 그렇지 않을 경우 스프링은 예외를 발생시킨다.

다음 코드는 Around Advice의 구현 예를 보여주고 있다.

package madvirus.spring.chap05.aop.pojo;

import java.util.HashMap;

import java.util.Map;

import madvirus.spring.chap05.board.Article;

import org.aspectj.lang.ProceedingJoinPoint;


public class ArticleCacheAdvice {

private Map<Integer, Article> cache = new HashMap<Integer, Article>();

// 첫 번째 파라미터로 ProceedingJoinPoint를 전달받고 있다. ProceedingJoinPoint의 procced() 메서드를 호출하면 프록시 대상 객체의 실제 메서드를 호출하게 된다.

// 따라서 ProceedingJoinPoint.proceed() 메서드를 호출하기 전과 후에 필요한 작업을 수행할 수 있다.

public Article cache(ProceedingJoinPoint joinPoint) throws Throwable {

Integer id = (Integer) joinPoint.getArgs()[0];

Article article = cache.get(id);

if (article != null) {

System.out.println("[ACA] 캐시에서 Article[" + id + "] 구함");

return article;

}

Article ret = (Article) joinPoint.proceed();

if (ret != null) {

cache.put(id, ret);

System.out.println("[ACA] 캐시에 Article[" + id + "] 추가함");

}

return ret;

}

}

위 코드는 대상 객체의 메서드 호출 전후에 캐시 기능을 삽입하였다. proceed() 메서드를 호출하기 전에 Map에 ID에 해당하는 Article 객체가 존재하는지 검사한다.