[Java] 제네릭

제네릭


제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다. 

즉, 클래스 내부에서 사용할 데이터 타입을 나중에 인스턴스를 생성할 때 확정하는 것을 제네릭이라 한다.

객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

ArrayList와 같은 컬렉션 클래스는 다양한 종류의 객체를 담을 수 있긴 하지만 보통 한 종류의 객체를 담는 경우가 더 많다. 그런데도 꺼낼 때 마다 타입체크를 하고 형변환을 하는 것은 아무래도 불편할 수 밖에 없다.


위의 그림은 아래의 코드를 간략화한 것이다. 

class Person<T>{

    public T info;// p1 일시 데이터 타입은 String이된다.(인스턴스 생성시 String 제네릭 생성)

// p2 일시 데이터 타입은 StringBuilder이 된다.

}

 

public class GenericDemo {

 

    public static void main(String[] args) {

        Person<String> p1 = new Person<String>();

        Person<StringBuilder> p2 = new Person<StringBuilder>();

    }

}

p1.info와 p2.info의 데이터 타입은 결과적으로 아래와 같다.

- p1.info : String

- p2.info : StringBuilder

그것은 각각의 인스턴스를 생성할 때 사용한 <> 사이에 어떤 데이터 타입을 사용했느냐에 달려있다. 


클래스 선언부를 보자.

public T info;


클래스 Person의 필드 info의 데이터 타입은 T로 되어 있다. 그런데 T라는 데이터 타입은 존재하지 않는다. 이 값은 아래 코드의 T에서 정해진다.

class Person<T>{

위 코드의 T는 아래 코드의 <> 안에 지정된 데이터 타입에 의해서 결정된다. 


Person<String> p1 = new Person<String>();

위의 코드를 나눠보자. 아래 코드는 변수 p1의 데이터 타입을 정의하고 있다.


Person<String> p1

아래 코드는 인스턴스를 생성하고 있다. 


new Person<String>();

즉 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능의 제네릭이다. 


1. 제네릭의 장점

1) 타입 안정성을 제공한다.(타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.)

2) 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다. 

간단히 얘기하면, 다룰 객체의 타입을 미리 명시해줌으로써 형변환을 하지 않아도 되게 하는 것이다.

기존에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴타입으로 Object 타입의 참조변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object타입 대신 원하는 타입을 지정하기만 하면 되는 것이다.

(타입을 지정하지 않으면 Object 타입으로 간주된다.)


2. 컬렉션 클래스 이름 바로 뒤에 저장할 객체의 타입을 적어주면, 컬렉션에 저장할 수 있는 객체는 지정한 타입의 객체 뿐이다.

컬렉션클래스<저장할 객체의 타입> 변수명 = new 컬렉션클래스<저장할 객체의 타입>();

ArrayList<Tv> tvList = new ArrayList<Tv>();

// Tv객체만 저장할 수 있는 ArrayList를 생성

tvList.add(new Tv());

tvList.add(new Radio());// 컴파일 에러


3. 다형성을 사용해야 하는 경우에는 부모타입을 지정함으로써 여러 종류의 객체를 저장할 수 있다.

class Product{ }

class Tv extends Product{ }

class Audio extends Product{ }


//Product 클래스의 자손객체들을 저장할 수 있는 ArrayList를 생성

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

list.add(new Product());

list.add(new Tv());

list.add(new Audio());


Product p = list.get(0);// 형변환이 필요없다.

Tv t = (Tv)list.get(i);// 형변환을 필요로 한다.

ArrayList가 Product타입의 객체를 저장하도록 지정하면, 이들의 자손인 Tv와 Audio타입의 객체도 저장할 수 있다. 다만 꺼내올 때 원래의 타입으로 형변환해야 한다.


4. Product 클래스가 Tv클래스의 조상이라 할지라도 아래와 같이는 할 수는 없다.

ArrayList<Product> list = new ArrayList<Tv>();//허용안함


List<Tv> tvList = new ArrayList<Tv>();// But, 허용된다.


5. 와일드카드

보통 제네릭에서는 단 하나의 타입을 지정하지만, 와일드 카드'?'를 사용하면 된다. 보통 제네릭에서는 단 하나의 타입을 지정하지만. 와일드 카드는 하나 이상의 타입을 지정하는 것을 가능하게 해준다.

아래와 같이 어떤 타입('?')이 있고 그 타입이 Product의 자손이라고 선언하면, Tv객체를 저장하는 'ArrayList<Tv>' 또는 Audio객체를 저장하는 'ArrayList<Audio>'를 매개변수로 넘겨줄 수 있다.

Tv와 Audio 모두 Product의 자손이기 때문이다.

public static void printAll(ArrayList<? extends Product> list){//Product 또는 그 자손들이 담긴 ArrayList를 매개변수로 받는 메서드

for(Unit u : list){

System.out.println(u);

}

}


6. 복수의 제네릭

클래스 내에서 여러개의 제네릭을 필요로 하는 경우가 있을 것이다. 예제를 보자.

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{//복수의 제네릭을 사용할 시에는 ','를 사용한다.

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info; 

        this.id = id;

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);

    }

}

위의 코드는 예외를 발생시키지만 문제는 다음 예제에서 처리하고 형식만 보자. 

즉, 복수의 제네릭을 사용할 때는 <T, S>와 같은 형식을 사용한다. 여기서 T와 S 대신 어떠한 문자를 사용해도 된다. 하지만 묵시적인 약속(convention)이 있기는 하다. 그럼 예제의 오류를 해결하자.


7. 기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서만 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다.(wrapper 클래스로 사용할 수 있다.--> 기본타입을 객체타입으로 만드는 것) 따라서 아래와 같이 코드를 변경한다.

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info;

        this.id = id;

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        EmployeeInfo e = new EmployeeInfo(1);

        Integer i = new Integer(10);

        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

        System.out.println(p1.id.intValue());

    }

}

new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다. 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.


8. 제네릭의 생략

제네릭은 생략 가능하다. 아래 두 개의 코드가 있다. 이 코드들은 정확히 동일하게 동작한다. e와 i의 데이터 타입을 알고 있기 때문이다.

EmployeeInfo e = new EmployeeInfo(1);

Integer i = new Integer(10);

Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

Person p2 = new Person(e, i);// 제네릭 생략함


9. 메소드에 적용

제네릭은 메소드에 적용할 수도 있다. 

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info;

        this.id = id;

    }

    public <U> void printInfo(U info){// U 데이터 타입은 info라는 매개변수의 데이터타입(EmployeeInfo)이 된다.

        System.out.println(info);

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        EmployeeInfo e = new EmployeeInfo(1);

        Integer i = new Integer(10);

        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

        p1.<EmployeeInfo>printInfo(e);//

        p1.printInfo(e);// 제네릭 생략이 가능함

    }

}


10. 제네릭의 제한

(1) extends

제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.

abstract class Info{//부모 클래스가 반드시 추상클래스일 필요가 없다.

    public abstract int getLevel();

}

class EmployeeInfo extends Info{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

    public int getLevel(){

        return this.rank;

    }

}

class Person<T extends Info>{// info 클래스나 info의 자식클래스만이 제네릭으로 올 수 있는 데이터 타입이 된다.(info의 자식이면 OK, 자식이 아니면 컴파일 에러)

    public T info;

    Person(T info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person p1 = new Person(new EmployeeInfo(1));

        Person<String> p2 = new Person<String>("부장");

    }

}

위의 코드에서 중요한 부분은 다음과 같다.


class Person<T extends Info>{

즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.


extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

interface Info{

    int getLevel();

}

class EmployeeInfo implements Info{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

    public int getLevel(){

        return this.rank;

    }

}

class Person<T extends Info>{// extends는 상속이 무엇인가가 아니라, 부모가 누군가를 확인하는 코드이다.(implement가 아니다.)

    public T info;

    Person(T info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person p1 = new Person(new EmployeeInfo(1));

        Person<String> p2 = new Person<String>("부장");

    }

}


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

[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14

[Java] 컬렉션 프레임워크


컬렉션 프레임워크



 인터페이스

 특 징

 List

순서가 있는 데이터의 집합, 데이터의 중복을 허용한다.

--> 데이터를 add하면 앞에서 부터 순차적(순서대로)으로 데이터가 들어간다. 

       그래서 각각의 저장되어 있는 공간들은 고유한 index를 갖는다. 

ex.) 대기자 명단

구현 클래스: ArrayList, LinkedList, Stack, Vector등

 

 Set

순서를 유지하지 않는 데이터의 집합, 데이터의 중복을 허용하지 않는다.

--> 집합이다. 데이터가 순서와는 상관없이 add된다. 중복되지 않는다.

ex.) 양의 정수 집합, 소수의 집합

구현 클래스: HashSet, TreeSet등

 Map

키와 값의 쌍으로 이루어진 데이터의 집합. 순서는 유지되지 않으며, 키는 중복을 허용하지 않고, 값을 중복을 허용한다.

ex.) 우편번호, 지역번호

구현 클래스: HashMap, TreeMap, Hashtable, Properties등


1. 컬렉션 프레임워크란

컬렉션즈 프래임워크라는 것은 다른 말로는 컨테이너라고도 부른다. 즉 값을 담는 그릇이라는 의미이다. 그런데 그 값의 성격에 따라서 컨테이너의 성격이 조금씩 달라진다. 자바에서는 다양한 상황에서 사용할 수 있는 다양한 컨테이너를 제공하는데 이것을 컬렉션즈 프래임워크라고 부른다. ArrayList는 그중의 하나다.


2. 배열과 비교

 배열은 연관된 데이터를 관리하기 위한 수단이었다. 그런데 배열에는 몇가지 불편한 점이 있었는데 그 중의 하나가 한번 정해진 배열의 크기를 변경할 수 없다는 점이다.(배열을 만들고 나서 추후에 배열의 크기를 바꿀수 없다.) 이러한 불편함을 컬렉션즈 프래임워크를 사용하면 줄어든다.


import java.util.ArrayList;

public class ArrayListDemo {

 

    public static void main(String[] args) {

        String[] arrayObj = new String[2];

        // 배열의 크기 지정--> 생성후 변경 불가

        arrayObj[0] = "one";

        arrayObj[1] = "two";

        // arrayObj[2] = "three"; 오류가 발생한다.

        // --> 배열의 크기 변경 불가 (배열은 배열의 크기를 알 때만 사용할 수 있다.)

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

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

        }

         

        ArrayList<String> al = new ArrayList<String>(); // <Strinf>: al 참조변수에 추가되는 값이 String 데이터 타입이라는 것을 지정한다.

        // 배열과 비슷하나, 객체 생성후 몇개의 값을 사용할 지 지정할 필요가 없다.

