Страницы

Поиск по вопросам

Показаны сообщения с ярлыком classloader. Показать все сообщения
Показаны сообщения с ярлыком classloader. Показать все сообщения

понедельник, 24 февраля 2020 г.

Когда может понадобиться самому загружать классы в java?

#java #jvm #classloader


Помогите пожалуйста разобраться с динамической загрузкой классов в java. Как я понимаю,
при старте программы загружаются классы из rt.jar, потом загружается главный класс,
а все остальные пользовательские классы загружаются по мере необходимости (например
класс A загружается только когда в программе создается объект класса A).
Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"?
Имеется в виду что в этот момент происходит компиляция класса?
Так же мне интересно, зачем JVM загружает класс, когда используется его статический
метод? Я провел небольшой эксперимент: написал класс, в котором есть один статический
метод и несколько нестатических. 

public class StExmp {
static{
    c = 5;
}
private int a;
public int getA() {
    return a;
}

public void setA(int a) {
    this.a = a;
}

public int getB() {
    return b;
}

public void setB(int b) {
    this.b = b;
}

private int b;
private static int c;

public StExmp(int a, int b){
    this.a = a;
    this.b = b;
    c = 5;
}

public static void show(){
    System.out.println(c);
}
}


Вызываю статический метод в главном классе StExmp.show();, потом запускаю программу
с флагом -verbose:class и вижу что когда программа дошла до вызова этого метода, она
загрузила весь класс. А почему нельзя загружать только статические члены класса? Ведь
получилось что из-за вызова одного маленького метода пришлось загрузить весь класс,
хотя он больше никак и не используется.
И главный вопрос: Когда может потребоваться самому загружать классы? Например с помощью
ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем
какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто
использовать его в программе (например, создать объект) JVM же сама его загрузит. А
когда это может потребоваться делать методом ClassLoader.loadClass()?
    


Ответы

Ответ 1



Ну и топик. Про загрузку классов можно много чего написать. Тема интересная, правда, уже 100 лет теорией не занимался. В контексте треда, пожалуй, отвечу на вопросы конкретные. Если в чём-то не прав, поправляйте, критика приветствуется. Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"? У вас есть скомпилированные байт коды классов, ClassLoader их грузить по мере необходимости. Зачем нам держать в памяти класс, если он не используется? Класс будет загружен только в момент использования. Точно так же, если на класс не осталось никаких ссылок, то ClassLoader может выгрузить класс из памяти при проходе GC. Имеется в виду что в этот момент происходит компиляция класса? Ваши классы уже скомпилированы javac. ClassLoader в память его грузит. Это если в 2-х словах, на самом деле там происходит верификация байт-кода и т.п. Так же мне интересно, зачем JVM загружает класс, когда используется его статический метод? Статически метод, константы - это метаданные класса. Не представляю, как их можно загрузить в отрыве без класс. А почему нельзя загружать только статические члены класса? Ведь получилось что из-за вызова одного маленького метода пришлось загрузить весь класс, хотя он больше никак и не используется. Предположим, есть у вас: public class StExmp { public static void show(){ System.out.println(c); } } Ок, давайте методом рассуждения выведем необходимость грузить класс. Вы хотите, чтобы метод show был загружен без класса. Но ведь в коде вы потом вызываете метод как StExmp.show()? Если класс не загружен, как вы себе представляете вызов метода? Ну хорошо, предположим загрузчик метод добавить в какую-то общую таблицу виртуальных методов. А потом вы создадите класс: public class StExmp2 { public static void show(){ System.out.println(c); } } Метод show загрузчик так же добавит его в общую таблицу статических методов? Проблему уже видите? Как потом при вызове StExmp2.show() понять какой из этих методов вызвать? Когда может потребоваться самому загружать классы? Например с помощью ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто использовать его в программе (например, создать объект) JVM же сама его загрузит. А когда это может потребоваться делать методом ClassLoader.loadClass()? Например, у вас high-load проект. Приложение должно работать непрерывно. Но вам понадобилось заменить реализацию какого-то метода. Не перезапускать же всё приложение? Если оно стетйтлесс, то ещё ладно, но если там в памяти много данных/кэш и т.п.? Можно заменить налету. Когда это надо? Ну, скажем, вы хотите поправить какой-то критический баг, оптимизировали метод и т.п. Тут можно много чего придумать. К примеру у вас игра, в которой есть возможность добавлять кастомных npc. Вы просто пишите новые класс, который в рантайме подтягивается. Или просто неизвестно какой класс будет использоваться в итоге, решение принимаете в рантайме и грузите необходимый класс. Бывает случаи, когда классы хранятся в базе (да-да, бывает такое). Их иначе и не загрузить вовсе.

