728x90


이제 프로젝트 다운 프로젝트를 할 수 있게 되기까지 두 단계가 남았다.
바로 리소스와 유닛 테스트인데 이번에는 리소스를 사용하는 방법에 대해서 알아보도록 하자.


사실 리소스는 컴파일 때는 아예 필요없다.

리소스는 실행시간에만 존재하면 되기 때문이다.

그러나 정말 아쉽게도 실제로 jar파일을 만들거나 실행파일을 배포해본적이 없다면

이 리소스를 정확히 어디에 위치시켜야할지 잘 모르기 때문에 우를 범하기 쉽다.

이번에는 그러한 실수를 막게 하기 위해서 어떻게 해야하는지를 알려드리겠다.



여기서 보면 소스 폴더안에 리소스폴더가 같이 존재한다.

리소스를 소스폴더안에 넣는지 아니면 밖에 빼는지는 프로그램의 목적에 따라 다르다.

물론 리소스를 소스폴더를 소스에 넣지 않고 아예 밖으로 빼는 경우도 많다.

사실 그런거에 큰 신경쓸 필요는 없다. 중요한건 jar파일안에 리소스를 넣을건지 아니면 jar에서 뺄건지 이다.

일단 먼저 jar를 리소스안에 넣는다고 가정하고 하겠다.


jar파일 안에 리소스를 넣기 - 리소스가 소스 디렉터리에 들어있는 경우


일단 먼저 Main을 보자.


package net.theceres.main;

import net.theceres.gui.MyFrame;

public class Main {
public static void main(String[] args) {
MyFrame myframe = new MyFrame(500, 300);
myframe.setVisible(true);
}
}

Main클래스는 크게 신경쓸게 없다. 왜냐하면 여기서는 리소스를 긁어 쓰는 부분이 없기 때문이다.


package net.theceres.gui;

import resource.text.ResTxt;

import javax.swing.*;
import java.awt.*;

public class MyFrame extends JFrame {

public MyFrame(int width,int height){
setSize(width,height);
setContentPane(new JPanel(){
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
ImageIcon ii = new ImageIcon(getClass().getResource("/resource/img/index.jpeg"));
Image img = ii.getImage();
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
}
});
setTitle(ResTxt.getS("title"));
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}

여기서는 일단 그림을 하나 가져온다. 리소스를 가져오는 코드를 줌해서 보자.


ImageIcon ii = new ImageIcon(getClass().getResource("/resource/img/index.jpeg"));

ImageIcon은 파일 경로를 받아서 사용한다. 여기서 경로를 사용하는 방식이 매우매우매우 중요하다.

혹시 기존에 absolute패스라던지 현재작업디렉터리라던기 그런걸 써선 안된다. 절대 안된다.

현재 작업 디렉터리는 언제든지 바뀔수 잇는 개념이다. 따라서 경로를 가져올때는 반드시 getClass().getResource로 가져온다.

여기서 getClass().getResource("/")의 의미는 컴파일된 class파일의 최상위 혹은 jar파일의 최상위 파일을 의미한다.


package resource.text;

import java.util.ResourceBundle;

public class ResTxt {
private final static String RESOURCE = "resource.text.main";
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(RESOURCE);

public static String getS(String key) {
return RESOURCE_BUNDLE.getString(key);
}
}

이건 텍스트를 가져오는 ResourceBundle역시 마찬가지 이다. 보면 알겠지만 resource.text.main으로 시작해서 가져온다.

그럼 ant build.xml이 어떻게 이루어지는지 한번 보도록 하자.


<?xml version="1.0"?>
<project name="Swing App" default="main" basedir=".">
<property name="Name" value="Swing App"></property>
<property name="name" value="swing-app"></property>
<property name="groupid" value="net.theceres"></property>
<property name="project.version" value="1.0.0"></property>

<property name="src.dir" value="src"></property>
<property name="build.dir" value="build"></property>
<property name="classes.dir" value="${build.dir}/classes"></property>
<property name="jar.dir" value="${build.dir}/jar"></property>


<target name="clean">
<delete dir="${build.dir}"></delete>
</target>

<target name="init">
<mkdir dir="${build.dir}"></mkdir>
<mkdir dir="${classes.dir}"></mkdir>
</target>

<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false">
</javac>
</target>

<target name="jar" depends="compile">
<jar destfile="${jar.dir}/${name}-${project.version}.jar" basedir="${classes.dir}">
<fileset dir="${src.dir}">
<patternset>
<exclude name="**/*.java"></exclude>
</patternset>
</fileset>
<manifest>
<attribute name="Main-Class" value="net.theceres.main.Main"></attribute>
</manifest>
</jar>
</target>

<target name="run" depends="jar">
<java fork="true" classname="net.theceres.main.Main">
<classpath>
<path location="${jar.dir}/${name}-${project.version}.jar"/>
</classpath>
</java>
</target>