        // --> 여기서 al 참조변수는 컨테이너라고 한다.(자료를 담는 그릇이 된다.) --> 아래의 코드는 add()를 이용하여 al에 one, two, Three를 넣은 모습이다.

        al.add("one");

        al.add("two");

        al.add("three");

        // 값을 추가해도 상관없다. outofbound 에러 없음

        for(int i=0; i<al.size(); i++){// 배열은 length

        // String value = al.get(i);// value 변수에 리턴되는 데이터타입은 ArrayList이므로 String으로 데이터타입 지정시 에러가 발생한다. 

        // 그래서 (String)al.get(i) 또는 ArrayList<String>을 객체 선언시 지정해야 한다. 

            System.out.println(al.get(i));//i=1이면 one, i=2이면 two, 제네릭을 통해서 (String)al.get(i)을 통해 형변환할 필요가 없다.

        }

    }

}

실행결과)

one

two

one

two

three

- ArrayList는 배열과는 사용방법이 조금 다르다. 배열의 경우 값의 개수를 구할 때 .length를 사용했지만 ArrayList는 메소드 size를 사용한다. 

또한, 특정한 값을 가져올 때 배열은 [인덱스 번호]를 사용했지만 컬렉션은 .get(인덱스 번호)를 사용한다.


3. 중복을 허용하는 List/ 중복 허용하지 않는 Set

자료를 꺼낼때 순서가 유지되는지 아닌지 주의있는 보자

import java.util.ArrayList;

import java.util.HashSet; 

import java.util.Iterator;

 

public class ListSetDemo {

 

    public static void main(String[] args) {

        ArrayList<String> al = new ArrayList<String>();

        al.add("one");

        al.add("two");

        al.add("two");

        al.add("three");

        al.add("three");

        al.add("five");

        System.out.println("array");

        Iterator<String> ai = al.iterator(); //<String>에서 제네릭을 선언하지 않았다면 (String)로 형변환을 해야 한다.

        while(ai.hasNext()){

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

        }

         

        HashSet<String> hs = new HashSet<String>();

        hs.add("one");

        hs.add("two");

        hs.add("two");

        hs.add("three");

        hs.add("three");

        hs.add("five");

        Iterator<String> hi = hs.iterator();

        System.out.println("\nhashset");

        while(hi.hasNext()){

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

        }

    }

}

실행결과)

array

one

two

two

three

three

five

 

hashset

two

five

one

three


4. Map

(1) 예제 1

import java.util.*;


public class MapDemo {

 

    public static void main(String[] args) {

        HashMap<String, Integer> a = new HashMap<String, Integer>();

        a.put("one", 1);// key, value

        a.put("two", 2);

        a.put("three", 3);

        a.put("four", 4);

        System.out.println(a.get("one"));

        System.out.println(a.get("two"));

        System.out.println(a.get("three"));

         

        iteratorUsingForEach(a);

        iteratorUsingIterator(a);

    }

    

    // Map 데이터 iterator 기능 없음 --> Map 데이터 가져오는 방법 1

    static void iteratorUsingForEach(HashMap<String, Integer> map){

        Set<Map.Entry<String, Integer>> entries = map.entrySet();

        for (Map.Entry<String, Integer> entry : entries) {

            System.out.println(entry.getKey() + " : " + entry.getValue());

            //Map 데이터를 가져오는 방법 --> getKey()=key값 ,getValue()=value값

        }

    }

    // Map 데이터 iterator 기능 없음 --> Map 데이터 가져오는 방법 2

    static void iteratorUsingIterator(HashMap<String, Integer> map){

        Set<Map.Entry<String, Integer>> entries = map.entrySet();

        Iterator<Map.Entry<String, Integer>> i = entries.iterator();

        while(i.hasNext()){

            Map.Entry<String, Integer> entry = i.next();

            System.out.println(entry.getKey()+" : "+entry.getValue());

        }

    }

}

Map은 iterator 기능이 없기 때문에 Map의 데이터를 가지고 있는 Set을 만들고 Set에 들어가 있는 데이터 타입은 Map.Entry이다.
(Set에 있는 값은 Map에 있는 값이 대응된다.--> Map에 있는 값을 getKey( ), getValue( )로 알아낸다.) 


(2) 예제2

import java.util.*;


class GenericsEx5

{

public static void main(String[] args) 

{

HashMap<String,Student> map = new HashMap<String,Student>();

map.put("1-1", new Student("자바왕",1,1,100,100,100));

map.put("1-2", new Student("자바짱",1,2,90,80,70));

map.put("2-1", new Student("홍길동",2,1,70,70,70));

map.put("2-2", new Student("전우치",2,2,90,90,90));

Student s1 = map.get("1-1");

System.out.println("1-1 :" + s1.name);

Iterator<String> itKey = map.keySet().iterator();

// key값만 가져오는 경우

while(itKey.hasNext()) {

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

}


Iterator<Student> itValue = map.values().iterator();

int totalSum = 0;

// value값만 가져오는 경우

while(itValue.hasNext()) {

Student s = itValue.next();

System.out.println(s);

totalSum += s.total;

}


System.out.println("전체 총점:"+totalSum);

} // main

}


class Student extends Person implements Comparable<Person> { 

String name = ""; 

int ban = 0; 

int no = 0; 

int koreanScore = 0; 

int mathScore = 0; 

int englishScore = 0; 


int total = 0; 


Student(String name, int ban, int no, int koreanScore, int mathScore, int englishScore) { 

super(ban+"-"+no, name);

this.name = name; 

this.ban = ban; 

this.no = no; 

this.koreanScore = koreanScore; 

this.mathScore = mathScore; 

this.englishScore = englishScore; 


total = koreanScore + mathScore + englishScore; 


public String toString() { 

return name + "\t" 

+ ban + "\t" 

+ no + "\t" 

+ koreanScore + "\t" 

+ mathScore + "\t" 

+ englishScore + "\t" 

+ total + "\t"; 


   // Comparable<Person>이므로 Person타입의 매개변수를 선언.

public int compareTo(Person o) { 

  return id.compareTo(o.id);    // String클래스의 compareTo()를 호출

}


} // end of class Student 


class Person  {    

     String id;

     String name;


     Person(String id, String name) { 

         this.id = id;

         this.name = name; 

     }

}

실행결과)
1-1 :자바왕
2-2
1-1
2-1
1-2
전우치 2 2 90 90 90 270
자바왕 1 1 100 100 100 300
홍길동 2 1 70 70 70 210
자바짱 1 2 90 80 70 240
전체 총점:1020

5. Iterator

컬렉션 프레임워크에서 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화 하였다. 컬렉션에 저장된 각 요소에 접근하는 기능을 가진 Iterator인터페이스를 정의하고, Collection인터페이스에는 Iterator

(Iterator를 구현한 클래스의 인스턴스)를 반환하는 iterator()를 정의하고 있다.

public interface Iterator{

boolean hasNext();

Object next();

void remove();

}


public interface Collection{

...

public Iterator iterator();

...

}

iterator()는 Collection인터페이스에 정의된 메서드이므로 Collection인터페이스의 자손인 List와 Set에도 포함되어 있다.

그래서 List나 Set인터페이스를 구현하는 컬렉션은 iterator()가 각 컬렉션의 특징에 알맞게 작성되어 있다. 


List list = new ArrayList();


Iterator it = list.iterator();

while(it.hasNext()){

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

}

ArrayList에 저장된 요소들은 출력하기 위한 코드이다.

ArrayList대신 List인터페이스를 구현한 다른 컬렉션 클래스에 대해서도 이와 동일한 코드를 사용할 수 있다. 첫 줄에서 ArrayList 대신 List인터페이스를 구현한 다른 컬렉션 클래스의 객체를 생성하도록 변경하기만 하면 된다.


Map map = new HashMap();

...

Iterator it = map.keySet().iterator();

Map 인터페이스를 구현한 컬렉션 클래스는 키와 값을 쌍을 저장하고 있기 때문에 iterator()를 직접 호출할 수 없고, 그 대신 ketSet()이나 entrySet()과 같은 메서드를 통해서 키와 값을 각각 따로 Set의 형태로 얻어 온 후에 다시 iterator()를 호출해야 Iterator를 얻을 수 있다.


(1) 예제1

import java.util.*;


class IteratorEx1{

public static void main(String[] args) {

ArrayList list = new ArrayList();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

list.add("5");


Iterator it = list.iterator();

while(it.hasNext()) {

Object obj = it.next();

System.out.println(obj);

}

} // main

} // class

실행 결과)

1

2

3

4

5

List 클래스들은 저장순서를 유지하기 때문에 Iterator를 이용해서 읽어 온 결과 역시 저장 순서와 동일하지만 Set클래스들은 각 요소간의 순서가 유지 되지 않기 때문에 Iterator를 이용해서 저장된 요소들은 읽어와도 처음에 저장된 순서와 같지 않다.


6. 정렬

import java.util.*;


class ArrayListEx1

{

    public static void main(String[] args) 

    {

        ArrayList<Integer> list1 = new ArrayList<Integer>(10);

        list1.add(new Integer(5));

        list1.add(new Integer(4));

        list1.add(new Integer(2));

        list1.add(new Integer(0));

        list1.add(new Integer(1));

        list1.add(new Integer(3));

 

        ArrayList list2 = new ArrayList(list1.subList(1,4)); 

        print(list1, list2);

 

        Collections.sort(list1);    // list1과 list2를 정렬한다.

        Collections.sort(list2);    // Collections.sort(List l)

        print(list1, list2);

 

        System.out.println("list1.containsAll(list2):"+ list1.containsAll(list2));

 

        list2.add("B");

        list2.add("C");

        list2.add(3, "A");

        print(list1, list2);

 

        list2.set(3, "AA");

        print(list1, list2);

         

        // list1에서 list2와 겹치는 부분만 남기고 나머지는 삭제한다.

        System.out.println("list1.retainAll(list2):" + list1.retainAll(list2)); 

        print(list1, list2);

         

        //  list2에서 list1에 포함된 객체들을 삭제한다.

        for(int i= list2.size()-1; i >= 0; i--) {

        //--> for문의 카운터를 0부터 증가시킨 것이 아니라, list2.size()-1 부터 감소시키면서 거꾸로 반복시켰다.

       //만약 카운터를 증가시켜가면서 삭제하면 한 요소가 삭제될 때마다 빈 공간을 채우기 위해 나머지 요소들이 자리이동을 하기 때문에 올바른 결과를 얻을 수 없다. 그래서 카운터를 감소시켜가면서

       //삭제를 해야 자리이동이 발생해도 영향을 받지 않고 작업이 가능하다.

            if(list1.contains(list2.get(i)))

                list2.remove(i);

        }

        print(list1, list2);

    } // main

 