Ответ 2



Итак. По умолчанию класслоадеров всего три но вам никто не мешает определять свой. Вам никто не мешает грузить классы не из jar или из файлов, а из БД или вообще через HTTP. Каждый новый класслоадер работает в своем неймспейсе, потому они могут загружать классы с одинаковым именем, но с абсолютно разным содержимым. В качестве "простого" примера: Определим интерфейс Meower, чтоб не мучаться с рефлекшном: package pkg; public interface Meower { String meow(); } package pkg; И вот такую вот конструкцию: public class Main { static String CLASS_NAME = "Cat"; static String CLASS_V1 = "package pkg;\n" + "\n" + "public class Cat implements Meower {\n" + " public String meow() {\n" + " return \"Meo..ow\";\n" + " }\n" + "}\n"; static String CLASS_V2 = "package pkg;\n" + "\n" + "public class Cat implements Meower {\n" + " public String meow() {\n" + " return \"Mrr, meo..ow\";\n" + " }\n" + "}\n"; public static void main(String[] args) throws Exception { // Делаем из строки SourceFile SourceFile srcv1 = new SourceFile(CLASS_NAME, CLASS_V1); SourceFile srcv2 = new SourceFile(CLASS_NAME, CLASS_V2); // Компилируем и загружаем ClassLoader clrv1 = new MemoryClassLoader(srcv1); ClassLoader clrv2 = new MemoryClassLoader(srcv2); // Берем наш класс Class clazzv1 = (Class) Class.forName("pkg." + CLASS_NAME, true, clrv1); Class clazzv2 = (Class) Class.forName("pkg." + CLASS_NAME, true, clrv2); // Инстанцируем экземпляры Meower ov1 = clazzv1.newInstance(); Meower ov2 = clazzv2.newInstance(); // Мяукаем System.out.println(ov1.meow()); // Meo..ow System.out.println(ov2.meow()); // Mrr, meo..ow } } Иными словами, немного помучавшись и дописав какую-нибудь Factory я смогу(хоть и не рекомендую этого делать :)) в рантайме, без остановки JVM, брать исходники откуда-нибудь, компилировать их на ходу и менять поведение остальной программы. Также, учитывая сказанное выше, можно придумать еще несколько причин вызвать Class.forName, наиболее распространенный - заранее прогрузить, а не затормозить когда вызовут. Менее распространенный - если вы грузите не с локальной файловой системы и хотите убедиться заранее что класс доступен. ПС: исходники MemoryClassLoader и SourceFile я сюда не выкладываю чтоб не получилась портянка, но они легко гуглятся.

воскресенье, 9 февраля 2020 г.

Почему статический блок инициализации выполнился после конструктора?

#java #инициализация #classloader


Доброго времени суток коллеги!

В литературе пишут, что при загрузке класса в первую очередь выполняется статический
блок, а потом уже динамический блок и конструктор. У меня получилось написать класс
так, что статический блок выполнился в последнюю очередь. 

public class Singleton {
 static Singleton st = new Singleton();
static { //статический блок
    System.out.println("Статический блок");
}

{ //анонимный блок
    System.out.println("Динамический блок");
}

Singleton(){ //конструктор
    System.out.println("Конструктор");

}
public static void main(String[] args) {
   //Пустая точка входа
}
}


OUTPUT:

Динамический блок
Конструктор
Статический блок


Вопрос: Как объяснить наблюдаемое поведение?
    


Ответы

Ответ 1



Давайте по порядку посмотрим: Первым у вас объявлено статическое поле: static Singleton st = new Singleton(); Его нужно проинициализировать, соответственно вызывается конструктор: Singleton() { System.out.println("Конструктор"); } Но перед вызовом конструктора есть динамический блок инициализации, поэтому сперва инициализация объекта происходит в этом блоке, а потом уже выполняется конструктор: { //анонимный блок System.out.println("Динамический блок"); } //затем вызывается непосредственно конструктор Итак, первая строчка класса готова, статическое поле static Singleton st проинициализировано, далее уже инициализируем статический блок. Можете, на основе моего ответа предположить, что будет если в статический блок прописать ещё один вызов конструктора Singleton? Предположите, и можете попробовать затем и сравнить своё предположение. UPD. Нельзя сказать что static блок вызывается перед динамическим блоком. Они вызываются при разных условиях. Static блок вызывается после загрузки класса class loader'oм, а динамический блок вызывается при создании экземпляра класса. То есть static - инициализация класса, dynamic - инициализация экземпляра класса. И динамический блок в вашем конкретном примере вызывается во время инициализиции экземляра Singleton в первой строчке, которая static поле.

Ответ 2



Инициализация статических полей и статические блоки выполняются в порядке их объявления в классе. Поскольку у вас статическое поле static Singleton st = new Singleton(); объявляется первым, то оно инициализируется в первую очередь. При инициализации поля создается объект класса, но выполнение статик блока при создании объекта пропускается, потому что его выполнение начнется только после инициализации объявленного первым статического поля. Соответственно, при инициализации данного поля игнорируется статик блок, выполняется "Динамический блок", затем конструктор класса, поле теперь проинициализировано, и, в соответствии с порядком объявления, выполняется статический блок. http://www.quizful.net/post/java-fields-initialization

пятница, 27 декабря 2019 г.

Как сериализовать/десериализовать объект который содержит другой объект?

#java #serializable #classloader


Может кто-нибудь объяснить, что делать в случае когда при сериализации нам надо передать
не только данные об объекте, но и сам класс этого объекта, чтобы его предварительно
загрузить и потом выполнить десериализацию.
Можно ли это сделать с помощью той же сериализации или это делается отдельно?

Например, класс Foo с Bar объектом внутри.

class Foo implements Serializable {
    Bar bar;
}


Класс Bar

class Bar implements Serializable {
    String s = "test"
}


сначала делаю сериализацию для объекта:

Foo foo = new Foo();
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
writeObject(foo);
flush();
close();


получаю бинарный файл с объектом, а как мне передать и загрузить классы, чтобы можно
было прочитать этот файл?
    


Ответы

Ответ 1



Как то так: import java.io.*; public class Foo implements Serializable { private Bar bar; private Foo() { bar = new Bar(); } public static void main(String[] args) { String filename = "foo.ser"; Foo foo = new Foo(); try (FileOutputStream fos = new FileOutputStream(filename); ObjectOutputStream out = new ObjectOutputStream(fos)) { out.writeObject(foo); } catch (Exception ex) { ex.printStackTrace(); } Bar bar = null; try (FileInputStream fis = new FileInputStream(filename); ObjectInputStream in = new ObjectInputStream(fis)) { bar = ((Foo) in.readObject()).getBar(); } catch (Exception ex) { ex.printStackTrace(); } System.out.println(bar.getText()); } private Bar getBar() { return bar; } } class Bar implements Serializable { private String text; Bar() { text = "test"; } public String getText() { return text; } }

Ответ 2



Сериализация и так всегда сохраняет тип объекта. Так что, если Foo и Bar доступны компилятору читающего кода, всё просто: Object o = oin.readObject(); Foo f = null; Bar b = null; if (o instanceof Foo) f=(Foo) o; if (o instanceof Bar) b=(Bar) o; или if (o.getClass()==Foo.class) f=(Foo) o; Если классы Foo/Bar недоступны читающему коду в принципе, то при чтении будет ClassNotFoundException . Теоретически, сами Foo.class и Bar.class сериализуются, но передать их потом в ObjectInputStream - отдельная история. Например, говорят, что можно переопределить метод ResolveClass: https://community.oracle.com/thread/1151865 , но это не готовое решение.

Ответ 3



Файл будет десереализован в объект типа вашего класса, потом можно с помощью .getClass() получить класс объекта, если он вам действительно нужен Если необходимо просто десереализовать объект то для этого нет необходимости предварительно получать класс будущего объекта если же нужно потом вызвать метод этого объекта, при чем у вас нет ни интерфейса ни класса объекта то стоит посмотреть в сторону Class c = obj.getClass(); Method m = c.getDeclaredMethod("methodName"); m.invoke(obj);

вторник, 17 декабря 2019 г.

Загрузка классов в Java. Написание собственного загрузчика классов

#java #классы #maven #classloader


Я хочу загружать классы из подключенного, собственного, написанного jar-файла, к проекту.

Конкретно где я хочу это применить.
Многомодульное приложение. У меня есть три модуля,к примеру A.war B.war C.jar. Где
A и B являются родительскими модулями для С. Так вот что бы мне исключить модуль С
я хочу загружать из него классы собственным загрузчиком классов.
Как возможно такое реализовать?
    


Ответы

Ответ 1



Как говорили в комментариях, есть класс URLClassLoader. Конструктор принимает в качестве параметра массив URL адресов до jar файлов. URL[] classLoaderUrls = new URL[]{new URL("./somestuf.jar")}; URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls); А вот работать с этим не совсем удобно: Загрузка класса Class beanClass = urlClassLoader.loadClass("ex.coolStuf.Stuf"); Создание объекта класса Constructor constructor = beanClass.getConstructor(); Object beanObj = constructor.newInstance(); Вызов метода Method method = beanClass.getMethod("sayHello"); method.invoke(beanObj);

вторник, 25 июня 2019 г.

SimpleJavaFileObject импорт кастом пакета/классов

Приветствую!
Задача заключается в компиляции динамического класса(созданного из строкового представления) и дальнейшем инстанцировании.
Проблема заключается в импортировании моего пакета/классов, хранящихся в отличных от стандартных java-директорий (lib), в динамический класс. К примеру: после выполнения строки import controller.*; возникает ошибка:
/CompiledClass.java:4: error: package controller does not exist
но стандартные пакеты/классы импортируются без ошибок, и компиляция соответственно выполняется тоже без ошибок.
Пробовал решить через getTask метод JavaCompiler Api путем отправки optionList с параметрами, но в итоге не успешно.
Как правильно импортировать в динамический класс свои пакет/классы?
Мой код:
package view;
import controller.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.*;
import javax.tools.*; import javax.tools.JavaCompiler.*; import javax.tools.JavaFileObject.Kind;
public class CompileJavaManager {
public static void compileJavaFileObject(StringBuilder inputString, Writer jspOut) throws Exception { String className = "CompiledClass"; String classMethod = "methodOf" + className; Class compiledClass = null; //Class c = Class.forName(className);
StringBuilder javaFileContents = new StringBuilder("" + //"package compiled;" + '
' + "import java.io.*;
" + "import java.util.*;
" + "import controller.*;
" + '
' + "public class " + className +"{
" + " private Writer out = null;
" + '
' + " public " + className +"(Writer out){
" + " this.out = out;
" + //" this.container = container;" + " }" + " public void " + classMethod + "() throws java.io.IOException{
" + //inputString + " }
" + "}
");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector diagnosticsCollector = new DiagnosticCollector(); StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null); SpecialClassLoader classLoader = new SpecialClassLoader(); SpecialJavaFileManager fileManager = new SpecialJavaFileManager(standardJavaFileManager, classLoader);
List optionList = new ArrayList(); optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
JavaObjectFromString javaObjectFromString = new JavaObjectFromString(className, javaFileContents.toString());
Iterable fileObjects = Arrays.asList(javaObjectFromString); Iterable classes = null; Writer out = new PrintWriter(jspOut);
CompilationTask task = compiler.getTask(out, fileManager, diagnosticsCollector, optionList, classes, fileObjects); Boolean result = task.call(); List diagnostics = diagnosticsCollector.getDiagnostics();
if (result) { compiledClass = classLoader.findClass(className); Constructor constructor = compiledClass.getConstructor(Writer.class); Object instance = constructor.newInstance(jspOut); //call the method, pass a null params Method instanceMethod = compiledClass.getDeclaredMethod(classMethod, null); instanceMethod.invoke(instance, null); //System.out.println(instance); } else { // Compilation fails for (Diagnostic d : diagnostics) { System.out.println(d); } } }
private static class JavaObjectFromString extends SimpleJavaFileObject { private String sourceCode = null;
public JavaObjectFromString(String className, String sourceCode) throws Exception { super(URI.create("file:///" + className + Kind.SOURCE.extension), Kind.SOURCE); this.sourceCode = sourceCode; }
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return sourceCode; }
@Override public OutputStream openOutputStream() { throw new IllegalStateException(); }
@Override public InputStream openInputStream() { return new ByteArrayInputStream(sourceCode.getBytes()); } }
private static class JavaObjectFromByteCode extends SimpleJavaFileObject { private ByteArrayOutputStream baos;
public JavaObjectFromByteCode(String name) { super(URI.create("byte:///" + name + Kind.CLASS.extension), Kind.CLASS); }
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { throw new IllegalStateException(); }
@Override public OutputStream openOutputStream() { baos = new ByteArrayOutputStream(); return baos; }
@Override public InputStream openInputStream() { throw new IllegalStateException(); }
public byte[] getBytes() { return baos.toByteArray(); } }
private static class SpecialClassLoader extends ClassLoader { private Map m = new HashMap();
protected Class findClass(String name) throws ClassNotFoundException { JavaObjectFromByteCode jobc = m.get(name); if (jobc==null){ jobc = m.get(name.replace(".","/")); if (jobc==null){ return super.findClass(name); } } return defineClass(name, jobc.getBytes(), 0, jobc.getBytes().length); }
public void addClass(String name, JavaObjectFromByteCode jobc) { m.put(name, jobc); } }
private static class SpecialJavaFileManager extends ForwardingJavaFileManager { private SpecialClassLoader scl;
public SpecialJavaFileManager(StandardJavaFileManager sjfm, SpecialClassLoader scl) { super(sjfm); this.scl = scl; }
public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException { JavaObjectFromByteCode jobc = new JavaObjectFromByteCode(name); scl.addClass(name, jobc); return jobc; }
public ClassLoader getClassLoader(Location location) { return scl; } }
}
Заранее спасибо!


Ответ

Во время компиляции ты указал
optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
Это значит, что компилятор во время компиляции видит только jar'ники из стандартной библиотеки. Твой jar'ник с пакетом controller наверняка лежит отдельно. Поэтому надо испльзовать
optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")+";путь/где/лежит/jar/с/пакетом/controller.jar"));

воскресенье, 12 мая 2019 г.

Когда может понадобиться самому загружать классы в java?

Помогите пожалуйста разобраться с динамической загрузкой классов в java. Как я понимаю, при старте программы загружаются классы из rt.jar, потом загружается главный класс, а все остальные пользовательские классы загружаются по мере необходимости (например класс A загружается только когда в программе создается объект класса A). Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"? Имеется в виду что в этот момент происходит компиляция класса? Так же мне интересно, зачем JVM загружает класс, когда используется его статический метод? Я провел небольшой эксперимент: написал класс, в котором есть один статический метод и несколько нестатических.
public class StExmp { static{ c = 5; } private int a; public int getA() { return a; }
public void setA(int a) { this.a = a; }
public int getB() { return b; }
public void setB(int b) { this.b = b; }
private int b; private static int c;
public StExmp(int a, int b){ this.a = a; this.b = b; c = 5; }
public static void show(){ System.out.println(c); } }
Вызываю статический метод в главном классе StExmp.show();, потом запускаю программу с флагом -verbose:class и вижу что когда программа дошла до вызова этого метода, она загрузила весь класс. А почему нельзя загружать только статические члены класса? Ведь получилось что из-за вызова одного маленького метода пришлось загрузить весь класс, хотя он больше никак и не используется. И главный вопрос: Когда может потребоваться самому загружать классы? Например с помощью ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто использовать его в программе (например, создать объект) JVM же сама его загрузит. А когда это может потребоваться делать методом ClassLoader.loadClass()?


Ответ

Ну и топик. Про загрузку классов можно много чего написать. Тема интересная, правда, уже 100 лет теорией не занимался. В контексте треда, пожалуй, отвечу на вопросы конкретные. Если в чём-то не прав, поправляйте, критика приветствуется.
Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"?
У вас есть скомпилированные байт коды классов, ClassLoader их грузить по мере необходимости.
Зачем нам держать в памяти класс, если он не используется? Класс будет загружен только в момент использования. Точно так же, если на класс не осталось никаких ссылок, то ClassLoader может выгрузить класс из памяти при проходе GC.
Имеется в виду что в этот момент происходит компиляция класса?
Ваши классы уже скомпилированы javac. ClassLoader в память его грузит. Это если в 2-х словах, на самом деле там происходит верификация байт-кода и т.п.
Так же мне интересно, зачем JVM загружает класс, когда используется его статический метод?
Статически метод, константы - это метаданные класса. Не представляю, как их можно загрузить в отрыве без класс.
А почему нельзя загружать только статические члены класса? Ведь получилось что из-за вызова одного маленького метода пришлось загрузить весь класс, хотя он больше никак и не используется.
Предположим, есть у вас:
public class StExmp { public static void show(){ System.out.println(c); } }
Ок, давайте методом рассуждения выведем необходимость грузить класс. Вы хотите, чтобы метод show был загружен без класса. Но ведь в коде вы потом вызываете метод как StExmp.show()? Если класс не загружен, как вы себе представляете вызов метода? Ну хорошо, предположим загрузчик метод добавить в какую-то общую таблицу виртуальных методов. А потом вы создадите класс:
public class StExmp2 { public static void show(){ System.out.println(c); } }
Метод show загрузчик так же добавит его в общую таблицу статических методов? Проблему уже видите? Как потом при вызове StExmp2.show() понять какой из этих методов вызвать?
Когда может потребоваться самому загружать классы? Например с помощью ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто использовать его в программе (например, создать объект) JVM же сама его загрузит. А когда это может потребоваться делать методом ClassLoader.loadClass()?
Например, у вас high-load проект. Приложение должно работать непрерывно. Но вам понадобилось заменить реализацию какого-то метода. Не перезапускать же всё приложение? Если оно стетйтлесс, то ещё ладно, но если там в памяти много данных/кэш и т.п.?
Можно заменить налету. Когда это надо? Ну, скажем, вы хотите поправить какой-то критический баг, оптимизировали метод и т.п.
Тут можно много чего придумать. К примеру у вас игра, в которой есть возможность добавлять кастомных npc. Вы просто пишите новые класс, который в рантайме подтягивается. Или просто неизвестно какой класс будет использоваться в итоге, решение принимаете в рантайме и грузите необходимый класс.
Бывает случаи, когда классы хранятся в базе (да-да, бывает такое). Их иначе и не загрузить вовсе.

воскресенье, 14 апреля 2019 г.

Почему статический блок инициализации выполнился после конструктора?

Доброго времени суток коллеги!
В литературе пишут, что при загрузке класса в первую очередь выполняется статический блок, а потом уже динамический блок и конструктор. У меня получилось написать класс так, что статический блок выполнился в последнюю очередь.
public class Singleton { static Singleton st = new Singleton(); static { //статический блок System.out.println("Статический блок"); }
{ //анонимный блок System.out.println("Динамический блок"); }
Singleton(){ //конструктор System.out.println("Конструктор");
} public static void main(String[] args) { //Пустая точка входа } }
OUTPUT:
Динамический блок Конструктор Статический блок
Вопрос: Как объяснить наблюдаемое поведение?


Ответ

Давайте по порядку посмотрим:
Первым у вас объявлено статическое поле:
static Singleton st = new Singleton();
Его нужно проинициализировать, соответственно вызывается конструктор:
Singleton() { System.out.println("Конструктор"); }
Но перед вызовом конструктора есть динамический блок инициализации, поэтому сперва инициализация объекта происходит в этом блоке, а потом уже выполняется конструктор:
{ //анонимный блок System.out.println("Динамический блок"); }
//затем вызывается непосредственно конструктор
Итак, первая строчка класса готова, статическое поле static Singleton st проинициализировано, далее уже инициализируем статический блок. Можете, на основе моего ответа предположить, что будет если в статический блок прописать ещё один вызов конструктора Singleton? Предположите, и можете попробовать затем и сравнить своё предположение.
UPD.
Нельзя сказать что static блок вызывается перед динамическим блоком. Они вызываются при разных условиях. Static блок вызывается после загрузки класса class loader'oм, а динамический блок вызывается при создании экземпляра класса. То есть static - инициализация класса, dynamic - инициализация экземпляра класса. И динамический блок в вашем конкретном примере вызывается во время инициализиции экземляра Singleton в первой строчке, которая static поле.

среда, 5 декабря 2018 г.

Загрузка классов в Java. Написание собственного загрузчика классов

Я хочу загружать классы из подключенного, собственного, написанного jar-файла, к проекту.
Конкретно где я хочу это применить. Многомодульное приложение. У меня есть три модуля,к примеру A.war B.war C.jar. Где A и B являются родительскими модулями для С. Так вот что бы мне исключить модуль С я хочу загружать из него классы собственным загрузчиком классов. Как возможно такое реализовать?


Ответ

Как говорили в комментариях, есть класс URLClassLoader
Конструктор принимает в качестве параметра массив URL адресов до jar файлов.
URL[] classLoaderUrls = new URL[]{new URL("./somestuf.jar")}; URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls);
А вот работать с этим не совсем удобно:
Загрузка класса
Class beanClass = urlClassLoader.loadClass("ex.coolStuf.Stuf");
Создание объекта класса
Constructor constructor = beanClass.getConstructor(); Object beanObj = constructor.newInstance();
Вызов метода
Method method = beanClass.getMethod("sayHello"); method.invoke(beanObj);