Spring: Жизненный цикл бинов, методы init() и destroy()
Всем привет! Я начинающий Java-разработчик. В рамках развития своей карьеры я решил сделать две вещи:
- Завести канал в ТГ, где собираюсь рассказывать о себе, своем пути и проблемах, с которыми сталкиваюсь — https://t.me/java_wine
- Завести блог на Хабре, куда буду выкладывать переводы материалов, которые использую для своего обучения и развития.
Надеюсь, буду полезен сообществу и новичкам, которые пойдут по моим или чьим-то еще стопам.
Жизненный цикл любого объекта означает следующее: как и когда он появляется, как он себя ведет во время жизни и как и когда он исчезает. Жизненный цикл бина ровно про это же: «как и когда».
Жизненным циклом управляет спринг-контейнер. В первую очередь после запуска приложения запускается именно он. После этого контейнер по необходимости и в соответствии с запросами создает экземпляры бинов и внедряет необходимые зависимости. И, наконец, бины, связанные с контейнером, уничтожаются когда контейнер завершает свою работу. Поэтому, если мы хотим выполнить какой-то код во время инстанцирования бина или сразу после завершения работы контейнера, один из самых доступных нам вариантов это вынести его в специальные init() и destroy() методы.
На картинке условно это можно изобразить следующим образом
Спринг предоставляет несколько способ настроить выполнение этих методов. Для того чтобы понять и разобраться в них, давайте посмотрим на простой пример.
Попробуем создать бин HelloWorld и вызвать у него методы init() и destroy(). Сделаем это тремя разными способами:
XML
Сначала сделаем простой класс:
public class HelloWorld < public void init() throws Exception < System.out.println("Я инит-метод " + this.getClass().getSimpleName()); >public void destroy() throws Exception < System.out.println("Я дестрой-метод " + this.getClass().getSimpleName()); >>
Чтобы использовать пользовательские методы init() и destroy() нам необходимо зарегистрировать их в Spring XML конфигурационном файле при описании бина:
Также потребуется класс-runner, который и запустит наш контекст:
Обратите внимание, что в нём указан адрес файла конфигурации бинов.
Java-код («программный метод»)
Для того чтобы это реализовать необходимо в классе бина HelloWorldByJavaCode имплементировать два интерфейса, InitializingBean и DisposableBean, а затем переопределить методы afterPropertiesSet() и destroy().
Метод afterPropertiesSet() вызывается при запуске спринг-конейтнера и инстанцировании бина, а метод destroy() сразу после того как контейнер завершит свою работу.
Чтобы вызвать метод destroy() нам необходимо явно закрыть спринг-контекст, вызвав метод close() у объекта ConfigurableApplicationContext.
public class HelloWorldByJavaCode implements InitializingBean, DisposableBean < @Override public void destroy() throws Exception < System.out.println("Я дестрой-метод " + this.getClass().getSimpleName()); >@Override public void afterPropertiesSet() throws Exception < System.out.println("Я инит-метод " + this.getClass().getSimpleName()); >>
В XML регистрация бина будет выглядеть так:
Класс-runner остается прежним
С помощью аннотаций
Чтобы вызывать методы init() и destroy() нам необходимо указать над методами соответствующие аннотации — @PostConstruct и @PreDestroy.
Чтобы вызвать метод destroy() нам необходимо явно закрыть спринг-контекст, вызвав метод close() у объекта ConfigurableApplicationContext. Класс-runner остается прежним
Cоздадим бин HelloWorldByAnnotations.java и аннотируем соответствующие методы:
public class HelloWorldByAnnotations < @PostConstruct public void init() throws Exception < System.out.println("Я инит-метод " + this.getClass().getSimpleName()); >@PreDestroy public void destroy() throws Exception < System.out.println("Я дестрой-метод " + this.getClass().getSimpleName()); >>
В XML-файле появится дополнительная строчка, отвечающая за бин, читающий аннотации:
Размещу еще одну схему жизненного цикла, более полную. Взял её уже с другого ресурса. Мне кажется для понимания и подготовки к собеседованиям полезно.
Методы и в JVM
JVM использует два различных метода для инициализации экземпляров объектов и классов.
В этой быстрой статье мы увидим, как компилятор и среда выполнения используют методы и для целей инициализации.
2. Методы инициализации экземпляра
Начнем с простого выделения и назначения объектов:
Если мы скомпилируем этот фрагмент и посмотрим на его байт-код через javap -c , мы увидим что-то вроде:
0: new #2 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."":()V 7: astore_1
Для инициализации объекта JVM вызывает специальный метод с именем . На жаргоне JVM этот метод является методом инициализации экземпляра . Метод является инициализацией экземпляра тогда и только тогда, когда:
Каждый класс может иметь ноль или более методов инициализации экземпляра . Эти методы обычно соответствуют конструкторам в языках программирования на основе JVM, таких как Java или Kotlin.
2.1. Конструкторы и блоки инициализатора экземпляра
Чтобы лучше понять, как компилятор Java переводит конструкторы в , давайте рассмотрим другой пример:
public class Person private String firstName = "Foo"; // private String lastName = "Bar"; // // System.out.println("Initializing. "); > // public Person(String firstName, String lastName) this.firstName = firstName; this.lastName = lastName; > // public Person() > >
Это байт-код для этого класса:
public Person(java.lang.String, java.lang.String); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: ldc #7 // String Foo 7: putfield #9 // Field firstName:Ljava/lang/String; 10: aload_0 11: ldc #15 // String Bar 13: putfield #17 // Field lastName:Ljava/lang/String; 16: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 19: ldc #26 // String Initializing. 21: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 24: aload_0 25: aload_1 26: putfield #9 // Field firstName:Ljava/lang/String; 29: aload_0 30: aload_2 31: putfield #17 // Field lastName:Ljava/lang/String; 34: return
Несмотря на то, что конструктор и блоки инициализатора в Java разделены, они находятся в одном и том же методе инициализации экземпляра на уровне байт-кода. Собственно говоря, этот метод :
- Сначала инициализируются поля firstName и lastName (индекс от 0 до 13) .
- Затем он выводит что-то на консоль как часть блока инициализатора экземпляра (индексы с 16 по 21).
- И, наконец, он обновляет переменные экземпляра аргументами конструктора.
Если мы создадим Person следующим образом:
Person person = new Person("Brian", "Goetz");
Затем это переводится в следующий байт-код:
0: new #7 // class Person 3: dup 4: ldc #9 // String Brian 6: ldc #11 // String Goetz 8: invokespecial #13 // Method Person."":(Ljava/lang/String;Ljava/lang/String;)V 11: astore_1
На этот раз JVM вызывает другой метод с сигнатурой, соответствующей конструктору Java.
Ключевым моментом здесь является то, что конструкторы и другие инициализаторы экземпляров эквивалентны методу в мире JVM.
3. Методы инициализации класса
В Java статические блоки инициализатора полезны, когда мы собираемся инициализировать что-то на уровне класса:
public class Person private static final Logger LOGGER = LoggerFactory.getLogger(Person.class); // // static System.out.println("Static Initializing. "); > // omitted >
Когда мы компилируем предыдущий код, компилятор переводит статический блок в метод инициализации класса на уровне байт-кода.
Проще говоря, метод является инициализирующим класс тогда и только тогда, когда:
Поэтому единственный способ сгенерировать метод в Java — это использовать статические поля и инициализаторы статических блоков.
JVM вызывает при первом использовании соответствующего класса. Поэтому вызов происходит во время выполнения, и мы не можем увидеть вызов на уровне байт-кода.
4. Вывод
В этой быстрой статье мы увидели разницу между методами и в JVM. Метод используется для инициализации экземпляров объектов. Кроме того, JVM при необходимости вызывает метод для инициализации класса.
Чтобы лучше понять, как работает инициализация в JVM, настоятельно рекомендуется прочитать спецификацию JVM .
init Method in Java
In Java, the init method is a special method that can be defined in classes to perform initialization tasks. The init method is not specific to a particular interface or class like in the case of servlets, but rather it can be defined in any class as a regular method. The init method in Java is typically used to initialize the state of an object or perform setup tasks before the object can be used. It is commonly invoked after an object is created or instantiated.
The init method is called by the servlet container when the servlet is first loaded or initialized. It is invoked only once during the lifecycle of a servlet. The purpose of the init method is to perform any necessary setup or initialization tasks before the servlet can handle requests.
In this above program, we have a Student class with two private member variables: name and roll number. The class has a default constructor, which automatically calls the init method. After calling the init method it will be going to display student information.
FileName: Student.java
public class Student < private String name; private int rollNo; private String branch; public Student () < init(); // Call the init() method to initialize the Student object >private void init() < name = "Sakshi Pawar"; // Set the default name to "Sakshi Pawar" rollNo = 25; // Set the default Roll to 25 branch= "CSE"; //set the branch as CSE System.out.println("Student initialized: " + name + ", " + rollNo + ", " + branch); // Print a message indicating the person has been initialized >public void displayInfo() < System.out.println("Name: " + name); // Print the name of Student System.out.println("Roll No: " + rollNo); // Print the Roll number of student System.out.println("Branch: " + branch); // Print the Branch of student >public static void main(String[] args) < Student student = new Student (); // Create a new Student object student.displayInfo(); // Display the Student’s information >>
Student initialized: Sakshi Pawar, 25, CSE Name: Sakshi Pawar Roll No: 25 Branch: CSE
In the above program, we have a Car class with a default constructor and an init method that takes make, model, and year as parameters. The main method creates an instance of the Car class, initializes it using the init() method, and then prints the details using the printDetails() method.
FileName: Car.java
public class Car < private String make; private String model; private int year; public Car() < // Default constructor >public void init(String make, String model, int year) < this.make = make; this.model = model; this.year = year; >public void printDetails() < System.out.println("Make: " + make); // prints the maker of the car System.out.println("Model: " + model); // prints the model of the car System.out.println("Year: " + year); // prints the launching year of the car >public static void main(String[] args) < // Create a new instance of the Car class Car car = new Car(); // Initialize the car object using the init method car.init("Mahindra & Mahindra", "Thar SUV", 2023); // Print the details of the car car.printDetails(); >>
Make: Mahindra & Mahindra Model: Thar SUV Year: 2023
In the above program, I have added a JButton and a JTextField to the panel object. The button is created with the label «Click Me» and added to the panel using the add() method. Similarly, the text field is also added to the panel. we can further customize and add additional components as per our requirements within the init() method.
FileName: MyFrame.java
import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; public class MyFrame extends JFrame < private JPanel panel; private JButton button; private JTextField textField; public MyFrame() < init(); >private void init() < // Set up the frame setTitle("My Swing Application"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create a panel panel = new JPanel(); // Create a button with label click me button = new JButton("Click Me"); panel.add(button); // Create a text field with size 20 textField = new JTextField(20); panel.add(textField); // Add the panel to the frame add(panel); // Pack the frame and make it visible to the end user pack(); setVisible(true); >public static void main(String[] args) < // main method // Create and display the frame MyFrame frame = new MyFrame(); >>