    static void print(ArrayList list1, ArrayList list2) {

        System.out.println("list1:"+list1);

        System.out.println("list2:"+list2);

        System.out.println();       

    }

} // class

실행 결과)

list1:[5, 4, 2, 0, 1, 3] 

list2:[4, 2, 0]


list1:[0, 1, 2, 3, 4, 5] // Collections.sort(List1)을 이용해서 정렬하였다.(Collections는 클래스이다.)

list2:[0, 2, 4]


list1.containsAll(list2):true // list1이 list2의 모든 요소를 포함하고 있는 경우에만 true를 얻는다.

list1:[0, 1, 2, 3, 4, 5]

list2:[0, 2, 4, A, B, C]// add(Object obj)를 이용해서 새로운 객체를 저장하였다.


list1:[0, 1, 2, 3, 4, 5]

list2:[0, 2, 4, AA, B, C]// set(int index, Object obj)를 이용해서 저장된 객체를 변경하였다.


list1.retainAll(list2):true// retainAll의 작업결과로 list1에 변화가 있었으므로 true를 반환한다.

list1:[0, 2, 4]// list2와의 공통 요소 이외에는 모두 삭제되었다.(변화가 있었다.)

list2:[0, 2, 4, AA, B, C]


list1:[0, 2, 4]

list2:[AA, B, C]



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

[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출  (0) 2014.12.14

[Java] static


static

static이라는 의미는 ‘정적인, 움직이지 않는다.’는 뜻이다. 메모리에서 고정되기 때문에 붙은 이름이지만, 실제 소스에서 static을 사용한다는 의미는 모든 객체가 ‘공유’한다는 의미이다.

cf.) 객체지향이라는 패러다임이란 데이터와 기능(로직, 메소드)을 가진 객체들의 커뮤니케이션으로 어떤 작업을 완료하는 것을 의미한다. 


cf.) 객체마다 데이터를 가져도 불편한 때도 있다

예를 들어 여러분이 어떤 쇼핑몰을 운영한다고 가정해보자. 여러분의 시스템에서 발생하는 매출 현황은 비단 여러분뿐 아니라 여러분의 회사의 모든 직원이 알아야 한다.

이 비유는 객체지향 프로그래밍에서도 마찬가지로 적용할 수 있다.

1 모든 객체가 동일한 데이터를 참고해야 할 필요가 있다.

2 모든 객체는 데이터에 영향을 줄 수 있다.


1. static 중요 법칙

1 static이 붙은 변수들은 객체들이 다 같이 공유하는 데이터를 의미한다.

2 static이 붙은 메소드들은 객체들의 데이터와 관계없는 완벽하게 공통적인 로직을 정의할 때 사용한다.

3 따라서 static 메소드에서는 인스턴스 변수나 객체의 메소드를 사용할 수 없다.


2. static을 사용하는 기준

(1) 객체가 데이터를 보관할 필요가 없다면 static 메소드로 만들어도 된다.

ex.) 

    Random random = new Random( ); ①

    double a = Math.ramdom( ); ②

둘다 마찬가지로 임의의 소수를 발생시킨다.

①은 객체가 데이터를 가지기 때문에 동일한 메소드를 호출해도 다른 결과를 만들어 낼 수 있다는 것을 의미한다.(Random 클래스는 객체를 생성할 때 데이터를 가지게 할 수 있다.)

②Math.random( )은 특정한 데이터를 기준으로 하는 것이 아니라, 완전히 매번 동일한 방법으로 동작하게 된다. 결론적으로 어떤 메소드가 완전히 객체의 데이터와 독립적인 결과를 만들어내야 하는 상황에서는 static 메소드로 만드는 것이 더 나은 선택이다.


(2) 객체들이 공유하는 데이터를 사용할 때에도 static 메소드를 이용한다.

static이라는 키워드가 붙은 존재는 객체와 무관해지기 위해서 사용한다. 그런데 static한 메소드에서 어떤 특정한 객체의 데이터를 활용하게 되면 몇 가지 문제가 생긴다.

만일 static한 공통된 기능의 메소드에서 어떤 하나의 객체의 데이터를 사용하게 된다면 대체 어떤 객체의 데이터를 활용해야 할까?

static이 붙으면 객체와는 무관해진다. 따라서 그 안에서 특정한 하나의 객체의 데이터를 활용하게 되면 문제가 되는데, 가장 근본적인 것은 만들어진 수많은 객체 중에서 어떤 객체의 데이터를 활용해야 할지 알 수 없게 된다는 점이다. 이런 이유 때문에 static이 붙은 메소드 안에서는 인스턴스 변수를 활용할 수 없도록 컴파일러가 확인하게 된다. 


3. static의 의미 

(1) 공유

어떤 변수에 static을 붙이게 되면 이 변수는 모든 객체가 다 같이 공유하는 변수가 된다. (static이 붙은 변수를 사용하는 모든 객체는 공유된 데이터를 사용한다.)

위와 같은 문제에서 어떤 데이터를 모든 객체가 같이 사용하게 하고 싶다면 static이라는 키워드가 바로 방법이다. 

ex.) 은행에 있는 여러개의 번호표 → 각 기계(객체)마다 각각의 번호표를 사용한다면 1번 번호표를 가진 고객들은 서로 자신이 1번이라고 주장하는 웃지 못할 상황이 발생한다.

이 문제의 근본적인 문제는 각 객체가 자신만의 데이터를 갖는다는 것에 있다. 이 문제를 해결하기 위한 가장 손쉬운 해결책 역시 모든 객체가 같은 데이터를 공유할 수 있게 하면 된다.

→ static은 어떤 변수를 모든 객체가 공유하는 값으로 변경해버리기 때문에 아무리 많은 객체가 있더라도 같은 데이터를 사용하게 하는 공유의 개념을 완성할 수 있다.


(2) 객체와 무관한 메소드와 함수라는 개념

static이 붙은 메소드는 모든 객체가 공유하는 메소드이다.(static이 붙은 변수도 모든 객체가 공유)

모든 객체가 자신만의 데이터를 가지고 있으니 발생되는 문제처럼 메소드 역시 객체의 데이터의 의해서 좌우되는 상황을 벗어나기 위해서 Java에서의 static은 메소드에서도 적용할 수 있게 된다.

1) static 메소드는 결과적으로 모든 객체가 완벽하게 똑같이 동작하는 메소드를 위해서 사용

클래스에서 나온 객체의 데이터에 영향받지 않고 완벽히 동일하게 동작하는 메소드를 만들고 싶다면 static을 이용해서 객체의 메소드를 처리하면 된다. 메소드 앞에 static이 붙으면 모든 객체가 클래스에 정의된 방법대로만 사용하게 된다. 이 문제는 좀 더 현실적인 해결책으로 생각해보면 객체가 가진 데이터의 의해서 좌우되고 싶지 않다는 뜻이다.

객체가 어떤 데이터를 가지든 객체의 데이터의 영향 없이 동작하게 만든다면 언제나 완전하게 동일하게 동작하는 것을 보장해줄 수 있다.

2) Integer.parseInt( )는 static 메소드이다.

이 메소드는 언제 어디서나 동일하게 동작한다는 것이다. 이 메소드 자체가 어는 상황에서든 객체의 데이터에 의해서 영향받지 않고 같은 기능만을 제공한다.

→ static 메소드는 마치 C언어의 함수와 유사한다. 완벽하게 언제나 동일하게 동작하는 것이 보장되기 때문에 사용자는 객체의 데이터를 신경 쓰지 않아도 된다. 이런 의미에서 보면 static메소드라는 것은 객체가 가지는 메소드라기 보다는 클래스 자체의 메소드라는 의미로 생각해볼 수 있다.


(3) 단 한번만 동작하게 하는 static

static이 실행되는 시점은 클래스가 메모리상에 올라갈 때이다. 즉 우리가 프로그램을 실행하면 필요한 클래스가 메소리상에 로딩되는 과정을 거치게 된다. 그리고 한번 로딩된 클래스는 특별한 일이 발생하지 않는 이상 메모리상에서 객체를 생성할 수 있도록 메모리에 상주하게 된다. 

static 은 바로 이 시점에 동작한다. 클래스가 메모리상에 올라가자마자 실행되면서 필요한 처리를 하게 된다. 따라서 static 은 객체의 생성과는 관계없이 클래스가 로딩되는 시점에 단 한 번만 실행하고 싶은 처리를 하기 위해서 사용한다.


4. static이 붙은 변수가 공유 되는 원리

cf.) static이 붙은 변수는 객체의 레퍼런스를 이용해서 사용하는 일반적인 객체지향 프로그래밍과는 달리 클래스의 변수이기 때문에 그냥 클래스 ‘이름, 클래스’변수라는 방식으로 사용하게 된다.(new해서 객체생성 불요)

(1) static이 붙은 변수를 클래스 변수라고 부른다.

모든 객체는 자신이 어디에서 생산되었는지 해당 클래스를 알 수 있다. 모든 객체가 자신이 속한 클래스를 알 수 있다는 것을 다른 말로 하면 모든 객체는 자신이 속한 클래스의 정보를 공유한다는 뜻이다.

사실 static의 비밀은 바로 이곳에 있다. static의 가장 중요한 내용은 객체와 묶이는 데이터가 아니라 클래스와 묶이는 데이터라는 것이다.

static을 사용하는 변수는 객체가 아닌 클래스의 변수이기 때문에 별도의 사용할 필요 없이 그냥 사용할 수 있다.

(2) JVM의 구조(java의 메모리 구조)

1) Heap 영역(객체 영역)

객체의 영역이란 객체들이 만들어지고 살다가 죽는 영역이다. 이 영역에서 가장 중요한 존재는 다름 아닌 가비지 컬렉터이다. 

2) Method Area(비객체 영역)

비객체 영역인 Method Area는 클래스가 메모리상에 올라가는 영역이다. 이 영역은 가비지 컬렉터의 영향을 받지 않고 메모리에 상주한다. 간단히 생각해보면 모든 객체는 클래스에서 나오는데 이 클래스가 나도 모르게 메소리상에서 지워지면 난감한 일이 발생할 수 있다. 비객체 영역은 가비지 컬렉터의 영향으로부터 자유롭고, 메모리에 상주하게 되어 있는데 이런 상주의 의미를 ‘static'이라는 뜻으로 사용된다.

(3) static이 붙은 부분은 클래스가 메모리상에 로딩되면서 같이 올라간다.

static 키워드가 붙으면 메소리상에서 다르게 처리된다는 것이다. static이 붙은 변수를 클래스 변수라고 하는 것은 변수가 존재하는 영역이 클래스가 존재하는 영역과 같기 때문이다.

