본문 바로가기
언어공부/JAVA&SPRING

[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 11회차 미션

by hobbiz 2020. 8. 20.
반응형

오늘은 Class 클래스와 String 클래스, Wrapper에 대해 공부해보았다.

 

Class 라는 이름의 클래스가 있다고 한다.

 

자바의 모든 클래스와 인터페이스는 컴파일 후 class파일로 생성된다.

 

class클래스는 컴파일된 class파일에서 객체의 정보를 가져올 수 있다.

 

Class 클래스는 컴파일된 class파일에서 객체의 정보를 가져올 수 있다.

 

 

 

Class 클래스를 가져오는 방벙은 아래와 같다.

 

 

 

forName() 메서드와 동적 로딩에 대해서도 알아보았다.

 

forName()은 Class클래스의 static 메서드이다.

 

동적 로딩이란 컴파일시에 데이터 타입이 모두 binding 되어 자료형이 로딩(static loading)되는 것이 아니라 실행중에 데이터 타입을 알고 binding 되는 방식이다.

 

실행시에 로딩되므로 경우에 따라 다른 클래스가 사용될 수 있어 유용하다.

 

컴파일 타임에 체크할 수 없으므로 해등 문자열에 대한 클래스가 없는 경우 예외(ClassNotFoundException)가 발생할 수 있다.

 

 

String 클래스의 경우 두가지 방법으로 사용될 수있는데,

 

하나는 인스턴스로 생성되는 방법이고(주소값을 저장)

하나는 상수풀에 있는 문자열을 가리키는 방법이다.

 

 

 

<추가공부>

자바 동적로딩 이해(델리게이션 모델)

 

자바 프로그램은 한 개 혹은 그 이상의 클래스들의 조합으로 실핸된다. 그리고 실행 시 모든 클래스 파일드이 한 번에 JVM 메모리에 로딩되지 않고 요청되는 순간 로딩된다. 자바의 클래스 로더가 이런 역할을 수행한다. 클래스 로더란 '.class' 바이트 코드를 읽어 들여 class 객체를 생성하는 역할을 담당한다. 즉, 클래스 로더는 클래스가 요청될 때 파일로부터 읽어 메모리로 로딩하는 역할을 하며 자바 가상 머신의 중요한 요소 중 하나다.

 

※ 클래스 로더가 classpath라는 환경 변수에 등록된 디렉토리에 있는 모든 클래스들을 먼저 JVM에 로딩한다. JVM에 로딩된 클래스만이 JVM에서 객체로 사용할 수 있다. 클래스 로딩은 클래스를 로딩하는 시점 또는 실행 중간에도 할 수 있다.

 

자바의 클래스 로딩은 세부적으로 로딩, 링크, 초기화라는 세 단계 과정을 거친다.

  • 로딩 : 클래스 파일을 바이트 코드로 읽어 메모리로 가져오는 과정
  • 링크 : 가장 복잡한 과정으로, 읽어본 바이트 코드가 자바 규칙을 따르는지 검증하고, 클래스에 정의된 필드, 메소드, 인터페이스들을 나타내는 데이터 구조를 준비하며, 그 클래스가 참조하는 다른 클래스를 로딩한다.
  • 초기화 : 슈퍼 클래스 및 정적 필드를 초기화한다.

 

 

 

public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}

 

 

 

 

위 코드를 보면 Hello 클래스를 로드할 때 이 클래스가 참조하고 있는 Object, String, System 클래스가 아직 로드되지 않았으므로 Hello 클래스를 로드하는 일을 중단하고 우선 이 클래스들을 로딩한다. 이처럼 한 클래스의 로드 타임에 필요한 다른 클래스들을 동적 로딩하는 것을 로드타임 동적 로딩이라고 한다.

 

public interface PrintInterface {
public void print();
}
 
public class ClassLoadingSample1 implements PrintInterface {
@Override
public void print() {
System.out.println("Sample1");
}
}
 
public class ClassLoadingSample2 implements PrintInterface {
 
@Override
public void print() {
System.out.println("Sample2");
}
}
 
