라이브러리는 프로그램 개발 시 활용할 수 있는 클래스와 인터페이스들을 모아놓은 것을 말한다. 일반적으로 JAR(java archive)
압축 파일인(~.jar) 형태로 존재한다. JAR 파일에는 클래스와 인터페이스의 바이트 코드(~.class)들이 압축되어있다.
(~.jar)
---------------
|pack1 | ---import---> application1
| |- A.class |
|pack2 | ---import---> application2
| |- B.class |
---------------
프로그램 개발 시 라이브러리를 이용하려면 JAR
파일을 ClassPath
에 추가해야 한다. ClassPath
란 말 그대로 클래스를 찾기 위한 경로이다. ClassPath
에 라이브러리를 추가하는 방법은 다음과 같다.
-classpath
로 제공CLASSPATH
환경 변수에 경로를 추가my_lib
라이브러리 프로젝트를 생성하고, pack1
, pack2
package를 생성하자. 그리고 다음의 코드를 넣어주도록 하자.
package pack1;
public class A {
public void method() {
System.out.println("A-method 실행");
}
}
package pack2;
public class B {
public void method() {
System.out.println("B-method 실행");
}
}
다음으로 jar파일 결과를 넣을 dist
디렉터리를 만들도록 하자.
src
pack1
- A.java
pack2
- B.java
dist
이렇게 구성된 것이다. jar를 만드는 방법은 다음과 같다.
javac
를 통해서 .java
파일들을 .class
파일로 컴파일하기jar
명령어를 통해서 jar 생성intellij에서는 최상위 tab의 Build
-> Build Project
를 누르면 out
디렉터리에 .class
결과가 나온다.
javac
로 complie하는 방법은 다음과 같다.
javac -d ./bin ./src/**/*.java
./src/**/*.java
는 src
에 있는 모든 패키지들에 .java
파일들을 .class
로 컴파일하겠다는 것이다. 그 결과는 ./bin
디렉터리에 넣도록하겠다는 것이다.
이제 jar
를 통해서 이 .class
파일을 jar로 만들어보도록 하자.
jar cvf ./dist/my_lib.jar -C ./bin/ .
-C ./bin/ .
로 bin
에 있는 모든 .class
파일들을 묶어서 jar
로 만들겠다는 것이다.
결과가 다음과 같이 /dist/my_lib.jar
로 나올 것이다.
이제, 새로운 project를 만들고 my_lib.jar
를 적용해보도록 하자. 이를 적용하기 위해서, intellij에서는 다음과 같이 설정할 수 있다.
새로운 project에 lib
디렉터리를 만들고 my_lib.jar
를 넣도록 하자.
src
- app.Main.java
lib
- my_lib.jar
File -> Project Structure -> 왼쪽의 Modules -> + 버튼 -> JARs or Dictionaries -> my_lib.jar 선택
잘 설정되었다면 intellij에서 해당 jar를 읽고 package들을 불러올 수 있다.
import pack1.A;
import pack2.B;
public class app.Main {
public static void main(String[] args) {
A a = new A();
a.method();
B b = new B();
b.method();
}
}
이제 실행시켜보도록 하자.
java -classpath ./lib/my_lib.jar:. ./src/app.Main.java
-classpath
로 현재 my_lib.jar
파일을 포함시키도록 하자. 또한, ./bin
으로 우리의 app.Main.class
의 경로도 포함시켜야한다. 이때, 경로의 구분자는 :
으로 my_lib.jar
과 ./bin
경로를 :
로 구분한 것이다. app.Main
은 app.Main.class
이다.
결과는 다음과 같다.
A-method 실행
B-method 실행
우리의 my_lib.jar
라이브러리가 잘 적용된 것을 볼 수 있다.
java 9부터 지원하는 모듈(module)은 패키지 관리 기능까지 포함된 라이브러리이다. 일반 라이브러리는 내부에 포함된 모든 패키지에 외부 프로그램에서의 접근이 가능하지만, 모듈은 다음과 같이 일부 패키지를 은닉하여 접근할 수 없게끔 할 수 있다.
----A module----
| package1---------사용----->
| package2---------사용----->
| package3(은닉)|
----------------
또 다른 차이는 모듈은 자신이 실행할 때 필요로 하는 의존 모듈을 module-info.java
에 기술할 수 있기 대문에 모듈 간의 의존 관계를 쉽게 파악할 수 있다는 것이다. 다음은 모듈 A가 모듈 B에 의존하여 실행할 수 있고, 모듈 B는 모듈 C에 의존하여 실행할 수 있는 모습을 보여준다.
Module A ---> Module B ---> Module C
모듈도 라이브러리이므로 jar파일 형태로 배포할 수 있다. 대규모 응용 프로그램은 기능별 모듈화를 해서 개발할 수도 있다.
------------------
|my_application_2|
------------------
| |
---my_module_a--- ---my_module_b---
| package | | package |
----------------- -----------------
다음의 형식으로 만들어보도록 하자.
새로운 프로젝트인 my_module_a
을 만들어, 모듈화를 해보도록 하자.
src
pack1
- A.java
pack2
- B.java
- module-info.java
다음의 코드를 입력하도록 하자.
package pack1;
public class A {
public void method() {
System.out.println("A-method 실행");
}
}
package pack2;
public class B {
public void method() {
System.out.println("B-method 실행");
}
}
module my.module.a {
exports pack1;
exports pack2;
}
pack1
과 pack2
는 외부에서 사용할 때 import를 허용하는 것이다.
my_module_a
에 가서 다음의 명령어를 입력하여 liba.jar
를 만들도록 하자.
javac -d ./bin ./src/module-info.java ./src/**/*.java
jar cvf ./dist/liba.jar -C ./bin/ .
dist
에 liba.jar
가 만들어졌을 것이다.
새로운 프로젝트인 my_module_b
을 만들어, 모듈화를 해보도록 하자.
src
pack3
- C.java
pack4
- D.java
- module-info.java
package pack3;
public class C {
public void method() {
System.out.println("C-method 실행");
}
}
package pack4;
public class D {
public void method() {
System.out.println("D-method 실행");
}
}
module my.module.b {
exports pack3;
}
pack3
은 외부에 노출하지만, pack4
은 외부에 노출하지 않도록 하는 것이다.
my_module_b
에 가서 다음의 명령어를 입력하여 liba.jar
를 만들도록 하자.
javac -d ./bin ./src/module-info.java ./src/**/*.java
jar cvf ./dist/libb.jar -C ./bin/ .
dist
에 libb.jar
가 만들어졌을 것이다.
새로운 project를 만들어서 lib
에 liba.jar
와 libb.jar
를 넣주도록 하자.
lib
- liba.jar
- libb.jar
src
app
- Main.java
- module-info.java
src에 app
package를 만들고, Main.java
를 쓰는 것이다. module system을 쓰기 위해서는 application code도 module을 지정해야하기 때문에 모듈명을 쓰는 것이다.
이제 코드를 넣어보도록 하자.
package app;
import pack1.A;
import pack2.B;
import pack3.C;
public class Main {
public static void main(String[] args) {
A a = new A();
a.method();
B b = new B();
b.method();
C c = new C();
c.method();
// 불가 D d = new D();
}
}
pack4는 exports하지 않았으므로 D
는 불러오지 못한다.
module JavaGrammer {
requires my.module.a;
requires my.module.b;
}
실행해보도록 하자.
java --module-path ./lib:. ./src/app/Main.java
A-method 실행
B-method 실행
C-method 실행
module은 java로 실행할 때 --module-path
를 써야한다.
다음과 같이 모듈의 의존관계가 형성될 수 있다.
app ---> my_module_a --> my_module_b
이런 경우, app
이 my_module_a
을 사용할 때 my_module_b
도 필요하다는 것이다.
가령, app
의 module-info.java
가 아래와 같다고 하자.
module JavaGrammer {
requires my.module.a;
}
그리고 my_module_a
의 module-info.java
는 아래와 같다고 하자.
module my.module.a {
exports pack1;
requires my_module_b;
}
이러한 경우 app
은 my_module_a
만 의존했지만, my_module_b
도 의존해야하는 상황이 나온다. 만약, my_module_b
를 의존해주지 않으면 에러가 발생하게 되는 것이다. 이러한 문제를 해결하기 위해서, java에서는 '전이 의존'이라는 것을 만들었는데, my_module_a
가 my_module_b
를 의존하므로 my_module_a
를 의존하는 모듈은 my_module_b
도 자동으로 가지도록 하는 것이다. 이를 다음과 같이 쓸 수 있다.
module my.module.a {
exports pack1;
requires transitive my_module_b;
}
의존관계가 다음과 같이 된다.
----------------------------
| |
| v
app ---> my_module_a --> my_module_b
transitive
덕분에 my_module_a
를 의존하는 시스템도 자동으로 my_module_b
도 의존할 수 있게 된 것이다.
은닉된 패키지는 기본적으로 다른 모듈에 의해 리플렉션을 허용하지 않는다. 리플렉션이란 실행 도중에 타입(클래스, 인터페이스 등)을 검사하고, 구성 맴버를 조사하는 것을 말한다.
경우에 따라서는 은닉된 패키지도 리플렉션을 허용해야 할 때가 있다. 모듈은 module-info.java
를 통해 모듈 전체 또는 지정된 패키지에 대해 리플렉션을 허용할 수 있고, 특정 외부 모듈에서만 리플렉션을 허용할 수도 있다.
open module 모듈명 {
...
}
module 모듈명 {
...
opens package1;
opens package2;
}
exports
된 패키지는 언제나 리플렉션이 불가능하므로 opens
를 지정해야 리플렉션이 가능하다.
module 모듈명 {
...
opens 패키지1 to 외부모듈명, 외부모듈명, ...;
opens 패키지2 to 외부모듈명;
}
외부모듈은 해당 패키지를 리플렉션으로 사용하고 싶을 client를 말한다. 즉, 특정 client에게만 리플레션을 open하겠다는 것이다. 특정 모듈에 제한하여 사용하는 것이 안전한 사용 방법이다.