Java에서 모든 객체는 결과적으로는 동일한 클래스에서부터 생성되기 때문에 동일한 클래스에서 나온 모든 객체는 자신이 속한 클래스에 대해서 접근할 수 있다.

static 변수는 이런 방식을 통해서 모든 객체가 동일한 데이터를 사용할 수 있는 공유의 개념을 완성시킨다.


cp.) Tip

- 객체의 영향을 받지 않기 때문에 static은 굳이 객체를 통해서 사용할 이유가 없다.

어떤 변수나 메소드 앞에 static이 나온다는 의미는 결과적으로 객체의 데이터나 메소드의 영향을 받지 않고 완벽하게 동일한 공유의 개념을 완성하는 것이다.

따라서 굳이 객페의 레퍼런스를 이용해야 하는 필요성이 떨어진다. 어차피 객체보다는 클래스와 더 가깝기 때문이다.


→ static 클래스 이름. 함수(메소드)

static은 객체마다 다른 데이터를 가지고 동작하는 것을 막고 완벽하게 동일하게 동작하는 것을 보장하기 위해서 사용하기 때문에 굳이 객체를 사용해야 하는 이유가 없으므로 바로 ‘클래스 이름. 변수’나 ‘클래스 이름. 메소드’를 사용할 수 있다.

굳이 객체를 사용하지 않아도 된다는 의미는 객체를 사용하는 기존의 방식도 가능하기는 하다는 뜻이다.


- static이 붙은 메소드를 보면 무조건 ‘객체와 무관하다.’혹은 ‘객체의 영향 받지 않는 메소드’이다.라고 생각하자.

모든 경우에 완벽하게 동일하게 동작해주어야만 한다. 이런 상황에서는 static을 이용해서 경우에 따라서 다르게 동작할 수 없게 하는 것이 정상적인 static의 사용법이다.

→ 실무에서 static을 이용할 것인지 아닌지를 판단하는 데 있어서 가장 중요한 것은 객체마다 데이터를 활용해야 하는 경우인가. 그렇지 않은가 이다.

Integer.parseInt(),Math.random()을 보면 객체가 가진 데이터와는 완전히 무관하다. 그냥 단순히 로직을 묶어둔 존재에 가깝다.


- static은 속도는 빠르지만, 회수되지 않기 때문에 주의 요한다.

static이라는 키워드가 붙은 변수는 가비지 컬렉션의 대상이 아니라는 데에서 시작한다.

static을 클래스 변수라고 말하듯이 static이 붙은 변수는 클래스와 같은 영역에 생기고, 클래스와 동일하게 메모리에서 항상 상주하게 된다. 이 때문에 static으로 어떤 데이터나 객체를 연결해서 사용하게 되면 메모리 회수가 안된다. 실제로 시스템이 가동되면서 점점 느려진다.


5. 예제

class Exam_02_Sub {

private String name;

private double don = 1000.0;

private static float iyul = 0.05f;

// static 초기화 구문(static 초기화: 객체 발생 이전)

static {

iyul = 0.05f;

 // 보통 생성자를 선언하여 초기화 하는 것과 같은 구문이다.

// iyul이라는 공간을 0.05f로 초기화 및 선언한다.

// 이렇게 static 선언하면 객체 생성을 하지 않고도 iyul을 사용할 수 있다.

}

// static 메서드: static 필드 컨트롤 목적 즉, static 필드 초기화 및 수정하게 할 목적인 것이다.

public static void setIyul(float iyul) {

Exam_02_Sub.iyul = iyul;

// this.iyul=iyul; this를 넣을 수 없다. static에는 0번째 매개변수가 없다. 

// 왜냐하면 static메서드는 모든 객체가 공유하는 메서드이므로 자기자신이라는 것은 있을 수 없다.

// static 메서드는 this.disp();를 할 수 없다. -->this들어가는 것은 static에서 무조건 쓸 수 없다.

// static 메서드 안에서 일반 메서드를 호출하려면 객체 생성를 해야 한다. 바로 다이렉트로 일반 메서드를 사용할 수 없다.

Exam_02_Sub es = new Exam_02_Sub("C", 1000.0, 0.05f);

es.disp();

}

public Exam_02_Sub(String name, double don, float iyul) {

this.name = name;

this.don = don;

Exam_02_Sub.iyul = iyul;

// 같은 클래스 내부에서는 일반 메서드끼리는 호출이 가능하다.

// this.disp();

}

public void disp() {

System.out.println("name : " + this.name);

System.out.println("don : " + this.don);

// 일반 메서드에서 static을 다이렉트로 호출이 가능하다.

System.out.println("iyul : " + Exam_02_Sub.iyul);

}

public class Exam_02 {

// 일반 메서드를 main 메서드 안에서 호출하려면 static 메서드로 만들어야 한다. 그렇지 않으면 객체생성후 메서드 호출 요한다.

// 일반 메서드로 만들면 this 때문에 static 메서드에서 호출할 수 없다.

public static void main(String[] ar) {

Exam_02_Sub es = new Exam_02_Sub("A", 1000.0, 0.07f);

es.disp();

System.out.println();

Exam_02_Sub es1 = new Exam_02_Sub("B", 1000.0, 0.03f);

es.disp();

es1.disp();

System.out.println();

Exam_02_Sub es2 = new Exam_02_Sub("C", 1000.0, 0.05f);

es.disp();

es1.disp();

es2.disp();

}

}


class Exam_02_Sub_01 {

private String name;

private double don = 1000.0;

private static float iyul = 0.05f;

//private float iyul = 0.05f;

public Exam_02_Sub_01(String name, double don, float iyul) {

this.name = name;

this.don = don;

Exam_02_Sub_01.iyul = iyul;

}

public void disp() {

System.out.println("name : " + this.name);

System.out.println("don : " + this.don);

System.out.println("iyul : " + Exam_02_Sub_01.iyul);

}

}

public class Exam_02_01 {

public static void main(String[] ar) {

Exam_02_Sub_01 es = new Exam_02_Sub_01("A", 1000.0, 0.07f);

es.disp();

System.out.println("-------------");

Exam_02_Sub_01 es1 = new Exam_02_Sub_01("B", 1000.0, 0.03f);

es.disp();

es1.disp();

System.out.println("-------------");

Exam_02_Sub_01 es2 = new Exam_02_Sub_01("C", 1000.0, 0.05f);

es.disp();

es1.disp();

es2.disp();

}

}



[Java] 클래스 메서드와 인스턴스 메서드


클래스 메서드와 인스턴스 메서드


클래스메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다.

클래스는 데이터(변수)와 데이터에 관련된 메서드의 집합이라고 할 수 있다. 

같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

인스턴스메서드는 인스턴스변수와 관련된 작업을 하는, 즉, 메서드의 작업을 수행하는데 인스턴스변수를 필요로 하는 메서드이다. 그래서 인스턴스변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스변수를 사용하는 인스턴스메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다. 반면에 메서드 중에서 인스턴스와 관계없는(인스턴스변수나 인스턴스메서드를 사용하지 않는) 메서드를 클래스메서드로 정의한다.

cf.) 멤버변수는 인스턴스변수와 static변수를 모두 통칭하는 말이다. -->정확히 인스턴스변수라고 표현하자.


1) 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 붙인다.

생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다.


2) 클래스변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.

static이 붙은 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.


3) 클래스메서드는 인스턴스변수를 사용할 수 없다.

인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데,  클래스메서드는 인스턴스 생성 없이 호출가능하므로 클래스메서드가 호출되었을 때 인스턴스가 존재할 수도 있고, 존재하지 않을 수도 있다.

그래서 클래스메서드에서 인스턴스변수의 사용을 금지한다. 그러나, 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스변수가 존재한다는 것은 static이 붙은 변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.


4) 메서드 내에서 인스턴스변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면 static을 붙이자. 메서드 호출시간이 짧아지기 때문에 효율이 높아진다.

인스턴스 메서드는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.


cf.) random()과 같은 Math클래스의 모든 메서드는 클래스메서드이다. Math 클래스에는 인스턴스변수가 하나도 없거니와 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리하기 때문이다.


class MyMath2 {

long a, b;

// 인스턴스변수 a, b만을 이용해서 작업하므로 매개변수가 필요없다.

long add() { return a + b; } // a, b는 인스턴스변수

long subtract() { return a - b; }

long multiply() { return a * b; }

double divide() { return a / b; }


// 인스턴스변수와 관계없이 매개변수만으로 작업이 가능하다.

static long add(long a, long b) { return a + b; }     // a, b는 지역변수

static long subtract(long a, long b) { return a - b; }

static long multiply(long a, long b) { return a * b; }

static double divide(double a, double b) { return a / b; }

}


class MyMathTest2 {

public static void main(String args[]) {

// 클래스메서드 호출(객체생성없이 매개변수만으로 작업 수행)

System.out.println(MyMath2.add(200L, 100L));

System.out.println(MyMath2.subtract(200L, 100L));

System.out.println(MyMath2.multiply(200L, 100L));

System.out.println(MyMath2.divide(200.0, 100.0));


MyMath2 mm = new MyMath2();

mm.a = 200L;

mm.b = 100L;

// 인스턴스메서드는 객체생성 후에만 호출이 가능함.

System.out.println(mm.add());

System.out.println(mm.subtract());

System.out.println(mm.multiply());

System.out.println(mm.divide());

}

}

실행결과)

300

100

20000

2.0

30

100

20000

2.0




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

