Spring Data Redis を Tomcat で JNDI リソース化
前回、Jedis を Tomcat 上で JNDI リソース化しましたが、今回は Spring Data Redis を JNDI リソース化してみます。
実際は org.springframework.data.redis.connection.jedis.JedisConnectionFactory
を JNDI リソース化します。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20151005/
はじめに
今回もカスタムリソースファクトリを作成して対応します。
JedisConnectionFactory
は JavaBean として扱えるため org.apache.naming.factory.BeanFactory
も一応使えるのですが、その場合は JedisShardInfo
をどのように設定するかが課題となります。 (JedisShardInfo が未設定だと JedisConnection
取得時にエラーが発生します)
なお、afterPropertiesSet
メソッドを実行すれば、JedisShardInfo が未設定(null)の場合に JedisShardInfo をインスタンス化して設定してくれます。
カスタムリソースファクトリの作成
JedisConnectionFactory 専用のカスタムファクトリを作っても良かったのですが、今回はもう少し汎用的に org.springframework.beans.factory.InitializingBean
を JNDI リソース化するカスタムファクトリとして作成しました。
基本的な処理内容は 前回 と同じですが、リソースのクラス名を Reference
オブジェクトの getClassName
メソッドで取得しています。
getClassName
の戻り値は context.xml における Resource 要素の type 属性で指定した値です。
カスタムリソースファクトリ (src/main/java/sample/resource/InitializingBeanFactory.java)
package sample.resource; import javax.naming.Context; import javax.naming.Name; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import org.apache.commons.beanutils.BeanUtils; import org.springframework.beans.factory.InitializingBean; public class InitializingBeanFactory implements ObjectFactory { private final List<String> ignoreProperties = Arrays.asList("factory", "auth", "scope", "singleton"); @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return (obj instanceof Reference)? createBean((Reference) obj): null; } private Object createBean(Reference ref) throws Exception { // リソースクラスのインスタンス化 Object bean = loadClass(ref.getClassName()).newInstance(); setProperties(bean, ref); // afterPropertiesSet の実行 ((InitializingBean)bean).afterPropertiesSet(); return bean; } // 各プロパティの設定 private void setProperties(Object bean, Reference ref) throws InvocationTargetException, IllegalAccessException { for (Enumeration<RefAddr> em = ref.getAll(); em.hasMoreElements();) { RefAddr ra = em.nextElement(); String name = ra.getType(); if (!ignoreProperties.contains(name)) { BeanUtils.setProperty(bean, name, ra.getContent()); } } } // リソースクラスのロード private Class<?> loadClass(String className) throws ClassNotFoundException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); return (loader == null)? Class.forName(className): loader.loadClass(className); } }
動作確認
今回は Spring MVC を使って動作確認します。
Tomcat 用のリソース設定は以下の通りです。
type 属性で JNDI リソース化するクラス名 JedisConnectionFactory
を指定します。
JNDI 名は redis/ConnectionFactory
としました。
設定ファイル (src/main/webapp/META-INF/context.xml)
<Context> <Resource name="redis/ConnectionFactory" auth="Container" type="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" factory="sample.resource.InitializingBeanFactory" closeMethod="destroy" port="6380" timeout="500" poolConfig.maxTotal="100" poolConfig.maxIdle="100" /> </Context>
web.xml が不要な WebApplicationInitializer
を使う方式にします。
WebApplicationInitializer 実装クラス (src/main/java/sample/config/WebAppInitializer.java)
package sample.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { // Web アプリケーション設定クラスの指定 return new Class<?>[]{WebConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
Web アプリケーション用の設定クラスへ JNDI を使った JedisConnectionFactory の取得や RedisTemplate をインスタンス化する処理を実装しました。
設定クラス (src/main/java/sample/config/WebConfig.java)
package sample.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.jndi.JndiObjectFactoryBean; import javax.naming.NamingException; @Configuration @ComponentScan("sample.controller") public class WebConfig { @Bean public JedisConnectionFactory jedisConnectionFactory() throws NamingException { JndiObjectFactoryBean factory = new JndiObjectFactoryBean(); // 取得対象の JNDI 名を設定 factory.setJndiName("java:comp/env/redis/ConnectionFactory"); factory.afterPropertiesSet(); // JedisConnectionFactory 取得 return (JedisConnectionFactory)factory.getObject(); } @Bean public StringRedisSerializer stringRedisSerializer() { return new StringRedisSerializer(); } @Bean public RedisTemplate<String, String> redisTemplate() throws NamingException { RedisTemplate<String, String> template = new RedisTemplate<>(); // JNDI で取得した JedisConnectionFactory を設定 template.setConnectionFactory(jedisConnectionFactory()); template.setKeySerializer(stringRedisSerializer()); template.setValueSerializer(stringRedisSerializer()); return template; } }
コントローラークラス (src/main/java/sample/controller/SampleController.java)
package sample.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @Autowired private RedisTemplate<String, String> redisTemplate; @RequestMapping(value = "/app/{key}", method = RequestMethod.GET) @ResponseBody public String sample(@PathVariable String key) { // Redis から値を取得 return redisTemplate.boundValueOps(key).get(); } }
ビルド
Gradle のビルド定義は以下の通りです。
ビルド定義 (build.gradle)
apply plugin: 'war' war.baseName = 'sample' repositories { jcenter() } dependencies { compile 'redis.clients:jedis:2.7.3' compile 'commons-beanutils:commons-beanutils:1.9.2' compile 'org.springframework.data:spring-data-redis:1.6.0.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' providedCompile 'javax.servlet:javax.servlet-api:3.1.0' }
ビルドの実行
> gradle build
ビルド結果の build/libs/sample.war を Tomcat へ配置し、Tomcat と Redis (ポート番号を 6380 へ変更) を実行しておきます。
Redis へデータ設定
Redis へ確認用のデータを設定します。
> redis-cli -p 6380 127.0.0.1:6380> set a1 sample-data1 OK
実行結果
curl でアクセスすると Redis からデータを取得できました。
> curl http://localhost:8080/sample/app/a1 sample-data1