- Загрузка классов в Java. Теория
- Введение
- Виды загрузчиков
- Понятия
- Модель делегирования загрузки
- Inside
- Как вызвать getClass () из статического метода в Java?
- 8 ответов
- How to access static resources in Java
- Example code
- How do those paths work?
- Why not files?
- Why not the other ways to use .getResource / .getResourceAsStream
- How do I get my resource files into my jars?
Загрузка классов в Java. Теория
Одной из основных особенностей платформы Java является модель динамической загрузки классов, которая позволяет загружать исполняемый код в JRE не перезагружая основое приложение. Такая особенность широко используется в серверах приложений, получивших последнее время высокую популярность.
В статье рассмотрены базовые понятия, аспекты и принципы модели динамической загрузки кода. В следующей статье будет рассмотрена реализация собственного загрузчика классов, как основного механизма приложения с плагино-модульной архитектурой.
Введение
Любой класс (экземпляр класса java.lang.Class в среде и .class файл в файловой системе), используемый в среде исполнения был так или иначе загружен каким-либо загрузчиком в Java. Для того, чтобы получить загрузчик, которым был загружен класс А, необходимо воспользоваться методом A.class.getClassLoader().
Классы загружаются по мере надобности, за небольшим исключением. Некоторые базовые классы из rt.jar (java.lang.* в частности) загружаются при старте приложения. Классы расширений ($JAVA_HOME/lib/ext), пользовательские и большинство системных классов загружаются по мере их использования.
Виды загрузчиков
Различают 3-и вида загрузчиков в Java. Это — базовый загрузчик (bootstrap), системный загрузчик (System Classloader), загрузчик расширений (Extension Classloader).
Bootstrap — реализован на уровне JVM и не имеет обратной связи со средой исполнения. Данным загрузчиком загружаются классы из директории $JAVA_HOME/lib. Т.е. всеми любимый rt.jar загружается именно базовым загрузчиком. Поэтому, попытка получения загрузчика у классов java.* всегда заканчиватся null’ом. Это объясняется тем, что все базовые классы загружены базовым загрузчиком, доступа к которому из управляемой среды нет.
Управлять загрузкой базовых классов можно с помощью ключа -Xbootclasspath, который позволяет переопределять наборы базовых классов.
System Classloader — системный загрузчик, реализованный уже на уровне JRE. В Sun JRE — это класс sun.misc.Launcher$AppClassLoader. Этим загрузчиком загружаются классы, пути к которым указаны в переменной окружения CLASSPATH.
Управлять загрузкой системных классов можно с помощью ключа -classpath или системной опцией java.class.path.
Extension Classloader — загрузчик расширений. Данный загрузчик загружает классы из директории $JAVA_HOME/lib/ext. В Sun JRE — это класс sun.misc.Launcher$ExtClassLoader.
Управлять загрузкой расширений можно с помощью системной опции java.ext.dirs.
Понятия
Различают текущий загрузчик (Current Classloader) и загрузчик контекста (Context Classloader).
Current Classloader — это загрузчик класса, код которого в данный момент исполняется. Текущий загрузчик используется по умолчанию для загрузки классов в процессе исполнения. В часности, при использовании метода Class.forName(«»)/ClassLoader.loadClass(«») или при любой декларации класса, ранее не загруженного.
Context Classloader — загрузчик контекста текущего потока. Получить и установить данный загрузчик можно с помощью методов Thread.getContextClassLoader()/Thread.setContextClassLoader(). Загрузчик контекста устанавливается автоматически для каждого нового потока. При этом, используется загрузчик родительского потока.
Модель делегирования загрузки
Начиная с версии Java 2 Platform, Standard Edition, v1.2 загрузчики классов образуют иерархию. Корневым является базовый (у него предка нет). Все остальные загрузчики при инициализации инстанциируют ссылку на родительский загрузчик. Такая иерархия необходима для модели делегирования загрузки. В общем случа, иерархия выглядит следующим образом.
Право загрузки класса рекурсивно делегируется от самого нижнего загрузчика в иерархии к самому верхнему. Такой подход позволяет загружать классы тем загрузчиком, который максимально близко находится к базовому. Так достигается максимальная область видимости классов. Под областью видимости подразумевается следующее. Каждый загрузчик ведет учет классов, которые были им загружены. Множество этих классов и назвается областью видимости.
Рассмотрим процесс загрузки более детально. Пусть в систем исполнения встретилась декларация переменной пользовательского класс Student.
1) Системный загрузчик попытается поискать в кеше класс Student.
_1.1) Если класс найден, загрузка окончена.
_1.2) Если класс не найден, загрузка делегируется загрузчику расширений.
2) Загрузчик расширений попытается поискать в кеше класс Student.
_2.1) Если класс найден, загрузка окончена.
_2.2) Если класс не найден, загрузка делегируется базовому загрузчику.
3) Базовый загрузчик попытается поискать в кеше класс Student.
_3.1) Если класс найден, загрузка окончена.
_3.2) Если класс не найден, базовый загрузчик попытается его загрузить.
__3.2.1) Если загрузка прошла успешно, она закончена 😉
__3.2.2) Иначе управление предается загрузчику раширений.
_3.3) Загрузчик расширений пытается загрузить класс.
__3.3.1) Если загрузка прошла успешно, она закончена 😉
__3.3.2) Иначе управление предается системному загрузчику.
_3.4) Системный загрузчик пытается загрузить класс.
__3.4.1) Если загрузка прошла успешно, она закончена 😉
__3.4.2) Иначе генерируется исключение java.lang.ClassNotFoundException.
Если в системе присутствуют пользовательские загрузчики, они должны
а) расширять класс java.lang.ClassLoader;
б) поддерживать модель динамической загрузки.
Inside
Запустим простейшее приложениие с ключем -verbose:class.
public static void main( String args[]) C c = new C();
B b = new B();
A a = new A();
>
>
* This source code was highlighted with Source Code Highlighter .
Вывод показывает, что классы были загружены не в том порядке в котором были использованы. Это обусловлено наследованием.
[Loaded Main from file:/C:/devel/CL/bin/][Loaded A from file:/C:/devel/CL/bin/]
[Loaded B from file:/C:/devel/CL/bin/]
[Loaded C from file:/C:/devel/CL/bin/]
Как вызвать getClass () из статического метода в Java?
у меня есть класс, который должен иметь некоторые статические методы. Внутри этих статических методов, мне нужно вызвать метод getClass (), чтобы сделать следующий вызов:
public static void startMusic()
однако Eclipse говорит мне:
Cannot make a static reference to the non-static method getClass() from the type Object
каков подходящий способ исправить эту ошибку времени компиляции?
8 ответов
просто использовать TheClassName.class вместо getClass() .
Что касается примера кода в вопросе, стандартным решением является ссылка на класс явно по его имени, и даже можно обойтись без getClassLoader() звоните:
этот подход по-прежнему имеет обратную сторону, что он не очень безопасен от ошибок копирования/вставки в случае, если вам нужно реплицировать этот код в ряд аналогичных классов.
и что касается точного вопроса в заголовке, есть трюк, размещенный в рядом нить:
Class currentClass = new Object() < >.getClass().getEnclosingClass();
Он использует вложенный анонимный Object подкласс получить контекст выполнения. Этот трюк имеет преимущество быть безопасным для копирования / вставки.
внимание при использовании этого в базовом классе, который другие классы наследуют от:
также стоит отметить, что если этот фрагмент имеет форму статического метода некоторого базового класса, то currentClass value всегда будет ссылкой на этот базовый класс, а не на любой подкласс, который возможно, использует этот метод.
How to access static resources in Java
Your Java app may need access to data that you intend to ship directly with your code and which will not change without a version update. Examples might include a list of countries to show in a country dropdown, or icon images for a web or Swing application. The proper mechanism to access these files is:
YourClass.class.getResource("file.txt"); // or YourClass.class.getResourceAsStream("file.txt");
This mechanism is built into java and will allow you to store your data files in the same place you store your class files (so, generally, on the classpath, and usually in a jar file). getResource returns a URL instance; many APIs that require a complete resource will accept these. If you wish to read the resource yourself, use getResourceAsStream , which creates an InputStream . (remember to always close it with a try/finally block or @Cleanup !)
Example code
Here’s some example code to read an image icon for Swing:
package example1; import javax.swing.*; import java.net.URL; public class GetResourceExample < public static JLabel createPrintIcon() < URL printIconUrl = GetResourceExample.class.getResource("icons/printer63.png"); ImageIcon printIcon = new ImageIcon(printIconUrl, "Print document"); return new JLabel(printIcon); >public static void main(String. args) < JFrame frame = new JFrame("GetResourceExample"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.add(createPrintIcon()); frame.pack(); frame.setVisible(true); >>
In the above example, take the place GetResourceExample.class is located , and make sure a subdirectory called icons is also located there, which contains the printer63.png file. GetResourceExample.class is in a directory somewhere on your classpath, or in a jar. printer63.png should be in the same place.
Here’s an example to read a list of all states in the US:
package example2; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class PlainJavaStates < private static final Liststates; static < Listout = new ArrayList<>(); try (InputStream in = PlainJavaStates.class.getResourceAsStream("states.txt")) < BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); for (String line = br.readLine(); line != null; line = br.readLine()) < if (line.isEmpty() || line.startsWith("#")) continue; out.add(line); >> catch (IOException e) < // RuntimeException is fine; the 'states' // file not existing is as likely as your States.class // file not existing; your app can crash // in the face of corrupt executables, which is what's happened // if states.txt isn't here. throw new RuntimeException("states.txt cannot be loaded.", e); >states = Collections.unmodifiableList(out); > public static List getStates() < return states; >public static void main(String[] args) < System.out.printf("Number of states: %d%n ", getStates().size()); >>
Or, if you use Guava, this becomes even simpler:
package example2; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; import java.io.IOException; public class GuavaStates < private static final ImmutableListstates; static < try < states = ImmutableList.copyOf( Resources.readLines( GuavaStates.class.getResource("states.txt"), Charsets.UTF_8)); >catch (IOException e) < // RuntimeException is fine; the 'states' // file not existing is as likely as your States.class // file not existing; your app can crash // in the face of corrupt executables, which is what's happened // if states.txt isn't here. throw new RuntimeException("states.txt cannot be loaded.", e); >> public static ImmutableList getStates() < return states; >public static void main(String[] args) < System.out.printf("Number of states: %d%n ", getStates().size()); >>
How do those paths work?
Files are resolved relative to the location of the class file you use as context. In the ‘states’ example, states.txt is supposed to be located in the exact same place in the classpath as PlainJavaStates.class . You can also start this path with a slash to resolve relative to the root of the relevant classpath entry. For example, if your jar file looks like:
/META-INF/MANIFEST.MF /example2/PlainJavaStates.class /example2/states.txt /example2/foo/bar.txt /icons/printer63.png
Then you can get to these files in any of these ways:
PlainJavaStates.class.getResource("states.txt"); PlainJavaStates.class.getResource("foo/bar.txt"); PlainJavaStates.class.getResource("/example2/states.txt"); PlainJavaStates.class.getResource("/example2/foo/bar.txt"); PlainJavaStates.class.getResource("/icons/printer63.png");
Why not files?
You could use FileInputStream to just read files from somewhere, but then you would need to know exactly where your files were. The JVM starts in the ‘working directory’, which may not be the same place your jars or classpath is, and at any rate, this does not work if you ever decide to modularize your application. It is possible to figure out where your jar file is and read from there, but that’s even more effort and easy to do wrong. Why bother?
Why not the other ways to use .getResource / .getResourceAsStream
You may have read about one of these alternate forms:
getClass().getResource(. ); getClass().getResourceAsStream(. );
This is not a good idea; getClass() is dynamic and resolves to the actual class of the instance. If your class is extended by some class that lives in another jar, that means all of a sudden your resources will no longer be found. You may also have read about this alternate form:
ClassLoader.getSystemClassLoader().getResource(. );
getClass().getClassLoader().getResource(. ); MyClass.class.getClassLoader().getResource(. );
Do not use those forms either; the Java specification states that the class loader of certain classes can be null. That would mean a NullPointerException will occur. Also, the above forms are needlessly wordier. Just use this one form, there is no reason to use anything else:
MyClass.class.getResource(. ); MyClass.class.getResourceAsStream(. );
How do I get my resource files into my jars?
Eclipse will automatically copy any non-java files that are located in the same place your source files are, to the binary directory, and thus they’ll be found by your app when running it inside your IDE. Eclipse will also package these files along with your compiled code if you tell Eclipse to make jars.
With Maven or Gradle (or any build system that follows Maven’s source directory structure), put your resources in the src/main/resources directory, using the same path structure as in your src/main/java directory. Your build system will ensure your resources and your class files end up together in the same jar file, and that it’ll work out when you tell your build system to run your application.
With Ant or Ant+Ivy, you have to explicitly add your resources. You can use the task for this, or just list them as an in your task.
Lastly, you could simply put the place that holds your resource files on the classpath when you start the JVM.
If you’re interested in seeing a working example of the project layout, this site has a zipped maven project available.