[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출  (0) 2014.12.14
[Java] 변수의 종류  (0) 2014.12.14
[Java] abstract  (0) 2014.12.11

[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출


클래스멤버와 인스턴스멤버간의 참조와 호출


같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.

그 이유는 인스턴스멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 존재할 수도 있고 존재하지 않을 수도 있기 때문이다.


class TestClass{

void instanceMethod(){} //인스턴스 메서드

static void staticMethod(){} // static 메서드

void instanceMethod2(){ //인스턴스 메서드

instanceMethod();//다른 인스턴스메서드를 호출한다.

staticMethod();// static 메서드를 호출한다.

}

static void staticMethod2(){//static 메서드

instanceMethod();// 에러!! 인스턴스메서드를 호출할 수 없다. static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.

staticMethod();// static 메서드는 호출할 수 있다.

}

}// end of class;


같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하지만 static 메서드는 인스턴스 메서드를 호출할 수 없다.


class TestClass2{

int iv;// 인스턴스변수

static int cv;// 클래스 변수


void instanceMethod(){// 인스턴스메서드

System.out.println(iv);// 인스턴스 변수를 사용할 수 있다.

System.out.println(cv);// 클래스변수를 사용할 수 있다.

}


static void staticMethod(){// static 메서드

System.out.println(iv);// 에러! 인스턴스 변수를 사용할 수 없다.

System.out.println(cv);//  클래스 변수를 사용할 수 있다.

}

}// end of class

메서드간의 호출과 마찬가지로 인스턴스메서드는 인스턴스변수를 사용할 수 있지만, static 메서드는 인스턴스변수를 사용할 수 없다.


class MemberCall {

int iv = 10;

static int cv = 20;


int iv2 = cv;

        // static int cv2 = iv;// 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.

static int cv2 = new MemberCall().iv;// 이처럼 객체를 생성해야 사용가능.


static void staticMethod1() {

System.out.println(cv);

    // System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수를 사용불가.

MemberCall c = new MemberCall();

System.out.println(c.iv);  // 객체를 생성한 후에야 인스턴스변수의 참조가능.

}


void instanceMethod1() {

System.out.println(cv);

System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.

}


static void staticMethod2() {

staticMethod1();

    // instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드를 호출할 수 없음.

MemberCall c = new MemberCall();

c.instanceMethod1(); // 인스턴스를 생성한 후에야 호출할 수 있음.

  }

void instanceMethod2() {// 인스턴스메서드에서는 인스턴스메서드와 클래스메서드

staticMethod1();//  모두 인스턴스 생성없이 바로 호출이 가능하다.

instanceMethod1();

}

}

클래스 멤버(클래스변수와 클래스메서드)는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안된다.

클래스멤버간의 참조 또는 호출 역시 아무런 문제가 없다.

그러나, 인스턴스멤버(인스턴스변수와 인스턴스메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.

하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다. 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어 있다는 것을 의미하며, 즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.


cf.) MemerCall c = new MemberCall();

      int result = c. instanceMethod(); 


      int result = new MemberCall().instanceMethod(); 

      --> 위의 두 줄을 다음과 같이 한 줄로 할 수 있다.



1) 인스턴스 메소드는 클래스 맴버에 접근 할 수 있다.


2) 클래스 메소드는 인스턴스 맴버에 접근 할 수 없다. 


인스턴스 변수는 인스턴스가 만들어지면서 생성되는데, 클래스 메소드는 인스턴스가 생성되기 전에 만들어지기 때문에 클래스 메소드가 인스턴스 맴버에 접근하는 것은 존재하지 않는 인스턴스 변수에 접근하는 것과 같다.

class C1{

    static int static_variable = 1;

    int instance_variable = 2;

    static void static_static(){

        System.out.println(static_variable);

    }

    static void static_instance(){

        // 클래스 메소드에서는 인스턴스 변수에 접근 할 수 없다. 

        //System.out.println(instance_variable);

    }

    void instance_static(){

        // 인스턴스 메소드에서는 클래스 변수에 접근 할 수 있다.

        System.out.println(static_variable);

    }

    void instance_instance(){        

        System.out.println(instance_variable);

    }

}

public class ClassMemberDemo {  

    public static void main(String[] args) {

        C1 c = new C1();

        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공

        // 인스턴스 메소드가 정적 변수에 접근 -> 성공

        c.static_static();

        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공

        // 정적 메소드가 인스턴스 변수에 접근 -> 실패

        c.static_instance();

        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공

        // 인스턴스 메소드가 클래스 변수에 접근 -> 성공

        c.instance_static();

        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공 

        // 인스턴스 메소드가 인스턴스 변수에 접근 -> 성공

        c.instance_instance();

        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공

        // 클래스 메소드가 클래스 변수에 접근 -> 성공

        C1.static_static();

        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공

        // 클래스 메소드가 인스턴스 변수에 접근 -> 실패

        C1.static_instance();

        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패

        //C1.instance_static();

        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패

        //C1.instance_instance();

    } 

}

실행결과)

1

1

2

1



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

