阪本です。
前回に続き、Springでのオブジェクト初期化時の処理をもっと柔軟にしてみます。
今回は、以下のように記述できるようにする方法を説明します。
<bean id="table" class="sample.Table"> <init-method>self.addColumn("userId", "ユーザID")</init-method> <init-method>self.addColumn("userName", "ユーザ名")</init-method> </bean>
では、さっそく行ってみましょう。
XSDファイルの修正
init-methodタグを書けるようにするために、sample.xsdファイルを以下のように修正します。
sample.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <xsd:schema xmlns="http://www.foo.com/schema/sample" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.foo.com/schema/sample"> <xsd:element name="column"> <xsd:complexType> <xsd:attribute name="id" type="xsd:string" use="required" /> <xsd:attribute name="name" type="xsd:string" use="required" /> </xsd:complexType> </xsd:element> <xsd:element name="init-method"> <xsd:complexType mixed="true" /> </xsd:element> </xsd:schema>
ハンドラの修正とデコレータの作成
次に、XMLを解析してオブジェクトを生成するハンドラを修正します。
ここでは、init-methodタグを解析するパーサを登録します。
SampleBeanXmlHandler.java
public class SampleBeanXmlHandler extends NamespaceHandlerSupport { public void init() { // columnタグを解析するオブジェクトを登録する registerBeanDefinitionParser("column", new ColumnBeanDefinitionParser()); // init-methodタグを解析するオブジェクトを登録する registerBeanDefinitionDecorator("init-method", new InitMethodBeanDefinitionDecorator()); } }
また、init-methodタグを解析するデコレータを作成します。
InitMethodBeanDefinitionDecorator.java
public class InitMethodBeanDefinitionDecorator implements BeanDefinitionDecorator { @Override public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definitionHolder, ParserContext parserContext) { // init-methodタグで囲まれた文字列を取得する String initMethodValue = null; if (node instanceof Element) { Element element = (Element) node; Object value = parserContext.getDelegate().parseValueElement(element, null); initMethodValue = ((TypedStringValue) value).getValue(); } // 各設定をコピーする BeanDefinition targetDefinition = definitionHolder.getBeanDefinition(); RootBeanDefinition rootDefinition = new RootBeanDefinition(); rootDefinition.setScope(targetDefinition.getScope()); rootDefinition.setLazyInit(targetDefinition.isLazyInit()); rootDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); rootDefinition.setPrimary(targetDefinition.isPrimary()); // init-methodの処理対象のBean定義を設定する rootDefinition.setDecoratedDefinition(definitionHolder); // 処理対象のBean定義に対してinit-methodの処理を行うクラスを登録する rootDefinition.setBeanClass(InitMethodFactoryBean.class); // InitMethodFactoryBeanオブジェクトに渡す値をセットする rootDefinition.getPropertyValues().add("beanClass", targetDefinition.getBeanClassName()); rootDefinition.getPropertyValues().add("initMethod", initMethodValue); BeanDefinitionHolder holder = new BeanDefinitionHolder(rootDefinition, definitionHolder.getBeanName()); return holder; } }
前回のようにパーサ(ColumnBeanDefinitionParser)ではなく、デコレータ(InitMethodBeanDefinitionDecorator)を作成したのは、定義されたインスタンス(今回の例ではColumnオブジェクト)に対して変更を加えるからです。
init-methodの処理クラスの実装
処理対象のBeanに対して、init-methodで指定した処理を行うInitMethodFactoryBeanクラスは以下になります。
InitMethodFactoryBean
public class InitMethodFactoryBean implements FactoryBean<Object> { private Class<?> beanClass_; private String initMethod_; public void setBeanClass(String beanClassName) throws Exception { this.beanClass_ = Class.forName(beanClassName); } public void setInitMethod(String initMethod) { this.initMethod_ = initMethod; } @Override public Object getObject() throws Exception { // init-methodで指定された処理を行ったオブジェクトを返す Object object = this.beanClass_.newInstance(); Object ognl = Ognl.parseExpression(this.initMethod_); object = Ognl.getValue(ognl, Collections.singletonMap("self", object)); return object; } @Override public Class<?> getObjectType() { return this.beanClass_; } @Override public boolean isSingleton() { return false; } }
OGNL式でinit-methodの値を評価しています。
getObject() の中でオブジェクトを初期化しているので、OGNL式の評価に必要なキーワード(上記であればself)を追加することもできます。
これで、
<bean id="table" class="sample.Table"> <sample:init-method>self.addColumn("userId", "ユーザID")</sample:init-method> <sample:init-method>self.addColumn("userName", "ユーザ名")</sample:init-method> </bean>
と書くことにより、Tableクラスのインスタンス生成時に、TableクラスのaddColumnメソッドが2回呼ばれるようになります。
一見、「やりたかったことができた!」と思えそうなのですが、この方法には欠点があります。それは・・・
インスタンスにプロパティをセットする(beanタグを使用する例)前に、init-methodが実行される
こと!
プロパティの値がセットされてからinit-methodを実行するべきですので、これについてはまた次回に。。
では。