Spring+HibernateEntityManager(@Required編)
DIし忘れに注意
実行時に、いきなり NullPointerException 喰らって、それが単なる bean への DI し忘れだとヘコみますよね。
そういうときには、@Required アノテーションによって、コンテキスト起動時に DI の抜けを確認することができます。
ぬるぽの例
前回の beans.xml の userService bean の設定から、あえて entityManager への DI を外してプログラムを動作させると、以下のようなエラーが発生します。
Exception in thread "main" java.lang.NullPointerException at domain.service.UserServiceImpl.registerUser(UserServiceImpl.java:22) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at $Proxy12.registerUser(Unknown Source) at application.Main.main(Main.java:37)
これは、実際に entityManager を使用したときに発生した例外なので、ApplicationContext を起動しただけでは発生しません。
ということで、事前に抜けを検出できるようにすることにします。
bean の定義ファイルに
<context:annotation-config/>
を指定すると、コンテキスト設定に関するいくつかのアノテーションが使用可能になります。
その際、ネームスペース情報のため、beans 要素の属性に設定が必要になります。
- xmlns:context 属性
- xsi:schemaLocation 属性 :
※最後に beans.xml 全体のソースを載せておきます。
ネームスペースに関する設定を忘れると、以下のように、XML のパース時に怒られてしまうので、ご注意を、、、。
Exception in thread "main" org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 14 in XML document from class path resource [beans.xml] is invalid; nested exception is org.xml.sax.SAXParseException: The prefix "context" for element "context:annotation-config" is not bound. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:404) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:342) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:113) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:80) at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:123) at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:423) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:353) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at application.Main.main(Main.java:27) Caused by: org.xml.sax.SAXParseException: The prefix "context" for element "context:annotation-config" is not bound. at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195) at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:174) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:388) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318) at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:310) at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2740) at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:647) at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140) at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:508) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737) at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107) at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:225) at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:283) at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:75) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:396) ... 14 more
@Required アノテーション
DI を保証したい setter メソッドに、@Required アノテーションを追加します。
以下例
@Required public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; }
BeanInitializationException の例
@Required アノテーションを指定する前は、DI されずに null となっていた変数にアクセスした結果 NullPointerException が発生していましたが、@Required アノテーションを設定した後は、ApplicationContext 作成時に、BeanInitializationException が発生するようになります。
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in class path resource [beans.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'entityManager' is required for bean 'userService' at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:478) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409) at java.security.AccessController.doPrivileged(Native Method) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:220) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:729) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:381) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at application.Main.main(Main.java:27) Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'entityManager' is required for bean 'userService' at org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.postProcessPropertyValues(RequiredAnnotationBeanPostProcessor.java:121) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:996) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:470) ... 14 more
こうすることにより、DI し忘れを早期に発見することができるようになります。
設計的な視点(おいらの意見)
おいら的には、ドメインモデルに含まれるクラスの実装ソースに @Required を指定するという方法は、悪くないアイデアだと思います。
理由は、ドメインモデル中の各モデルは、アプリケーション層により、アプリケーションとして組み立てられることを前提に設計されているからです。そのため、ドメインモデルとしての責務を果たすために必要なリソースを、ドメインモデル側からアプリケーション層に要求するというのは自然なのかなと思います。
変更したソース
application/src/main/resources/beans.xml
contextネームスペース用の設定と、
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd "> <!-- for context configuration by annotations --> <context:annotation-config/> <!-- for @Transactional annotations --> <tx:annotation-driven transaction-manager="txManager"/> <!-- Actual DataSource --> <bean id="rawDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="" /> </bean> <!-- LazyConnectionDataSourceProxy --> <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> <property name="targetDataSource" ref="rawDataSource" /> </bean> <!-- Transaction Manager --> <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <!-- JpaVendorAdapter --> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> <!-- EntityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="persistence.xml" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> <property name="dataSource" ref="dataSource" /> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> </props> </property> </bean> <!-- EntityManager --> <bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <!-- Entity --> <bean id="user" class="domain.entity.UserImpl" scope="prototype" /> <!-- Service --> <bean id="userService" class="domain.service.UserServiceImpl"> <lookup-method name="newUser" bean="user"/> <property name="entityManager" ref="entityManager"/> </bean> </beans>
infrastracture-impl/src/main/java/infrastracture/ServiceBase.java
@Required アノテーションを追加しました。
package infrastracture; import javax.persistence.EntityManager; import org.springframework.beans.factory.annotation.Required; /** * Service のベースクラスです。現段階では、このサンプルは DAO 等の仕組みを持たず、Service 実装内で直接 EntityManager * を使用するため、 EntityManager を DI されるようになっています。 * * @author beyondseeker * @version $Id$ */ public class ServiceBase { /** * EntityManager */ private EntityManager entityManager; /** * EntityManager をセットします。 * * @param entityManager * EntityManager */ @Required public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } /** * EntityManager を返します。 * * @return EntityManager */ public EntityManager getEntityManager() { return entityManager; } }
メモ
links
第1回 Spring+HibernateEntityManager(HibernateEntityManager単体編)
第2回 Spring+HibernateEntityManager(とりあえずSpring編)
第3回 Spring+HibernateEntityManager(宣言的トランザクション編)
第4回 Spring+HibernateEntityManager(Spring+DDDっぽく編)
第5回 Spring+HibernateEntityManager(Spring+DDDっぽく編 その2)
第6回 Spring+HibernateEntityManager(Spring+DDDっぽく編 その3)
第7回 Spring+HibernateEntityManager(Spring+DDDっぽく編 その4)
第8回 Spring+HibernateEntityManager(@Transactionalアノテーション編)
第9回 Spring+HibernateEntityManager(@Required編)
第10回 Spring+HibernateEntityManager(XMLからの外部リソース参照編)
第11回 Spring+HibernateEntityManager(AspectJ AOP with Load-Time-Weaver編)
第12回 Spring+HibernateEntityManager(DBCPのvalidationQuery編)
第13回 Spring+HibernateEntityManager(@Resource編)
第14回 Spring+HibernateEntityManager(コンポーネント自動検出 with アノテーション編)
第15回 Spring+HibernateEntityManager(コンポーネント自動検出 without アノテーション編)