-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added auto-configuration for Spring Boot #103
Added auto-configuration for Spring Boot #103
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regardless of the PR, does it make sense to use instance name from configuration to seek for existing instances? If I'm not wrong, this will throw NPE when an ordinary hazelcast.xml with instance-name field is used whereas it's supposed to be a valid configuration to start a standalone instance:
Lines 57 to 58 in 3e86d43
} else if (instanceName != null) { | |
instance = Hazelcast.getHazelcastInstanceByName(instanceName); |
and then
Line 92 in 3e86d43
sessionMap = instance.getMap(mapName); |
...at9/src/main/java/com/hazelcast/session/springboot/HazelcastSessionManagerConfiguration.java
Outdated
Show resolved
Hide resolved
@enozcan yes, you are correct. This is a known issue, and actually, it requires much larger refactoring. Maybe we can create a GH issue for tracking, but I am pretty sure that it needs a larger effort to fix it in general. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added my comments.
Also, I tried this autoconfiguration with the latest Spring Boot Web and got the following exception. Could you check?
15:38:06.275 [main] ERROR o.s.boot.SpringApplication - Application run failed
java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:60)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:489)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:478)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:140)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:348)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:252)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:285)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:99)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:751)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:569)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298)
at com.example.demo.DemoApplication.main(DemoApplication.java:10)
Caused by: java.lang.IllegalStateException: Failed to introspect Class [com.hazelcast.session.springboot.HazelcastSessionManagerConfiguration] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@3764951d]
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481)
at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:358)
at org.springframework.util.ReflectionUtils.getUniqueDeclaredMethods(ReflectionUtils.java:414)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.lambda$getTypeForFactoryMethod$2(AbstractAutowireCapableBeanFactory.java:754)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:753)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:692)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:663)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1665)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:570)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:542)
at org.springframework.boot.autoconfigure.condition.OnBeanCondition.collectBeanNamesForType(OnBeanCondition.java:238)
at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanNamesForType(OnBeanCondition.java:231)
at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanNamesForType(OnBeanCondition.java:221)
at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchingBeans(OnBeanCondition.java:169)
at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:119)
at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47)
... 19 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/hazelcast/config/Config
at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3325)
at java.base/java.lang.Class.getDeclaredMethods(Class.java:2466)
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463)
... 35 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.hazelcast.config.Config
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 39 common frames omitted
@@ -28,16 +28,16 @@ | |||
|
|||
import static com.hazelcast.internal.config.ConfigLoader.locateConfig; | |||
|
|||
class ClientServerConfigLoader { | |||
public class ClientServerConfigLoader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idea: maybe we could get rid of the com.hazelcast.session.springboot
package and keep these package-private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, but I think it will be better to keep springboot
package since there are also test classes under the same package. Otherwise, all classes will be in the same parent package. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it possible to put tests in a separate package, but keep the code together? Like we already have separate packages for different tests: nonsticky
, sticky
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, surely we can do it. I just thought it is tidier :) I removed the springboot
package now 👍
README.md
Outdated
|
||
Starting with v2.2, Hazelcast Tomcat Session Manager supports auto-configuration when used with Spring Boot. The only thing you need to do is to add Hazelcast Tomcat Session Manager (for Tomcat 9) library to the classpath. This will set Hazelcast Session Manager as the session manager of the Tomcat. If you would like to configure the session manager properties, you can setup the following properties in your `application.properties` file: | ||
|
||
- `tsm.hazelcast.config.location`: Allows to provide Hazelcast member configuration. If not provided, `hazelcast.xml` in the classpath is used by default. Only works if `tsm.client.only` is **not** to set true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it common to use such abbreviations as prefix? Like tsm
? Did you see other projects using it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I haven't checked but I used it to separate from other properties with the same name for other modules. Do you have any other suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Yeah, makes sense, otherwise, they'd collide with other properties. But would it maybe make sense to stick to some convention from Spring, like here: https://docs.spring.io/spring-boot/docs/2.1.18.RELEASE/reference/html/boot-features-hazelcast.html
For example, in Spring you define the Hazelcast configuration with spring.hazelcast.config
and in your configuration it's tsm.hazelcast.config.location
. So from what I see, Spring just added a prefix spring.
to all the Hazelcast properties. So, maybe you should call yours like tsm.hazelcast.config
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I preferred to use the same config property names as we use in Tomcat Session Manger itself, just adding tsm.
in front of them (See: https://github.com/hazelcast/hazelcast-tomcat-sessionmanager#configuring-manager-element-for-tomcat). I think this makes more sense from the user's point of view. I changed the tsm.hazelcast.config.location
to tsm.config.location
accordingly.
|
||
@Bean | ||
@ConditionalOnMissingBean(type = "com.hazelcast.config.Config") | ||
@ConditionalOnProperty(name = "tsm.client.only", havingValue = "false", matchIfMissing = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, how Spring Boot now detects if P2P or Client-Server is used? Maybe we can piggy-back on this and not introduce our own parameter tsm.client.only
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check this 👍
/** | ||
* This package provides Spring Boot auto-configuration classes | ||
*/ | ||
package com.hazelcast.session.springboot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I already mentioned somewhere, consider removing this package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replied under the other comment.
throws Exception { | ||
HazelcastInstance hazelcastInstance = (HazelcastInstance) applicationContext.getBean("hazelcastInstance"); | ||
LinkedList<DistributedObject> distributedObjects = (LinkedList<DistributedObject>) hazelcastInstance.getDistributedObjects(); | ||
assertEquals("Session map should be created.", 1, distributedObjects.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a good practice to mix assertions with the test preparation. The test should have a clear structure given when then
. Could you either split into multiple tests or remove this assertion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed them, but I am not sure if we should follow given when then
structure here. These are more likely integration tests in general.
|
||
@SpringBootApplication | ||
@PropertySource("classpath:clientServer.properties") | ||
public class ClientServerApplication { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about embedding this class into ClientServerSpringBootConfigurationTest
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
|
||
@SpringBootApplication | ||
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, | ||
classes = ClientServerApplication.class)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you need classes = ClientServerApplication.class
? Isn't this class independent from ClientServerApplication
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClientServerApplication
adds clientServer.properties
as property source, and it is a component since they are all in the same package. I excluded it in the P2PApplication
to prevent tsm.client.only=true
take effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leszko, you got the exception because most likely you haven't added Hazelcast to the classpath. It should be also provided, I'll update the readme accordingly.
README.md
Outdated
|
||
Starting with v2.2, Hazelcast Tomcat Session Manager supports auto-configuration when used with Spring Boot. The only thing you need to do is to add Hazelcast Tomcat Session Manager (for Tomcat 9) library to the classpath. This will set Hazelcast Session Manager as the session manager of the Tomcat. If you would like to configure the session manager properties, you can setup the following properties in your `application.properties` file: | ||
|
||
- `tsm.hazelcast.config.location`: Allows to provide Hazelcast member configuration. If not provided, `hazelcast.xml` in the classpath is used by default. Only works if `tsm.client.only` is **not** to set true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I haven't checked but I used it to separate from other properties with the same name for other modules. Do you have any other suggestion?
@@ -28,16 +28,16 @@ | |||
|
|||
import static com.hazelcast.internal.config.ConfigLoader.locateConfig; | |||
|
|||
class ClientServerConfigLoader { | |||
public class ClientServerConfigLoader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, but I think it will be better to keep springboot
package since there are also test classes under the same package. Otherwise, all classes will be in the same parent package. WDYT?
/** | ||
* This package provides Spring Boot auto-configuration classes | ||
*/ | ||
package com.hazelcast.session.springboot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replied under the other comment.
throws Exception { | ||
HazelcastInstance hazelcastInstance = (HazelcastInstance) applicationContext.getBean("hazelcastInstance"); | ||
LinkedList<DistributedObject> distributedObjects = (LinkedList<DistributedObject>) hazelcastInstance.getDistributedObjects(); | ||
assertEquals("Session map should be created.", 1, distributedObjects.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed them, but I am not sure if we should follow given when then
structure here. These are more likely integration tests in general.
|
||
@SpringBootApplication | ||
@PropertySource("classpath:clientServer.properties") | ||
public class ClientServerApplication { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
|
||
@SpringBootApplication | ||
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, | ||
classes = ClientServerApplication.class)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClientServerApplication
adds clientServer.properties
as property source, and it is a component since they are all in the same package. I excluded it in the P2PApplication
to prevent tsm.client.only=true
take effect.
|
||
@Bean | ||
@ConditionalOnMissingBean(type = "com.hazelcast.config.Config") | ||
@ConditionalOnProperty(name = "tsm.client.only", havingValue = "false", matchIfMissing = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check this 👍
Wouldn't it be possible to detect it at runtime and give a proper warning/error message? |
|
||
@Bean(name = "hazelcastTomcatSessionManagerCustomizer") | ||
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizeTomcat(HazelcastInstance hazelcastInstance) { | ||
return new WebServerFactoryCustomizer<TomcatServletWebServerFactory>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I afraid in client.only
mode, a client will be created here via HazelcastInstance
bean with the client config above. However, another client will also be created at HazelcastSessionManager#startInternal for the manager. Is that the intent? For instance, this one fails in ClientServerSpringBootConfigurationTest:
@Test
public void createOnlyOneClient() {
Assert.assertEquals(1, HazelcastClient.getAllHazelcastClients().size());
// java.lang.AssertionError:
// Expected :1
// Actual :2
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@enozcan yes, you were correct. I fixed it by using the Hazelcast client instance initiated by Spring Boot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leszko regarding the error you got, we cannot give a proper error message for that since the exception is thrown from Spring Boot itself.
However, I removed the tsm.client.only
parameter as you suggested. Now, it is auto-configured accordingly with the provided Hazelcast configuration file.
|
||
@Bean(name = "hazelcastTomcatSessionManagerCustomizer") | ||
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizeTomcat(HazelcastInstance hazelcastInstance) { | ||
return new WebServerFactoryCustomizer<TomcatServletWebServerFactory>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@enozcan yes, you were correct. I fixed it by using the Hazelcast client instance initiated by Spring Boot.
...at9/src/main/java/com/hazelcast/session/springboot/HazelcastSessionManagerConfiguration.java
Outdated
Show resolved
Hide resolved
...at9/src/main/java/com/hazelcast/session/springboot/HazelcastSessionManagerConfiguration.java
Outdated
Show resolved
Hide resolved
tomcat9/src/main/java/com/hazelcast/session/HazelcastSessionManager.java
Show resolved
Hide resolved
Wouldn't it work if you check it somewhere here? I mean to check if you have Hazelcast on the classpath? The other option is to change the Hazelcast dependency scope to not be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes Alparslan. Added some replies to your comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make the classes package-private. The rest looks good 👍
Fixes #73.