Статьи

Spring — доступ к внедренным свойствам из конструктора

В Spring экземпляр bean-объекта создается перед тем, как вводятся его свойства. Это:

  1. Сначала создайте экземпляр компонента
  2. Введите свойства

Это связано с тем, что Spring использует методы установки экземпляра bean-компонента для добавления свойств. (Это верно, если внедрение свойства объявлено в файле конфигурации Spring, однако, если свойства bean-компонента имеют автоматическую привязку — методы установки не требуются. Но Spring все еще создает экземпляр bean-компонента перед автоматическим подключением.) Поэтому вы не можете получить доступ свойства из вашего конструктора (если только свойства не вводятся через аргументы конструктора), так как свойства по-прежнему содержат нулевые значения / значения по умолчанию при доступе в конструкторе.

Рассмотрим следующий пример.


Конфигурационный файл Spring

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="engine" class="me.fahimfarook.sample.spring.Engine">
  <property name="make"  value="Toyota" />
  <property name="cylinders" value="6" />
 </bean>
 
 <bean id="car" name="car" class="me.fahimfarook.sample.spring.Car" >
  <property name="brand" value="Micro" />
  <property name="year" value="2005" />
  <property name="engine" ref="engine" />
 </bean>
</beans>


Engine.java

package me.fahimfarook.sample.spring;

import java.util.logging.Logger;

public class Engine {
 
 private String make;
 
 private int cylinders;
 
 private static Logger LOGGER = Logger.getLogger(Engine.class.getName());

 public void cleanup() {
  LOGGER.info("Engine::cleanup - cleaning-up engine.");
 }

 public String getMake() {
  return make;
 }

 public void setMake(final String make) {
  this.make = make;
 }

 public int getCylinders() {
  return cylinders;
 }

 public void setCylinders(final int cylinders) {
  this.cylinders = cylinders;
 }

}


Car.java

package me.fahimfarook.sample.spring;


public class Car {
 
 private String brand;
 
 private int year;
 
 private Engine engine;
 
 public Car() {
  this.engine.cleanup();
 }
 
 public String getBrand() {
  return brand;
 }

 public void setBrand(final String brand) {
  this.brand = brand;
 }

 public int getYear() {
  return year;
 }

 public void setYear(final int year) {
  this.year = year;
 }

 public Engine getEngine() {
  return engine;
 }

 public void setEngine(final Engine engine) {
  this.engine = engine;
 }
}

Как вы можете видеть, я вызываю engine.start () из конструктора Car.


Main.java

package me.fahimfarook.sample.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
 
 private static final String CONFIG_PATH = "classpath*:configs/spring/applicaton-config.xml";
 
 public static void main(String[] args) {
  
  final ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_PATH);
  final Car car = (Car)context.getBean("car");
 }

}


При выполнении этого основного метода вы получите исключение NullPointerException.

Caused by: java.lang.NullPointerException
 at me.fahimfarook.sample.spring.Car.start(Car.java:18)
 at me.fahimfarook.sample.spring.Car.<init>(Car.java:14)

Несмотря на то, что мы настроили пружину для впрыска двигателя в автомобиль, к моменту создания экземпляра (автомобильного компонента) пружина еще не впрыскивала двигатель.

У нас есть два обходных пути для решения этой проблемы.

1. С точки зрения дизайна это исключение NullPointerException указывает на запах кода. То есть отношения между автомобилем и двигателем должны быть «составными», а не «агрегатными». Двигатель должен перестать существовать, когда автомобиль разрушен. Кроме того, двигатель не может существовать без автомобиля. Чтобы наложить композицию между автомобилем и двигателем, нам нужны следующие 2 изменения.

  • Добавить конструктор в Car с параметром Engine 
  • Удалите мутатор setEngine () 

Вот фиксированный код.

Spring config — Исправлено

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="car" name="car" class="me.fahimfarook.sample.spring.Car" >
  <constructor-arg name="engine" ref="engine" />
  <property name="brand" value="Micro" />
  <property name="year" value="2005" />
 </bean>
 
 <bean id="engine" class="me.fahimfarook.sample.spring.Engine">
  <property name="make"  value="Toyota" />
  <property name="cylinders" value="6" />
 </bean>
 
</beans>

Обратите внимание, что engine вводится через конструктор-аргумент и <propertyname = «engine» ref = «engine» /> удаляется. 


Car.java — Исправлено

package me.fahimfarook.sample.spring;

public class Car {

 private String brand;

 private int year;

 private Engine engine;

 public Car(final Engine engine) {
  this.engine = engine;
  this.engine.start();
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(final String brand) {
  this.brand = brand;
 }

 public int getYear() {
  return year;
 }

 public void setYear(final int year) {
  this.year = year;
 }

 public Engine getEngine() {
  return engine;
 }

// public void setEngine(final Engine engine) {
//  this.engine = engine;
// }
}


Обратите внимание, что установщик двигателя был закомментирован, и у Car есть конструктор с параметром Engine. Теперь, если вы выполните метод main, вы больше не получите исключение NullPointerException, так как мы заставили пружину впрыснуть двигатель при конструировании автомобиля.

2. Другим обходным решением будет перемещение логики в конструкторе в отдельный метод и аннотирование этого метода аннотацией @PostConstruct. @PostConstruct — это стандартная аннотация Java, которая поддерживается Spring. @PostConstruct указывает, что метод, аннотированный @PostConstruct, должен вызываться только после внедрения всех его зависимостей. Однако в нашем примере вам нужно включить <context: annotation-driven /> для поддержки аннотаций. Вы можете найти больше информации об этой аннотации  здесь .