Bean инъекция внутри JPA @Entity
Можно ли вставить beans в JPA @Entity
с помощью инъекции зависимостей Spring?
Я попытался @Autowire ServletContext, но, когда сервер действительно начал успешно, я получил исключение NullPointerException при попытке получить доступ к свойству bean.
@Autowired
@Transient
ServletContext servletContext;
Ответы
Ответ 1
Вы можете вставлять зависимости в объекты, не управляемые контейнером Spring, используя @Configurable
, как описано здесь: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable.
Как вы уже поняли, если не использовать @Configurable
и соответствующую конфигурацию ткачества AspectJ, Spring не встраивает зависимости в объекты, созданные с помощью оператора new
. Фактически, он не вводит зависимости в объекты, если вы не извлекли их из ApplicationContext
по той простой причине, что он просто не знает об их существовании. Даже если вы аннотируете свою сущность с помощью @Component
, экземпляр этого объекта все равно будет выполняться с помощью операции new
, либо вами, либо средой, такой как Hibernate. Помните, что аннотации - это просто метаданные: если никто не интерпретирует эти метаданные, он не добавляет никакого поведения или не влияет на запущенную программу.
Все сказанное я настоятельно рекомендую не вводить a ServletContext
в сущность. Объекты являются частью вашей модели домена и должны быть отделены от любого механизма доставки, такого как уровень веб-доставки на основе сервлета. Как вы будете использовать этот объект, когда он будет доступен клиенту командной строки или что-то еще, не связанное с ServletContext? Вы должны извлечь необходимые данные из этого ServletContext и передать его с помощью традиционных аргументов метода для своей сущности. Благодаря этому подходу вы получите гораздо лучший дизайн.
Ответ 2
Да, конечно, вы можете. Вам просто нужно убедиться, что сущность также зарегистрирована как Spring управляемая bean декларативно, используя теги <bean>
(в некотором spring -context.xml) или аннотации, как показано ниже.
Используя аннотации, вы можете либо пометить свои объекты с помощью @Component
(или более определенного стереотипа @Repository
, который позволяет автоматически переводить исключения для DAO и может или не может мешать JPA).
@Entity
@Component
public class MyJAPEntity {
@Autowired
@Transient
ServletContext servletContext;
...
}
После того, как вы сделали это для своих объектов, вам нужно настроить свой пакет (или некоторый пакет предков) для проверки на Spring, чтобы объекты получались как beans, и их зависимости получались автоматически.
<beans ... xmlns:context="..." >
...
<context:component-scan base-package="pkg.of.your.jpa.entities" />
<beans>
EDIT: (что, наконец, сработало и почему)
Поскольку JPA создает отдельный экземпляр объекта, т.е. не использует Spring bean Spring, он должен использоваться для совместного использования контекста.
Это срабатывает init()
после того, как Entity был создан, и, ссылаясь на ServletContext
внутри, он принудительно вставляет статическое свойство, если оно уже не было введено.
-
Перемещение @Autowired
в метод экземпляра, но установка статического поля внутри.
@Autowired
public void setServletContext(ServletContext servletContext) {
MyJPAEntity.servletContext = servletContext;
}
Цитата моего последнего комментария ниже, чтобы ответить, почему мы должны использовать эти махинации:
Там нет красивого способа делать то, что вам нужно, поскольку JPA не использует контейнер Spring для создания экземпляров своих объектов. Подумайте о JPA как отдельном контейнере ORM, который создает и управляет жизненным циклом сущностей (полностью отделяется от Spring) и делает DI только на основе связей с объектами.
Ответ 3
Спустя долгое время я наткнулся на этот SO ответ, который заставил меня подумать об элегантном решении:
- Добавьте в свои сущности все поля @Transient @Autowired, которые вам нужны.
- Создайте @Repository DAO с этим полем для автоподключения:
@Autowired private AutowireCapableBeanFactory autowirer;
- Из вашего DAO, после извлечения сущности из БД, вызовите этот код автоподключения:
String beanName = fetchedEntity.getClass().getSimpleName();
autowirer.autowireBean(fetchedEntity);
fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);
Затем ваша сущность сможет получить доступ к полям с автоподстановкой, как может любой @Component.