[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14
[Java] 변수의 종류  (0) 2014.12.14
[Java] abstract  (0) 2014.12.11
[Java] 인터페이스의 이해  (0) 2014.12.06

[Java] 변수의 종류


변수의 종류


변수의 종류 

선언위치 

생성시기 

클래스 변수 

클래스 영역 

클래스가 메모리에 올라갈 때 

인스턴스 변수 

클래스 영역 

인스턴스가 생성되었을 때 

 지역 변수

클래스 영역 이외의 블럭(메서드, 생성자, 초기화 블럭 내부) 

변수 선언문이 수행되었을 때 


1. 인스턴스 변수

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 그렇기 때문에 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야 한다.

인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다. 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.


2. 클래스 변수

클래스 변수를 선언하는 방법은 인스턴스 변수 앞에 static을 붙이기만 하면 된다. 인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 그래서 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 그래서 클래스 변수를 공유 변수라고도 한다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스변수로 선언해야 한다. 인스턴스 변수는 인스턴스를 생성한 후에야 사용가능하지만, 클래스 변수를 인스턴스를 생성하지 않고도 언제라고 바로 사용할 수 있다.

'클래스이름.클래스변수'와 같은 형식으로 사용한다.

클래스가 로딩될 때 생성되어  프로그램이 종료될 때 까지 유지되며, public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수의 성격을 갖는다.


3. 지역변수

메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.

for문 또는 while문의 블러 내에 선언된 지역변수는, 지역변수가 선언된 블럭{} 내에서만 사용 가능하며, 블럭{}을 벗어나면 소멸되어 사용할 수 없게 된다.


4. 예제

클래스 변수와 인스턴스 변수


class Card{

String kind;// 카드의 무늬(인스턴스 변수)

int number;// 카드의 숫자(인스턴스 변수)

static int width;// 카드의 폭(클래스 변수)

static int weight;// 카드의 높이(클래스 변수)

}


각 Card인스턴스의 자신만의 무늬와 숫자를 유지하고 있어야 하므로 이들을 인스턴스 변수로 선언

각 카드의 폭과 높이는 모든 인스턴스가 공통적으로 같은 값을 유지해야하므로 클래스 변수로 선언

카드의 폭을 변경할 필요가 있을 경우, 모든 카드의 width값을 변경하지 않고 한 카드의 width의 값만 변경해도 모든 카드의 width의 값이 변경되는 셈이다.

class CardTest{

public static void main(String argd[]){

System.out.println("Card.width="+Card.width);

System.out.println("Card.height="+Card.height);

// 클래스 변수는 객체 생성 없이 ‘클래스이름.클래스변수’로 직접 사용 가능하다.

Card c1 = new Card();

c1.kind = "Heart";

c1.number = 7;

Card.c2 = new Card();

c2.kind = "Spade";

c2.number = 4;// 인스턴스변수의 값을 변경한다.

System.out.println("c1은“+c1.kind+","+c1.number+"이며, 크기는 ("c1.width+","+c1.height+")");

System.out.println("c2은“+c2.kind+","+c2.number+"이며, 크기는  ("c2.width+","+c2.height+")");

System.out.println("c1의 width와 height를 각각 50,80으로 변경합니다.“);


c1.width = 50;

c1.height = 80;// 클래스 변수의 값을 변경한다.

System.out.println("c1은“+c1.kind+","+c1.number+"이며, 크기는 ("c1.width+","+c1.height+")");

System.out.println("c2은“+c2.kind+","+c2.number+"이며, 크기는  ("c2.width+","+c2.height+")");

}

}


class Card{

String kind;

int number;

static int width =100;

static int height =250;

}

실행결과)

Card.width=100

Card.height=250

c1은Heart,7이며, 크기는 (100,250)

c2은Spade,4이며, 크기는  (100,250)

c1의 width와 height를 각각 50,80으로 변경합니다.

c1은Heart,7이며, 크기는 (50,80)

c2은Spade,4이며, 크기는  (50,80)


Card 클래스의 클래스변수인 width, height를 공유하기 때문에, c1의 width와 height를 변경하면 c2의 width와 height값도 바뀐 것과 같은 결과를 얻는다.

Card.width, c1.width, c2.width는 모두 같은 저장 공간을 참조하므로 항상 같은 값을 갖게 된다.

--> 인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.



[Spring] web.xml 기본 설정


web.xml 기본 설정


1. 특정 xml 환경 파일 바라보기

DispatherServlet은 클라이언트의 요청을 전달받는 서블릿으로서, 컨트롤러나 뷰와 같은 스프링 MVC의 구성 요소를 이용하여 클라이언트에게 서비스를 제공하게 된다.

DispatherServlet의 설정은 웹 어플리케이션의 /WEB-INF/web.xml 파일에 추가하며, 다음과 같이 서블릿과 매핑 정보를 추가하면 DispatherServlet 설정이 완료된다.


web.xml 파일은 J2EE 웹 어플리케이션의 기본이 되는 설정 파일이다. DispatcherServlet 클래스를 서블릿으로 정의하며 context root 아래에 확장자 .html 파일로 요청을 하면 모두

DispatcherServlet 클래스로 랩핑하도록 정의하고 있다. 이 정의에 의해 .html의 확장자가 붙은 파일로의 액세스는 모두 DispatcherServlet로 송신된다.

DispatcherServlet 클래스에는 <servlet-name> 태그에 의해 ‘shopping-1’ 이라고 서블릿 이름을 지정하여 정의하고 있다.

이 서블릿 이름 ‘shopping-1’ 에 ‘-servlet.XML’ 라는 문자열을 부가함으로써 컨테이너 상에 로드된 스프링 설정 파일명이 결정된다. 예제의 경우 shopping-1-servlet.xml ([서블릿이름]-servlet.xml) 파일이 로드되게 된다.

<servlet>

<servlet-name>shopping-1</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>shopping-1</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>


2. ContextLoaderListener 로드 원리

DispatcherServlet 클래스를 정의하고 context root 이하로의 액세스에 대해서 DispatcherServlet을 맵핑하고 있다. 

다만, 앞 장의 예제와 달리 비즈니스 로직용의 스프링 설정 파일 (applicationContext.xml)을 작성했기 때문에 listener로 ContextLoaderListener 클래스를 정의하고 있다.

ContextLoaderListener 클래스는 스프링 설정 파일(디폴트에서 파일명 applicationContext.xml)을 로드하면 ServletContextListener 인터페이스를 구현하고 있기 때문에 ServletContext 인스턴스 생성 시(톰켓으로 어플리케이션이 로드된 때)에 호출된다. 즉, ContextLoaderListener 클래스는 DispatcherServlet 클래스의 로드보다 먼저 동작하여 비즈니스 로직층을 정의한 스프링 설정 파일을 로드한다.

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<servlet>

<servlet-name>shopping-1</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>shopping-1</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>


※ 설정 파일의 로드 순서

여러 개의 설정 파일을 이용할 경우 설정 파일의 참조 관계가 걸리기 때문에 따일의 로드 순서가 중요하다. 

위의 예제에서 사용하는 스프링 MVC의 설정 파일(shopping-1-servlet.xml)에서도 applicationContext.xml 파일로 정의된 인스턴스를 참조하고 있다.

그 때문에 shopping-1-servlet.xml 파일이 로드되기 전에 applicationContext.xml 파일이 로드되어 있어야한다.


cp.) ContextLoaderListener를 이용한 설정

일반적으로 빈 설정 파일은 하나의 파일만 사용되기 보다는 persistance, service, web등 layer 단위로 나뉘게 된다.

또한, 같은 persistance, service layer의 빈을 2개 이상의 DispatcherServlet이 공통으로 사용할 경우도 있다.

이럴때는 공통빈(persistance, service)설정 정보는 ApplicationContext에, web layer의 빈들은 WebApplicationContext에 저장하는 아래와 같은 방법을 추천한다.

공통빈 설정 파일은 서블릿 리스너로 등록된 org.springframework.web.context.ContextLoaderListener로 로딩해서 ApplicationContext을 만들고, web layer의 빈설정 파일은 DispatcherServlet이 로딩해서 WebApplicationContext을 만든다.

<!-- ApplicationContext 빈 설정 파일-->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

              <!--빈 설정 파일들간에 구분은 줄바꿈(\n),컴마(,),세미콜론(;)등으로 한다.-->

                            /WEB-INF/config/easycompany-service.xml,/WEB-INF/config/easycompany-dao.xml 

            </param-value>

</context-param>

<!-- 웹 어플리케이션이 시작되는 시점에 ApplicationContext을 로딩하며, 로딩된 빈정보는 모든 WebApplicationContext들이 참조할 수 있다.-->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<servlet>

<servlet-name>employee</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/config/easycompany-service.xml</param-value>

</init-param>

</servlet>

        

<servlet>

<servlet-name>webservice</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/config/easycompany-webservice.xml</param-value>

</init-param>

</servlet>

이 ApplicationContext의 빈 정보는 모든 WebApplicationContext들이 참조할 수 있다. 

예를 들어, DispatcherServlet은 2개 사용하지만 같은 Service, DAO를 사용하는 web.xml을 아래와 같이 작성했다면, 

easycompany-servlet.xml에 정의된 빈정보는 easycompany-webservice.xml가 참조할 수 없지만, 

easycompany-service.xml, easycompany-dao.xml에 설정된 빈 정보는 easycompany-servlet.xml, easycompany-webservice.xml 둘 다 참조한다. ApplicationContext과 WebApplicationContext과의 관계를 그림으로 나타내면 아래와 같다.



3. 설정 파일 순서 정하기

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

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

version="2.5">


<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<servlet>

<servlet-name>shopping3-2</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>shopping3-2</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

</web-app>

web.xml은 DispatherServlet 클래스를 정의하고, 컨텍스트 루트 이하로 들어오는 접근에 대해 DispatherServlet을 맵핑한다. 그리고 리스너로 ContextLoaderLister 클래스를 정의한다.

설정 파일을 복수로 사용할 때에는, 설정 파일을 읽는 순서가 중요하다. 설정 파일 A에서 참조하는 bean을 다른 설정 파일 B에 정의하고 있는 경우, A보다 먼저 B를 읽지 않으면 오류가 일어난다.

샘플에서 사용하는 설정 파일은 shopping3-2-server.xml과 applicationContext.xml 2가지인데, shopping3-2-server.xml에서 applicationContext.xml에 정의된 bean을 이용한다.

이런 이유로 applicationContext.xml 다음에 shopping3-1-server.xml 순서로 설정 파일을 읽는다.


web.xml에서 사용하는 org.springframework.web.context.ContextLoaderListener 클래스는 javax.servlet.ServletContextListener 인터페이스를 구현하는 클래스이다.

ContextLoaderListener 클래스의 기본은 applicationContext.xml이지만 변경도 가능하다. 샘플에서는 비즈니스 로직 계층용으로 설정 파일 'applicationContext.xml'을 만들고, ContextLoaderListner 클래스를 이용해서 읽는다. ContextLoaderListner 클래스는 ServletContext 인스턴스 생성 시(Tomcat 이 애플리케이션을 읽을 때) 호출되는 것으로, DispatherServlet 클래스가 읽기 전 ContextLoaderListener 클래스가 비즈니스 로직 계층용 설정 파일 'applicationContext.xml'을 읽는다.


4. 한 개 이상의 설정파일 설정하기

경우에 따라 한 개 이상의 설정 파일을 사용해야 하는 경우가 있다. 또는 기본 설정 파일 이름이 아닌 다른 이름의 설정 파일을 사용하고 싶은 경우도 있을 것이다.

(빈 설정 파일을 하나 이상을 사용하거나, 파일 이름과 경로를 직접 지정해주고 싶다면 contextConfigLocation 라는 초기화 파라미터 값에 빈 설정 파일 경로를 설정해준다.)

이런 경우, 다음과 같이 DispatherServlet을 설정할 때 contextConfigLocation 초기화 파라미터에 설정 파일 목록을 지정하면 된다.

 <servlet>

        <servlet-name>dispather</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfiguration</param-value>

<param-value>

/WEB-INF/main.xml

/WEB-INF/dds.xml

</param-value>

</init-param>

</servlet>

contextConfigLocation 초기화 파라미터는 설정 파일 목록을 값으로 갖는데, 이때 각 설정 파일은 콤마(","), 공백 문자(""), 탭(\t), 줄 바꿈(\n), 세미클론(":")을 이용하여 구분한다.

각 설정 파일의 경로는 웹 어플리케이션 루트 디렉터리를 기준으로 한다.


cp.) 또다른 방법

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/security.xml 

/WEB-INF/applicationContext.xml

</param-value>

</context-param>


<servlet>

<servlet-name>shopping3-7</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>shopping3-7</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

설정 파일이 applicationContext.xml 뿐만 아니라 security.xml도 있다. 복수의 설정 파일을 스프링에 읽게 할 때는 'contextConfigLocation'이라는 파라미터 이름으로, 값에 설정 파일 경로를 지정한다.

복수의 파일을 스페이스로 구획한다.(콤마로 구획 가능)


5. 인코딩 필터 배치하기

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

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

version="2.5">

<filter>

<filter-name>CharacterEncodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<param-name>forceEncoding</param-name>

<param-value>true</param-value>

</init-param>

</filter>


<filter-mapping>

<filter-name>CharacterEncodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<servlet>

<servlet-name>shopping3-3</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>shopping3-3</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

</web-app>


6. 전체적인 나열 보기

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

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

version="2.5">

<!-- 필터 설정 -->

<filter>

<filter-name>CharacterEncodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<param-name>forceEncoding</param-name>

<param-value>true</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>CharacterEncodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<!-- 리스너 설정 -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/security.xml /WEB-INF/applicationContext.xml</param-value>

</context-param>


<!-- DispathcherSerlvet 설정 -->

<servlet>

<servlet-name>shopping3-7</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>shopping3-7</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>


<!-- <filter-mapping> 요소에서의 'springSecurityFilterChain'은 'org.springframework.web.filter.DelegatingFilterProxy'에 붙인 필터 이름이다. 

DelegatingFilterProxy 클래스는 스프링이 제공하는 필터이고, 실제 처리를 필터 이름에 일치하는 이름의 Bean에 위임한다. 

스프링 시큐리티는 'springSecurityFilterChain'이라는 이름으로 스프링에 Bean을 등록하기 때문에 이렇게 필터를 정의하면 스프링 시큐리티 기능이 유효해 진다. 

또한 애플리케이션의 모든 URL에 대해 이 필터 체인을 유효하게 하고, 스프링 시큐리티를 작동시킨다.-->

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


<!-- 이중 로그인을 막기 위한 리스너 클래스이다.-->

<listener>

<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>

</listener>


<!-- error 페이지 설정 -->

<error-page>

<error-code>403</error-code>

<location>/WEB-INF/jsp/noAuthority.jsp</location>

</error-page>

</web-app>



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

[Spring] iBatis 2  (0) 2014.12.16
[Spring] iBatis 1  (0) 2014.12.16
[spring] 스프링 MVC 인터페이스 구현 클래스  (0) 2014.12.13
[Spring] 스프링 MVC 패턴 개요  (0) 2014.12.13
[Spring] AOP 용어 설명  (0) 2014.12.13

[spring] 스프링 MVC 인터페이스 구현 클래스


스프링 MVC 인터페이스 구현 클래스


스프링 MVC를 이용하면, 웹 어플리케이션에서 모델과 뷰, 컨트롤러 사이에 있는 의존관계를 의존 관계 주입 컨테이너인 스프링에서 관리하게 된다.

스프링 MVC는 org.springframework.web 패키지와 org.springframework.servlet.패키지에 포함된 클래스를 사용한다.


1. HandlerMapping 인터페이스 구현 클래스

(1) org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping

웹 요청 URL과 스프링 설정 파일에 정의한 컨트롤러의 name 속성을 맵핑시켜 컨트롤러를 찾는다. 스프링 설정 파일에  HandlerMapping을 전혀 정의하지 않을 때의 기본 클래스

BeanNameUrlHandlerMapping 클래스는 스프링 설정 파일에 컨트롤러를 정의하면서 지정한 name 속성의 값과 웹 요청 URL을 맵핑하는 HandlerMapping 구현 클래스이다.

<bean id="indexController" name="/index.html" class="controller.IndexController"

p:shopService-ref="shopService">

</bean>

BeanNameUrlHandlerMapping 클래스는 기본인 HandlerMapping 구현 클래스이기 때문에 스프링 설정 파일에 정의하지 않아도 된다.

Controller 클래스의 name 속성에 지정한 값을 그대로 경로로 사용한다. 다음 웹 요청 URL을 사용할 때만 IndexController 클래스가 호출된다.

http://localhos:8080/shopping3-1/index.html


(2) org.springframework.web.servlet.handler.SimpleUrlHandlerMapping

웹 요청 URL과 컨트롤러의 맵핑을 일괄 정의한다. 스프링 설정 파일에 컨트롤러의 정의가 분산되는 것을 방지한다.

스프링 MVC에서는, 어떤 URL에 요청이 들어오면 어느 컨트롤러가 처리할지를 컨트롤러와 URL의 맵핑으로 연관 짓는데, SimpleUrlHandlerMapping 클래스를 사용하면 그 맵핑을 설정 파일 한 곳에 모아 작성할 수 있다.

따라서, 스프링 MVC는 컨트롤러 클래스와 URL의 맵핑을 한곳에서 관리하기 위한 SimpleUrlHandlerMapping 클래스를 제공한다.

<!-- HandlerMapping -->

<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<value>

/index.html=indexController

/detail.html=detailController

</value>

</property>

</bean>


<!-- Controller -->

<bean id="indexController" class="controller.IndexController" p:shopService-ref="shopService"></bean>


<bean id="detailController" class="controller.DetailController"

p:shopService-ref="shopService"> 

</bean>

SimpleUrlHanlerMapping 클래스에는 Properties 타입 maapings 프로퍼티가 있다. 이 mapping 프로퍼티에 웹 요청 URL과 컨트롤러를 지정한다. 


(3) org.springframework.web.servlet.handler.ClassNameHandlerMapping

컨트롤러에 어노테이션을 부여해서 웹 요청 URL과의 관련을 정의한다.


(4) org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping

ControllerClassNameHandlerMapping 클래스는 컨트롤러의 클래스 이름에서 URL을 추출한다. 클래스 이름에서 'Controller'를 뺀 나머지를 모두 소문자로 바꾼 문자열이 URL이 된다.

ex.) IndexController : /index*


2. ViewResolver 인터페이스 구현

(1) org.springframework.web.servlet.view.InternalResolverViewResolver

WEB-INF 폴더 안에 있는 뷰 자원을 해결한다. 스프링 설정 파일에 ViewResolver를 전혀 정의하지 않을 때의 기본 클래스이다.

InternalResolverViewResolver 클래스는 컨트롤러가 반환한 뷰 정보를 컨텍스트 상의 경로로 변화시켜 처리하는 클래스이다.

스프링 MVC는 기본 ViewResolver인 InternalResolverViewResolver를 암묵적으로 사용한다.

하지만 뷰 정보에 prefix 프로퍼티나 suffix 프로퍼티를 추가하거나, 뷰의 구현 클래스를 지정할 수 있다.(이를 이용하면 프로그램에 직접 경로를 기술하지 않고, 이동할 뷰 파일을 배치하는 규칙에 유연하게 대응할 수 있다.)

<bean id="internalResourceViewResolver"

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="viewClass">

<value>org.springframework.web.servlet.view.JstlView</value>

</property>

<property name="prefix">

<value>/WEB-INF/jsp/</value>

</property>

<property name="suffix">

<value>.jsp</value>

</property>

</bean>


(2) org.springframework.web.servlet.view.ResourceBundleViewResolver

프로퍼티 파일에서 뷰 이름과 그 실체인 뷰를 관련 짓는다. 프로퍼티의 파일은 클래스패스 상에 배치한다.

ResourceBundleViewResolver 클래스는 View 인터페이스를 구현하는 클래스와, 이동할 뷰의 지정과 뷰 이름의 맵핑을 프로퍼티 파일로 관리한다.

InternalResolverViewResolver 클래스는 prefix, suffix를 지정해서 이동할 뷰 이름을 간략하게 할 수 있지만, 실제 뷰 이름을 변경하면 관련된 컨트롤러나 설정 파일도 수정해야 한다.

하지만, ResourceBundleViewResolver 클래스에서는 컨트롤러에서 지정한 것은 프로퍼티 파일에 정의된 뷰 이름이고, 뷰 이름은 설제 뷰 파일 이름이 아닌 임의의 이름을 붙일 수 있다.

때문에 뷰 파일 이름을 변경하더라도, 뷰 이름을 변경할 필요가 없고, 프로퍼티 파일 맵핑 내용을 수정할 뿐이다. 컨트롤러를 수정할 필요도 없다.

ResourceBundleViewResolver 클래스에서 사용하는 프로퍼티 파일 이름은 설정 파일의 basename 프로퍼티에 지정한다. 

<!-- ViewResolver -->

<bean id="resourceBundleViewResolver"

class="org.springframework.web.servlet.view.ResourceBundleViewResolver">

<property name="basename">

<value>views</value>

</property>

</bean>

basename 프로퍼티에 'views'라고 지정하고 있으므로, 스프링 MVC는 확장자 '.properties'를 추가해서 'views.properties'를 참조한다.

#userEntry View

userEntry.(class)=org.springframework.web.servlet.view.JstlView

userEntry.url=WEB-INF/jsp/userEntry.jsp

프로퍼티 파일에는, '뷰 이름.(class)'를 키로 해서 값에 View 인터페이스를 구현하는 클래스를, '뷰이름.url'을 키로 해서 값에 이동할 뷰 파일을 지정한다. 뷰 이름은 임의로 정할 수 있다.


(3) org.springframework.web.servlet.view.velocity.VelocityViewResolver

벨로시티 템플릿으로 만든 뷰를 해결한다.


3. View 인터페이스 구현

(1) org.springframework.web.servlet.view.internalResourceView

JSP 등 자원용 뷰, 요청 속성에 모델을 지정하고, RequestDispather클래스를 참조해서 지정된 뷰에 전달한다.


(2) org.springframework.web.servlet.view.JstlView

JSTL를 사용한 페이지용 뷰, InternalResourceView 클래스의 서브 클래스, 스프링의 메시지 자원 파일을 JSTL 포맷 태그에서 참조할 수 있게 된다.

JstlView 클래스는 JSTL을 사용해서 JSP를 만드는 것을 지원하는 클래스이며, View 인터페이스를 구현하고 있다. JstlView 클래스를 사용하면, JSTL 포맷 태그에서 스프링의 메시지 자원에 접근할 수 있다.

JstlView 클래스는 앞에서 사용한 InternalReaourceView 클래스의 서브 클래스이다. 


(3) org.springframework.web.servlet.view.velocity.VelocityView

벨로시티 템플릿용 뷰


org.springframework.web.servlet.view.json.MappingJacksonJsonView

JSON 형식으로 출력하기 위한 뷰, JSON으로의 변환 라이브러리로서 Jackson 라이브러리가 필요




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

[Spring] iBatis 1  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13
[Spring] 스프링 MVC 패턴 개요  (0) 2014.12.13
[Spring] AOP 용어 설명  (0) 2014.12.13
[Spring] 의존관계 주입  (0) 2014.12.13

[Spring] 스프링 MVC 패턴 개요

스프링 MVC 패턴 개요


1. 스프링 MVC 패턴

‘MVC(Model View Controller)’란 비즈니스 규칙은 표현하는 도메인 모델(Model)과 프레젠테이션을 표현하는 View를 분리하기 위하여 양측 사이에 컨트롤러를 배치하도록 설계한 디자인 패턴이다.

‘스프링 MVC ’ 란 스프링이 제공하는 웹 어플리케이션 구축 전용의 MVC 프레임워크이다. 스프링 MVC를 이용함으로써 웹 어플리케이션의 Model, View, Controller 사이의 의존 관계를 DI 컨테이너에서 관리하고 통일된 유연한 웹 어플리케이션을 구축할 수 있다.


2. 스프링 MVC 처리 흐름


스프링 MVC는 스트릿츠 등 웹 어플리케이션 프레임워크와 동일하게 Front Controller 패턴을 채용하고 있다. 

FrontController 패턴이란 핸들러 오브젝트를 매개로 하여 요청을 분배함으로써 요청을 통합하고, 요청에 대한 통일된 처리를 기술할수 있도록하기 위한 패턴이다.

다음 그림에서 보듯이 브라우저로부터 송신된 요청은 모두 스프링 MVC에 의해 제공되는DispatherServlet 클래스에 의해 관리되고 있다.


웹 브라우저로부터의 요청(Request)은 DispatcherServlet 인스턴스로 송신된다. 요청을 받은 DispatcherServlet 인스턴스는 어플리케이션 안에서 공통 처리를 실행한 다음, requestURL 고유의 처리를 실행하는 Request 컨트롤러(Controller 인스턴스)를 호출한다. 일반적으로 Request 컨트롤러는 처리 단위 별로 개발자가 작성하기 때문에 DispatcherServlet 인스턴스는 지정된 RequestURL이 어느 Request 컨트롤러에 맵핑되어 있는기를 알아야 한다. 그러나 DispatcherServlet은 어플리케이션에서 하나의 인스턴스일 뿐 랩핑 정보를 관리하는 기능을 갖고 있지 않는다. 

RequestURL과 Request 컨트롤러의 맵핑을 관리하고 있는 것은 HanadlerMapping 인스턴스이다. DispatcherSerγlet 인스턴스 HandlerMapping 인스턴스를 참조하여 HandlerMapping 인스턴스로부터 반환된 Controller 인스턴스로 처리를 전달한다. 처리가 전달된 Controller 인스턴스는 필요한 비즈니스 로직을 호출하여 처리 결과(모델)와 이동할 View 정보를 DispatcherServlet에 반환한다. 이들의 정보(모델과 View)는 스프링MVC가 제공하는 ModeAndView 인스턴스로 취급된다. 

그리고 Controller 인스턴스로부터 반환된 View는 논리 정보이기 때문에 DispatcherServlet 인스턴스는 그 View의 실체를 ViewResolver 인스턴스에 문의한다. DispatcherServlet은 ViewResolver 인스턴스에 의해 해결된 View 인스턴스에 대해 모댈을 rendering하여 처리 결과를 브라우저에 표시한다 이상이 스프링 MVC 처리의 흐름이다.


위의 그림에 있는 HandlerMapping, Controller, ViewResolver, View는 모두 스프링 MVC가 제공하는 인터페이스이다. 

그러나 인터페이스만으로는 어플리케이션이 이루어지지 않으므로 인터페이스를 구현한 클래스가 필요하다. 

하지만 어플리케이션 개발자가 각각의 인터페이스의 구현 클래스를 준비해야만 하는 것은 아니다. 스프링 MVC에는 인터페이스뿐만 아니라 각각의 인터페이스의 구현 클래스도 포함하고 있다.

통상 개발에서는 Controller를 제외한 HandlerlMapping, Viewresolver, View 인터페이스의 구현 클래스는 스프링 MVC에서 제공히는 구현 클래스를 활용하면 된다(Controller 인터페이스를 구현한 편리한 클래스도 준비되어 있다).

스프링 MVC는 이들의 구성 정보를 DI 컨테이너에서 관리하고 있다. 스프링 설정 파일을 어느 HandlerMapping의 구현 클래스를 사용할 것인지는 Controller 클래스로부터 호출할 비즈니스 로직을 연관 지음으로써 어플리케이션의 구성을 관리하면 된다.


(1) DispatcherServlet

웹 브라우저로부터 송신된 Request를 일괄적으로 관리한다 .

웹 브라우저로부터 요청은 DispatherServlet 인스턴스로 송신된다. DispatherSerlvet 인스턴스는 지정된 RequestURL이 어느 Request 컨트롤러에 맵핑되어 있는 가를 알아야 한다.


cf.) DispatherServlet 설정과 ApplicationContext 관계

DispatherServlet은 클라이언트의 요청을 중앙에서 처리하는 스프링 MVC의 핵심 구성 요소이다. 

web.xml 파일에 한 개 이상의 DispatherServlet을 설정할 수 있으며, 각 DispatherServle은  한 개의 WebApplicationContext를 갖게 된다.

또한, 각 DispatherServlet이 공유할 수 있는 빈을 설정할 수도 있다. 

DispatherServlet과 웹 어플리케이션을 위한 설정 방법 및 둘 사이의 관계에 대해서 살펴보자.


(2) HandlerMapping

- RequestURL과 Controller 클래스의 맵핑을 관리한다.

- HandlerMapping 인스턴스의 정의에 정의된 정보로부터 Controller에 해당하는 클래스(Request 컨트롤러)가 결정된다.

- BeanNameUrlHandlerMapping 클래스는 스프링 설정 파일 내에 정의된 Controller 클래스의 name 속성과 RequestURl과 맵핑하는 HandlerMapping 인터페이스의 구현 클래스이다.

- BeanNameUrlHandlerMapping 클래스는 name 속성으로 지정된 값을 그대로 context root 이하의 패스로 처리한다. 즉, 다음의 RequestURL이 웹 브라우저에서 보내졌을 경우에 여기에서 지정한 Controller 클래스가 호출되게 된다. HandlerMapping 인터페이스의 구현 클래스 중 디폴트 클래스:BeanNameUrlHandlerMapping


(3) Controller

Controller 인스턴스는 필요한 비즈니스 로직을 호출하여 처리 결과(모델)와 이동할 View 정보를 DispatherServlet에 반환된다. 

이들의 정보(모델과 view)는 스프링 MVC가 제공하는 ModelAndView 인스턴스로 취급된다. 

그리고 Controller 인스턴스로부터 반환된 View는 논리정보이기 때문에 DispatherServlet 인스턴스는 그 View의 실체를 ViewResolver 인스턴스에 문의한다. 

DispatherServlet은 ViewResolver 인스턴스에 의해 해결된 View 인스턴스에 대해 모델을 rendering하여 처리 결과를 브라우저에 표시한다. 

cf.) HandlerMapping, Controller, ViewResolver, View는 모두 스프링 MVC가 제공하는 인터페이스이다. 그러나 인터페이스만으로는 어플리케이션이 이루어지지 않으므로 인터페이스를 구현 클래스가 필요한다.

