是否可以使用Spring的依赖注入将bean注入JPA @Entity ?
@Entity
我尝试@Autowire ServletContext但是,当服务器确实成功启动时,我在尝试访问bean属性时收到了NullPointerException .
@Autowired @Transient ServletContext servletContext;
您可以使用 @Configurable 将依赖项注入不由Spring容器管理的对象,如下所述:http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable .
@Configurable
正如您现在已经意识到的那样,除非使用 @Configurable 和适当的AspectJ编织配置,否则Spring不会将依赖项注入使用 new 运算符创建的对象中 . 事实上,它并没有从 ApplicationContext 中检索到它们,原因很简单,它根本就不知道它们的存在 . 即使您使用 @Component 注释您的实体,该实体的实例化仍将由您或Hibernate等框架执行 new 操作 . 请记住,注释只是元数据:如果没有人解释该元数据,它不会添加任何行为或对正在运行的程序产生任何影响 .
new
ApplicationContext
@Component
话虽如此,我强烈建议不要将 ServletContext 注入实体 . 实体是域模型的一部分,应与任何交付机制(例如基于Servlet的Web交付层)分离 . 当命令行客户端或其他不涉及ServletContext的其他实体访问时,您将如何使用该实体?您应该从该ServletContext中提取必要的数据,并通过传统方法参数将其传递给您的实体 . 通过这种方法,您将获得更好的设计 .
ServletContext
是的,当然可以 . 您只需确保实体也注册为Spring托管bean,使用 <bean> 标签(在某些spring-context.xml中)或通过注释进行声明,如下所示 .
<bean>
使用注释,您可以使用 @Component 标记实体(或更具体的构造型 @Repository ,它可以为DAO启用自动异常转换,可能会也可能不会干扰JPA) .
@Repository
@Entity @Component public class MyJAPEntity { @Autowired @Transient ServletContext servletContext; ... }
一旦为实体完成了这些操作,就需要配置它们的包(或一些祖先包)以供Spring扫描,这样实体就可以作为bean获取,并且它们的依赖关系会自动连接 .
<beans ... xmlns:context="..." > ... <context:component-scan base-package="pkg.of.your.jpa.entities" /> <beans>
EDIT :(终于有效了,为什么)
@Transient private static ServletContext servletContext;
因为,JPA正在创建一个单独的实体实例,即不使用Spring托管bean,所以需要共享上下文 .
init()
@PostConstruct public void init() { log.info("Initializing ServletContext as [" + MyJPAEntity.servletContext + "]"); }
一旦实体被实例化并且通过在内部引用 ServletContext ,它将触发 init() ,如果未注入静态属性,则强制注入静态属性 .
@Autowired
@Autowired public void setServletContext(ServletContext servletContext) { MyJPAEntity.servletContext = servletContext; }
引用我在下面的最后评论来回答为什么我们必须使用这些恶作剧:
由于JPA不使用Spring容器来实例化其实体,因此没有很好的方法可以执行您想要的操作 . 将JPA视为一个单独的ORM容器,它实例化和管理实体的生命周期(完全独立于Spring),并且仅基于实体关系进行DI .
很长一段时间后,我偶然发现了this SO answer,这让我想到了一个优雅的解决方案:
将您需要的所有@Transient @Autowired字段添加到您的实体
使用此自动装配字段创建@Repository DAO: @Autowired private AutowireCapableBeanFactory autowirer;
@Autowired private AutowireCapableBeanFactory autowirer;
从您的DAO中,从DB获取实体后,调用此自动装配代码: String beanName = fetchedEntity.getClass().getSimpleName(); autowirer.autowireBean(fetchedEntity); fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);
String beanName = fetchedEntity.getClass().getSimpleName(); autowirer.autowireBean(fetchedEntity); fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);
然后,您的实体将能够像任何@Component一样访问自动装配的字段 .
3 回答
您可以使用
@Configurable
将依赖项注入不由Spring容器管理的对象,如下所述:http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable .正如您现在已经意识到的那样,除非使用
@Configurable
和适当的AspectJ编织配置,否则Spring不会将依赖项注入使用new
运算符创建的对象中 . 事实上,它并没有从ApplicationContext
中检索到它们,原因很简单,它根本就不知道它们的存在 . 即使您使用@Component
注释您的实体,该实体的实例化仍将由您或Hibernate等框架执行new
操作 . 请记住,注释只是元数据:如果没有人解释该元数据,它不会添加任何行为或对正在运行的程序产生任何影响 .话虽如此,我强烈建议不要将
ServletContext
注入实体 . 实体是域模型的一部分,应与任何交付机制(例如基于Servlet的Web交付层)分离 . 当命令行客户端或其他不涉及ServletContext的其他实体访问时,您将如何使用该实体?您应该从该ServletContext中提取必要的数据,并通过传统方法参数将其传递给您的实体 . 通过这种方法,您将获得更好的设计 .是的,当然可以 . 您只需确保实体也注册为Spring托管bean,使用
<bean>
标签(在某些spring-context.xml中)或通过注释进行声明,如下所示 .使用注释,您可以使用
@Component
标记实体(或更具体的构造型@Repository
,它可以为DAO启用自动异常转换,可能会也可能不会干扰JPA) .一旦为实体完成了这些操作,就需要配置它们的包(或一些祖先包)以供Spring扫描,这样实体就可以作为bean获取,并且它们的依赖关系会自动连接 .
EDIT :(终于有效了,为什么)
ServletContext
静态 . (删除@Autowired)因为,JPA正在创建一个单独的实体实例,即不使用Spring托管bean,所以需要共享上下文 .
init()
方法 .一旦实体被实例化并且通过在内部引用
ServletContext
,它将触发init()
,如果未注入静态属性,则强制注入静态属性 .@Autowired
移动到实例方法,但在其中设置静态字段 .引用我在下面的最后评论来回答为什么我们必须使用这些恶作剧:
很长一段时间后,我偶然发现了this SO answer,这让我想到了一个优雅的解决方案:
将您需要的所有@Transient @Autowired字段添加到您的实体
使用此自动装配字段创建@Repository DAO:
@Autowired private AutowireCapableBeanFactory autowirer;
从您的DAO中,从DB获取实体后,调用此自动装配代码:
String beanName = fetchedEntity.getClass().getSimpleName(); autowirer.autowireBean(fetchedEntity); fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);
然后,您的实体将能够像任何@Component一样访问自动装配的字段 .