public class RuntimeLoading {
 
public static void main(String[] args) {
RuntimeLoading runtimeLoading = new RuntimeLoading();
try {
Class<?> cls = Class.forName(args[0]);
Object obj = cls.newInstance();
PrintInterface print = (PrintInterface)obj;
print.print();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

 

위는 실행할 때 매개변수로 입력받은 클래스를 런타임시 동적으로 로딩하는 예시다. Class.forName() 메소드로 리턴 되는 클래스 객체는 해당 클래스에 대한 객체가 아닌 해당 클래에 대한 메타 정보를 가지는 클래스 객체다. 실제 객체가 아닌 객체를 생성하기 위한 정보라고 생각해보자. cls.newInstance()를 하면 이제 메타 데이터를 이용하여 해당 객체를 생성한다. 이때 Object로 리턴하기 때문에 적절한 캐스팅을 해줘야 한다.

 

클래스 로더가 최초 클래스를 로딩할 때, '.class' 바이트 파일에서 여러 정보를 추출해서 Class 객체로 만든다. 앞서 설명했듯이 Class 클래스는 클래스의 이름, 메소드, 생성자, 필드명, 부모 클래스의 정보를 개발자가 실행하거나 참조할 수 있다. 이와 같이 클래스의 내부 정보를 보는 기법을 리플렉션이라고 하며 이에 대한 자세한 내용은 11장에서 설명하겠다. 클래스 로더는 리플렉션과 매우 밀접한 관계가 있기 때문에 클래스를 로딩하는데 사용된다.

 

※newInstance()를 호출하면 String 객체를 제외한 모든 클래스는 Object를 리턴한다. Stirng 클래스만 유일하게 Stirng 객체를 리턴한다.

 

 

자바에서는 클래스 로딩 매커니즘을 보다 빠르게 동작하고 쉽게 확장할 수 있도록 하기 위해 클래스 로더 간 계층구조로 되어 있다. 그리고 클래스 로딩 시 델리게이션 모델을 따르도록 하였다.

 

  • Bootstrap Class Loader : JVM이 실행될 때 맨 처음 실행되는 클래스 로더로 $JAVA_HOME/jre/lib에 있는 JVM 실행에 필요한 가장 기본적인 라이브러리(rt.jar 등)를 로딩한다. 다른 클래스 로더와 달리 자바가 아닌 네이티브로 구현되어 있다.
  • Extensions Class Loader : Bootsrap Loading 후 기본적으로 로딩되는 클래스로 $JAVA_HOME/jre/lib/ext에 있는 클래스들이 로딩된다. 이 클래들은 별도로 classpath에 잡혀 있지 않아도 로딩된다.
  • System Class Loading :  다음으로 CLASS PATH에 정의되거나 JVM option에서 -cp, -classpath에 지정된 클래들이 로딩된다.
  • User-Defined Class Loader : 사용자가 직접 생성해서 사용하는 클래스 로더다.

 

클래스 로더 계층 구조에서 하위 클래스 로더는 델리게이션 오청에 의해 부모 클래스 로더가 로딩한 클래스를 찾을 수 있지만 그 반대는 불가능하다. 그리고 클래스 로더에 의해 로딩된 클래스는 언로드(unload)될 수 없고 그 클래스를 로딩한 클래스 로더가 삭제될 때까지 유지된다.

 

 

※ 클래스명이 같더라도 패키지 명이 다르면 구분이 가능하다.

 

 

클래스 로더가 클래스 로딩을 요청받게 되면 캐시, 부모 클래스 로더, 자신 클래스 로더 순으로 클래스 로딩이 된다. 캐시에서는 클래스를 로딩한 적이 있는지 확인한다. 이전에 로딩된 클래스는 해당 클래스 로더의 캐시에 저장되고 다음 번 요청 시 캐시에 저장된 내용을 사용한다. 해당 클래스를 로딩한 적이 없다면 상위 부모 클래스 로더에게 클래스 로딩 요청을 위임한다. 클래스 로딩을 위임받은 부모 클래스 로더 또한 자신의 캐시를 먼저 확인하고 해당 클래스를 이전에 로딩한 적이 없다면 그 클래스 로더의 부모에게 클래스 로딩을 위임하는 동일한 과정을 거친다. 최상위 부트스트랩 클래스 로더까지 요청이 위임되고 이전에 클래스가 로딩된 적이 없다면 최상위 부모부터 자식 클래스 로더 순서로 클래스 로딩을 시도한다. 이러한 방식을 '델리게이션 모델'이라 한다.

 

 

 

다음은 위에서 설명한 로딩 메커니즘을 구현한 코드다. 그림과 함께 이해하면 클래스 로딩 메커니즘을 이해하는데 도움이 될 것이다.

 

/**
* Created by moonti on 2017. 1. 3..
*/
public abstract class ClassLoader {
private ClassLoader parent;
 
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class c = findLoadClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

 

 

 

클래스 로더의 loadClass() 메소드를 호출하면 우선 findLoadedClass() 메소드가 로드할 클래스가 이미 로드되어 있는지 확인한다. 로드되지 않았다면 부모 클래스에게 클래스 로딩을 위임하고 마지막으로 부트스트랩 클래스 로더까지 확인한다. 모든 클래스 로더에서 이전에 로드된 적이 없다면 부트스트랩 클래스 로더부터 지정된 위치의 물리적인 클래스 파일을 찾는다. 만약 찾을 수 없다면 ClassLoader 클래스에 선언된 findClass() 메솓를 순차적으로 실행해서 클래스 파일을 찾으려 한다. 그리고 최종적으로 resolveClass() 메소드를 실행하여 링킹 작업을 실행한다. 

 

실제 Classloader 클래스는 추상 클래스로 객체화 될 수 없고 구현클래스를 작성해야 한다.

 

ClassLoader 클래스 생성자

메소드

설명 

protected ClassLoader() 

시스템 클래스 로더를 부모로 가지는 클래스 로더를 생성한다. 

protected ClassLoader(ClassLoader parent) 

 지정된 클래스 로더를 부모로 가지는 클래스 로더를 생성한다.

 

ClassLoader 클래스의 메소드

메소드 이름 

설명 

defineClass 

  • 선언부 : protected Class<?> defineClass(byte[] b, int off, int len)
  • 설명 : '.class' 파일로부터 읽어온 바이트 배열을 Class 클래스 객체로 변환한다.

findClass 

  •  선언부 : protected Class<?> findClass(String name)
  • 설명 : 이름으로 클래스 파일을 찾는다. 디폴트 구현은 무조건 ClassNotFoundException이 발생하도록 되어 있다. 클래스 파일이 존재하면 defineClass() 메소드를 통해 바이너리 코드로부터 Class 객체를 생성한다.

findLibrary 

  • 선언부 : protected String findLibrary (String libname)
  • 설명 : 이름으로 해당 라이브러리의 패스를 반환한다. 디폴트 구현은 null 반환한다.

findLoadeedClass 

  • 선언부 : protected Class<?> findLoadedClass (String name)
  • 설명 : 해당 클래스 로더에 지정된 이름으로 로드된 클래스가 있는 경우 해당 Class 객체를 반환한다.

findResource 

  • 선언부 : protected URL findResource(String name)
  • 설명 : 지정된 이름을 가지는 자원의 위치를 나타내는 URL을 반환한다. 디폴트 구현은 null을 반환한다.

findResources 

  • 선언부 :protected Enumeration<URL> findResources(String name)
  • 설명 : 지정된 이름을 가지는 모든 자원의 URL 열거형을 반환한다.

getParent 

  • 선언부 : public ClassLoader getParent()
  • 설명 : 지정된 이름의 자원을 반환한다.

getResource

  • 선언부 : public URL getResource(String name)
  • 설명 : 지정된 이름의 자원을 반환한다.

getResources

  • 선언부 : public Enumeration<URL> getResources(String name)
  • 설명 : 지정된 이름의 모든 자원을 반환한다.

getSystemClassLoader 

  • 선언부 : public static ClassLoader getSystemClassLoader()
  • 설명 : 시스템 클래스 로더를 반화한다.

loadClass

  • 선언부 : public Class<?> loadClass(String name)
  • 설명 : 지정된 이름을 가지는 클래스를 로드한다.

resolveClass 

  • 선언부 : protected void resolveClass(Class<?> c)
  • 설명 : 지정된 클래스를 링크한다.

 

 

 

 

 

 

URL 클래스 로더

 

개발자가 클래스를 로딩하기 위해서는 클래스 로더 객체를 인스턴스해야 한다. 이때 URLClassLoader 클래스를 사용해보도록 하자. 이 클래스는 자바에서 기본으로 제공하는 클래스로서, 개발자가 지정한 위치, 즉 URL로부터 클래스를 로딩한다. URLClassLoader는 ClassLoader 클래스를 확장한 클래스로 ClassLoader에서 제공하는 메소드 중 findClass(), getResource() 등의 메소드가 알맞게 동작하도록 구현된 활용도 높은 클래스 로더다. JVM에서 사용하는 클래스 로더의 대부분은 URLClassLoader를 확장해서 구현한다.

 

 

URLClassLoader 클래스 생성자

메소드

설명 

public URLClassLoader(URL[] urls)

지정된 URL로부터 클래스를 로딩하는 클래스 로더를 생성한다. 해당 클래스 로더의 부모는 시스템 클래스 로더가 된다.

public URLClassLoader(URL[] urls, ClassLoader parent)

지정된 URL로부터 클래스를 로딩하는 클래스 로더를 생성한다.

 

 

 

public class FileURLClient {
 
public static void main(String[] args) throws Exception {
FileURLClient fileURLClient = new FileURLClient();
URL[] urlArray = {new File(fileURLClient.getClass().getResource("/").getPath()).toURI().toURL()};
URLClassLoader ucl = new URLClassLoader(urlArray);
Object obj = ucl.loadClass("test.Hello").newInstance();
}
 
URL[] urlArray = {
new URL("http", "www.java.com", "/"),
new URL("ftp", "user:pass@www.java.com", "/")
};
}

 

 

 

URLCLassLoader를 이용하면 실행 시 클래스 패스에 등록되어 있지 않은 클래스들을 URL로 지정해 로딩할 수 있다. 지정된 URL에서 로딩할 클래스 파일을 찾을 수 없다면 ClassNotFoundException이 발생한다.

 

위 코드는 URLClassLoader를 사용하는 예제로 파일 시스템의 test 디렉토리 아래 Hello 클래스를 로딩한다. URL 클래스는 프로토콜에 개방적이고 유연하게 만들어졌기 때문에 파일, 데이터베이스, 네트워크 등 리소스 위치나 프로토콜과 관계 없이 개방적으로 리소스에 접근할 수 있다. 다음과 같이 URL을 정의하면 HTTP, FTP를 통해 클래스를 로딩할 수 있다.

 

 

이번에 소개할 내용은 *.jar 파일로부터 클래스를 로드하는 방법이다.  URLClassLoader를 이용하여 classpath가 아닌 사용자가 지정한 파일로부터 클래스를 로딩하는 예제다. classpath.properties는 jar파일이 있는 위치를 나타내는 데이터 파일이다. 이 예제는 classpath.properties 파일을 읽어 우리가 로드할 '.jar' 형태의 패키지 데이터의 경로를 얻고 이 경로로 URL을 얻는다. URL을 얻으면 URLClassLoader를 통해 jar파일에 있는 클래스를 로드할 수 있다.

 

 
public class FileClassLoader {
 
public static void main(String[] args) {
FileClassLoader fileClassLoader = new FileClassLoader();
File cpList = new File(fileClassLoader.getClass().getResource("/").getPath(), "classpath.properties");
FileClassLoader fcl = new FileClassLoader();
 
try {
BufferedReader br = new BufferedReader(new FileReader(cpList));
URL[] urlList = fcl.getUrl(br);
br.close();
 
URLClassLoader ucl = new URLClassLoader(urlList);
Object obj = ucl.loadClass("test.Hello").newInstance();
System.out.println(obj.getClass().getSimpleName());
 
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
 
}
 
private URL[] getUrl(BufferedReader br) throws MalformedURLException, IOException {
String line = null;
URL[] urlList = null;
while((line = br.readLine()) != null) {
File cp = new File(line);
if (cp.exists()) {
URL[] temp;
int length = 0;
if (urlList != null) {
length = urlList.length;
temp = new URL[length + 1];
System.arraycopy(urlList, 0, temp, 0, length);
} else {
temp = new URL[1];
}
temp[length] = cp.toURI().toURL();
urlList = temp;
}
}
return urlList;
}
}

 

나중에 jar파일에 있는 클래스를 이용할 때 용이할 것이라 생각한다.

 

 

사용자 정의 클래스 로더

 

사용자 정의 클래스 로더는 사용자가 직접 ClassLoader 클래스를 확장해서 구현하는 것을 의미한다. ClassLoader 클래스는 기본적으로 델리게이션 모델이 구현되어 있어 ClassLoader 클래스 확장 시 loadClass() 메소드를 재정의하지 않아도 된다. 즉, 캐시를 조회하고 없을 경우 부모 로더에게 순차적으로 로딩을 위임하도록 되어 있다. 하지만 부모가 클래스를 로딩하지 못할 경우 마지막으로 클래스를 로드할 findClass() 메소드는 무조건 ClassNotFoundException이 발생하기 때문에 재정의해야 한다.

 사용자 정의 클래스 로더를 이용하면 프로세스 재기동 없이 변경된 모듈을 적용할 수 있으며, 소스 파일이 변경되었을 경우에도 컴파일 후 로딩할 수 있다. 즉, 클래스 로딩 시점에 다양한 기능을 구현할 수 있어 유용하다.

 

 

 

 
public class CustomClassLoader extends ClassLoader{
public static void main(String[] args) {
CustomClassLoader ccl = new CustomClassLoader();
try {
Object obj = ccl.loadClass("test.Hello").newInstance();
System.out.println(obj.getClass().getSimpleName());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public CustomClassLoader() {
super(CustomClassLoader.class.getClassLoader());
}
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String path = "/Users/moonti/Documents/coding_study/java/BJP/out/artifacts/test/test.jar";
FileInputStream file = new FileInputStream(path);
byte[] classByte = new byte[file.available()];
file.read(classByte);
return defineClass(name, classByte, 0, classByte.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
throw new ClassNotFoundException();
}
return null;
}
}

 

 

CustomClassLoader 클래스에 오버로딩된 두 개의 생성자들을 주목하자. 두 생성자 모두 super 키워드를 사용해서 부모 클래스 로더를 지정하고 있다. 앞서 설명했듯이 클래스 로더는 델리게이션 모델을 사용하고 있으며, 부모 클래스 로딩 작업을 위임하기 위해 반드시 부모 클래스 로더가 지정되어야 한다.

 CustomClassLoader 클래스의 부모인 java.lang.ClassLoader 클래스의 findClass() 메소드의 선언부는 protected로 선언되어 있다. 즉 자식 클래스만이 findClass() 메소드에 접근 할 수 있다. findClass() 메소드의 특징 때문에 protected 접근 제한자로 선언된 것이다. findClass() 메소드의 특징은 클래스를 매번 새롭게 로드한다는 것이다. 일반적으로 델리게ㅅ이션 모델에서는 클래스를 새롭게 로드하기 전 로딩된 캐시를 먼저 조회한 다음, 없는 경우에만 부모에게 위임하는 방식이다. 따라서 함부로 findClass() 메소드가 호출되면 델리게이션 모델을 위반하게 된다. 그렇기 때문에 CustomClassLoader 클래스에 정의된 main() 메소드에서도 loadClass() 메소드를 호출하도록 프로그래밍되었다.

 

loadClass() 메소드는 다음과 가은 순서로 클래스를 로딩한다.

  1. findLoadedClass()를 호출해서 캐시에 로드된 클래스를 찾는다.
  2. 부모 클래스 로더의 loadClass()를 호출해서 클래스 로딩을 시도한다.
  3. 마지막으로 findClass()를 사용해서 클래스 로딩을 한다.

 

지금까지 자바에서 동적 로딩을 하는 방법을 소개했다. 나도 심도있게 내용을 아는 것이 아니라 거의 처음 책으로 접한 내용이라 이해도가 많이 부족한 상태에서 포스팅을 했다. 포스팅에 참고한 책은 [ 길벗 - 자바를 다루는 기술 ]이다. 이 책은 초급자도 봐야하지만 자바의 동작 방법에 대해 꽤 중요한 내용을 다루는 책이라 추천할 만하다고 생각한다.

 이것으로 이번 포스팅을 마치도록 하겠다.



출처: https://futurists.tistory.com/43?category=550970

 

패스트캠퍼스 강의 : https://bit.ly/3ilMbIO

 

Java 웹 개발 마스터 올인원 패키지 Online. | 패스트캠퍼스

자바 기초문법부터 프로젝트 실습까지 Java 문법부터 스프링/스프링부트, 트렌디한 기술인 JPA까지 모두 배우는 온라인 강의입니다.

www.fastcampus.co.kr

 

반응형

댓글