스프링 MVC에는 인터페이스뿐만 아니라 각각의 인터페이스의 구현 클래스도 포함하고 있다.

통상 개발에서는 Controller를 제외한 HandlerMapping, Viewresolver, View 인터페이스의 구현 클래스는 스프링 MVC에서 제공하는 구현 클래스를 활용하면 된다.(Controller 인터페이스를 구현한 편리한 클래스도 준비되어 있다.) 

스프링 MVC는 이들의 구성 정보를 DI 컨테이너에서 관리하고 있다.


(4) ModelAndView

1) Controller 처리 결과 후 응답할 view와 veiw에 전달할 값을 저장.

2) 생성자 

- ModelAndView(String viewName) : 응답할 view설정

- ModelAndView(String viewName, Map values) : 응답할 view와 view로 전달할 값들을 저장 한 Map 객체 

- ModelAndView(String viewName, String name, Object value) : 응답할 view이름, view로 넘길 객체의 name-value

3) 주요 메소드

- setViewName(String view) : 응답할 view이름을 설정

- addObject(String name, Object value) : view에 전달할 값을 설정 – requestScope에 설정됨

- addAllObjects(Map values) : view에 전달할 값을 Map에 name-value로 저장하여 한번에 설정 - requestScope에 설정됨

4) Redirect 방식 전송