<target name="main" depends="jar">
</target>
</project>

크게 바뀐건 없다. 한부분만 바뀌었다. 그 바뀐부분만 보도록하자.


<jar destfile="${jar.dir}/${name}-${project.version}.jar" basedir="${classes.dir}">
<fileset dir="${src.dir}">
<patternset>
<exclude name="**/*.java"></exclude>
</patternset>
</fileset>
<manifest>
<attribute name="Main-Class" value="net.theceres.main.Main"></attribute>
</manifest>

</jar>

여기서 보면 추가된 부분이 있다. 바로  fileset이라는 엘리먼트인데 이 엘리먼트안에 있는 파일은 jar파일에 추가된다.

근데 그냥 추가하면 문제점이 java파일들 역시 들어가게 된다는 점이다. 왜냐하면 fileset의 dir이 소스디렉터리이기 때문이다.

따라서 여기서는 java를 걸러줘야하므로 exclude해준다. exclude에서 와일드 캐릭터(*를 의미)를 쓰고싶다면 patternset으로 감싸줘야한다.

그럼 진짜로 되는지 한번 테스트해보자.



보다시피 제대로 실행됨을 확인할 수 있다.


jar파일 안에 리소스를 넣기 - 리소스가 밖에있는 경우


resource가 밖에 있는건 사실 ant를 사용할때는 조금 불편하다.

사실 ant만 사용한다면 별로 불편하지는 않는데 ide를 사용하면서

ant와 ide의 빌드를 번갈아가면서 사용할때는 조금 불편하게된다.

그래서 실제 ant프로젝트를 한번 보면 리소스를 소스에 넣는 경우와 넣지 않는 경우가 공존한다.

최근의 트랜드는 넣지 않는 것이기에 넣지 않고 사용하는 법을 알려주도록 하겠다.



리소스 폴더가 이번엔 분리 되어있다.

import를 제외하면 코드가 내부적으로 바뀐건 없다.

RestTxt는 이제 컴파일 해야하므로 main밑으로 옮겨준다.

이제 build.xml을 다시한번 보도록 하자.


<?xml version="1.0"?>
<project name="Swing App" default="main" basedir=".">
<property name="Name" value="Swing App"></property>
<property name="name" value="swing-app"></property>
<property name="groupid" value="net.theceres"></property>
<property name="project.version" value="1.0.0"></property>

<property name="src.dir" value="src"></property>
<property name="build.dir" value="build"></property>
<property name="classes.dir" value="${build.dir}/classes"></property>
<property name="jar.dir" value="${build.dir}/jar"></property>
<property name="res.dir" value="resource"></property>


<target name="clean">
<delete dir="${build.dir}"></delete>
</target>

<target name="init">
<mkdir dir="${build.dir}"></mkdir>
<mkdir dir="${classes.dir}"></mkdir>
</target>

<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false">
</javac>
</target>

<target name="jar" depends="compile">
<jar destfile="${jar.dir}/${name}-${project.version}.jar" basedir="${classes.dir}">
<fileset dir="${basedir}">
<patternset>
<include name="${res.dir}/**"></include>
</patternset>
</fileset>
<manifest>
<attribute name="Main-Class" value="net.theceres.main.Main"></attribute>
</manifest>
</jar>
</target>

<target name="run" depends="jar">
<java fork="true" classname="net.theceres.main.Main">
<classpath>
<path location="${jar.dir}/${name}-${project.version}.jar"/>
</classpath>
</java>
</target>

<target name="main" depends="jar">
</target>
</project>

코드가 조금 바뀌었다. 바뀐부분을 한번 보도록 하자.

<property name="res.dir" value="resource"></property>

먼저 resource디렉터리를 프로퍼티로 선언해준다.


<target name="jar" depends="compile">
<jar destfile="${jar.dir}/${name}-${project.version}.jar" basedir="${classes.dir}">
<fileset dir="${basedir}">
<patternset>
<include name="${res.dir}/**"></include>
</patternset>
</fileset>
<manifest>
<attribute name="Main-Class" value="net.theceres.main.Main"></attribute>
</manifest>
</jar>
</target>

이번에는 fileset 으로 포함할 디렉터리의 루트를 basedir로 하자. 그리고 이번엔 반대로 포함해줄 파일을 선택해야한다.

물론 그 와중에 뺼게 있으면 빼도된다.

와일드카드를 쓸것이므로 patternset으로 감싸고 include로 resource디렉터리의 모든 하위폴더를 선택해준다.

이제 빌드를 해서 사용하면 된다.


ant를 사용할때 리소스를 뺴면 ide로 빌드할때는 실행이 안될것이다.

물론 되게 할 수 있지만 여기서는 ide를 가르치는게 아니므로 더 이상 언급하지 않겠다.




+ Recent posts