Spring Session 設定

在現代的web application中,不管是為了scaling,failover或是其他原因,
我們會需要將application部署在多台機器上,如此一來碰到的首要問題就是session synchronization/replication。
目前cluster session management的方式有限,本篇先介紹Spring Session使用方式。

Verison:
Spring Session: 1.2.2.RELEASE
Spring framework: 4.0.5.RELEASE

已測試過兩種Spring Session integration
1. Spring Session with Redis — Success
2. Spring Session with JDB — Failed(下面會詳述原因)

1. Spring Session with Redis

設定方式其實相當簡單,只有兩個地方要設定:
a. pom.xml
新增dependency

<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
版本自訂
</dependency>

b. applicationContext.xml

<!-- Spring session -->
<context:annotation-config />
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"></bean>
<!-- 由於我們系統使用AWS Redis,config action要關閉 (note 1) -->
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>

c. web.xml

<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- 假設原本沒有使用ContextLoaderListener -->
				<listener>
				<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:${your applicationContext.xml path}
</param-value>
</context-param>

note 1: 會出現以下error,所以會需要加上:

<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

17:39:24,954 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation (“deploy”) failed – address: ([(“deployment” => “cms-cm-su.war”)]) – failure description: {“WFLYCTL0080: Failed services” => {“jboss.undertow.deployment.default-server.default-host./cms-cm-su” => “org.jboss.msc.service.StartException in service jboss.undertow.deployment.default-server.default-host./cms-cm-su: java.lang.RuntimeException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘enableRedisKeyspaceNotificationsInitializer’ defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Unable to configure Redis to keyspace notifications.

參考:http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent

如此一來就大功告成了。

備註:假設你有用Spring Security,以及一些比較特殊的request獲取方式,然後又用了自定的AuthenticationSuccessHandler,例如

@Autowired
private HttpServletRequest request;

或是

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
        .getRequestAttributes()).getRequest();

在AuthenticationSuccessHandler success時,從

@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {

中的request貌似會被security機制替換成新的,並且還沒有commit到redis上,這就會造成兩邊不同步的情況,要特別注意(要以後者提供的request為主)。

2. Spring Session with JDBC

a. c. 與Redis設定相同
b. application.xml

<!-- Embedded JDBC 測試有問題-->
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration">
<property name="springSessionConversionService">
<!-- note 1 -->
</property>
</bean>

<jdbc:embedded-database id="dataSourceForSession" database-name="localSessionDB" type="H2"><!-- note 2 -->
<jdbc:script location="classpath:org/springframework/session/jdbc/schema-h2.sql"/><!-- note 3 -->
</jdbc:embedded-database>

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>

note 1: (note 1的前提在於我不使用上面的jdbc:embedded-database作為datasource,而是使用mysql為一般的datasource時,才可以正常開啟並啟動app)
Conversion Service的問題,在JDBC的Conversion Service中,會出現以下exception:
23:48:33,575 ERROR [io.undertow.request] (default task-2) UT005023: Exception handling request to /cms-cm-su/: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type java.lang.Object to type byte[]

會需要自訂conversion service
範例:

public class CustomConversionService extends GenericConversionService {

public CustomConversionService() {
 addConverter(Object.class, byte[].class, new SerializingConverter());
 addConverter(byte[].class, Object.class, new DeserializingConverter());
 }
}

參考以下回答:http://stackoverflow.com/questions/38125977/no-converter-found-capable-of-converting-from-type-java-lang-object-to-type-byte

ISSUE單:https://github.com/spring-projects/spring-session/issues/556(看來這位老兄沒有reproduce problem 所以單子就一直open在這…)

note 2: 當使用H2 embedded DB時,貌似會跟原本app中的c3p0 datasource有衝突,一啟動就爆炸,跳出一個Timer.class的ClassNotFoundException
所以此處建議直接使用一般的datasource。

note 3:
當你的app啟動時,spring session會使用這段sql去create table,所以當你第二次啟動server時,他就會跳table already exist的exception,相當尷尬。
結論就是直接去執行裡面的sql,然後不要使用教學的方式去設定(拿掉schema-xxx.sql)。

放棄Spring Session with JDBC的原因:
就算我已經成功讓spring session使用一般的datasource設定(連mysql),conversion service還是會有問題,而且所有的resource等等request都會變得非常慢,不曉得是remote db的緣故或是converison service有問題,嚴重懷疑是後者。

note 4: spring session will ignore the

<session-config>
<session-timeout>1440</session-timeout>
</session-config>

configuration in the web.xml

You need need to set this in the context configuration via

<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="86400"></property>
</bean>

Notice the unit of time in web.xml is minutes and context config is seconds.

Ref:

redis offical: http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-redis

jdbc official: http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-jdbc

jdbc stackoverflow: http://stackoverflow.com/questions/38125977/no-converter-found-capable-of-converting-from-type-java-lang-object-to-type-byte

jdbc issue: https://github.com/spring-projects/spring-session/issues/556

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s