- view이름에 redirect: 접두어 붙인다.ex) mv.setViewName(“redirect:/welcome.html”);


protected ModelAndView HandlerequestInternal(HttpServletRequest req,  HttpServletResponse res) throws Exception{

//Business Logic 처리

ModelAndView mv = new ModelAndView();      

mv.setViewName(“/hello.jsp”);

mv.addObject(“greeting”, “hello world”);

return mv;

}


(5) ViewResolver

Controller 클래스로부터 반환된 View 정보가 논리적인 View 이름일 경우에는 bean 설정파일에 정의되어 있는 ViewResolver 클래스를 이용하여 클라이언트에게 출력할 View 객체를 얻게 된다.

InternalResource ViewResolver 클래스는 Request 컨트롤러로부터 반환된 View 정보를 context root 이하의 패스로서 처리한다.

1) Controller가 넘긴 view이름을 통해 알맞은 view를 찾는 역할

- Controller는 ModelAndView 객체에 응답할 view이름을 넣어 return.

- DispatchServlet은 ViewResolver에게 응답할 view를 요청한다.

- ViewResolver는 View 이름을 이용해 알 맞는 view객체를 찾는다.

2) 다양한 ViewResolver를 SpringFramework는 제공한다.

- InternalResourceViewResolver : 뷰의 이름을 JSP, HTML등과 연동한 View를 return

- ViewResolver – Spring 설정파일에 등록한다.

Controller

ModelAndView mv = new ModelAndView();

mv.setViewName(“hello”)

위의 경우

/WEB-INF/jsp/hello.jsp 를 찾는다.


(6) View

프레젠테이션층으로의 출력 데이터를 설정한다.



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

[Spring] iBatis 1  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13
[spring] 스프링 MVC 인터페이스 구현 클래스  (0) 2014.12.13
[Spring] AOP 용어 설명  (0) 2014.12.13
[Spring] 의존관계 주입  (0) 2014.12.13

[Spring] AOP 용어 설명


AOP 용어 설명



1. Advice

언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의하고 있다. 예를 들어, '메서드를 호출하기 전'(언제)에 '트랜잭션을 시작한다.'(공통기능)기능을 적용한다는 것을 정의하고 있다.

Target 클래스에 조인 포인트에 삽입되어져 동작(적용할 기능)할 수 있는 코드를 '어드바이스'라 한다.

관점으로서 분리되고 실행시 모듈에 위빙된 구체적인 처리를 AOP에서는 Advice라고 한다. Advice를 어디에서 위빙하는지는 뒤에 나오는 PointCut이라는 단위로 정의한다.

또한 Advice가 위빙되는 인스턴스를 '대상객체'라고 한다.

advice는 Pointcut에서 지정한 Jointpoint에서 실행되어야하는 코드이다.


cp.) 스프링의 Advice 타입

- Around Advice: Joinpoint 앞과 뒤에서 실행되는 Adcvice

- Before Advice: Joinpoint 앞에서 실행되는 Advice

- After Returning Advice: Jointpoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice

- After Throwing Advice: 예외가 던져질 때 실행되는 Advice

- Introduction:  클래스에 인터페이스와 구현을 추가하는 특수한 Advice


2. JoinPoint

Advice를 적용 가능한 지점을 의미한다. 메서드 호출, 필드 값 변경 등이 Joinpoint에 해당한다.

클래스의 인스턴스 생성 시점', '메소드 호출 시점', '예외 발생 시점'과 같이 어플리케이션을 실행할 때 특정 작업이 시작되는 시점을 '조인포인트'라고 한다

실행시의 처리 플로우에서 Advice를 위빙하는 포인트를 JointPoint라고 한다. 구체적으로는 메서드 호출이나 예외발생이라는 포인트를 Joinpoint라고 한다.

인스턴의 생성시점, 메소드를 호출하는 시점, Exception이 발생하는 시점과 같이 어플리케이션이 실행될 때 특정작업이 실행되는 시점을 의미한다.


3. Pointcut

Joinpoint의 부분 집합으로서 실제로 Advice가 적용되는 Jointpoint를 나타낸다. 스프링에서는 정규 표현식이나 AspectJ 문법을 이용하여 Pointcut을 정의할 수 있다.

여러 개의 조인포인트를 하나로 결합한 것을 포인트 컷이라 한다.

하나 또는 복수의 Jointpoint를 하나로 묶은 것을 Pointcut 이라고 한다. Advice의 위빙 정의는 Pointcut을 대상으로 설정한다. 하나의 Pointcut에는 복수 Advice를 연결할 수 있다. 반대로 하나의 Advice를 복수 Pointcut에 연결하는 것도 가능하다.

Pointcut(교차점)은 JoinPoint(결합점)들을 선택하고 결합점의 환경정보를 수집하는 프로그램의 구조물이다. Target 클래스와 Advice가 결합(Weaving)될 때 둘 사이의 결합 규칙을 정의하는 것이다.


4. Weaving

Advice를 핵심 로직 코드에 적용하는 것을 weaving 이라고 한다.(분리한 관점을 여러 차례 모률에 삽입하는 것을 AOP에서는 위빙 (Weaving: 엮기)이라고 부른다.) 즉 공통 코드를 핵심 로직 코드에 삽입하는 것이 weaving이다.

어드바이스를 핵심 로직 코드에 삽입하는 것을  위빙이라고 한다.

Aspect를 target 객체에 제공하여 새로운 프록시 객체를 생성하는 과정을 말한다.


5. Aspect

여러 객체에 공통으로 적용되는 공통 관심 사항을 Aspect라고 한다. 트랜잭션이나 보안 등이 Aspect의 좋은 예이다.

여러 객체에 공통으로 적용되는 공통 관점 사항을 에스펙트라 한다.

Aspect는 AOP의 중심단위, Advice와 Pointcut을 합친 것이다.(Advisor)


6.Target

핵심 로직을 구현하는 클래스를 말한다.

충고를 받는 클래스를 대상(target)이라고 한다. 대상은 여러분이 작성한 클래스는 물론, 별도의 기능을 추가하고자 하는 써드파티 클래스가 될 수 있다.


7. advisor

어드바이스와 포인트컷을 하나로 묶어 취급한 것을 '어드바이저'라 부른다.

advisor와 Pointcut을 하나로 묶어 다루는 것을 Advisor라고 한다. Advisor는 스프링 AOP에만 있는 것인데, 관점 지향에서 관점을 나타내는 개념이라고 할 수 있다.


8. proxy

대상 객체에 Advice가 적용된 후 생성된 객체




cp.) 언제,어디서,누가,무엇을,어떻게, 왜 할 때 AOP 걸어라.....

내가 원하는 메소드 실행할 때(언제)

객체(어디서)

로그를 찍는 기능(누가)

로그를(무엇을)

앞뒤로(어떻게)


cp.) AOP 핵심

1. 대상: Target

2. 적용할 기능: advice

3. 대상의 어디에서 적용할지 구체적인 명시:pointcut


aspect와 advice의 관계(추상 명사와 구체 명사의 관계)

advice라는 코드를 내가 만듬

로그를 찍는 기능 자체를 aspect라고 한다. 




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

[Spring] iBatis 1  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13
[spring] 스프링 MVC 인터페이스 구현 클래스  (0) 2014.12.13
[Spring] 스프링 MVC 패턴 개요  (0) 2014.12.13
[Spring] 의존관계 주입  (0) 2014.12.13