Spring configuration for multiple Activemq remote brokers - spring

How to configure multiple remote activemq brokers (different IP address) in spring context? Below is the configuration for 1 remote broker. I am using camel to create routes that produce and consume messages from and to different queues in multiple remote brokers. Based on the following routes, how do the system knows which remote broker each queue belongs to?
List item
from("direct:start").to("activemq:queue:outgoingRequests")
List item
from("activemq:queue:incomingOrders").to("log:Events?
showAll=true").to("bean:jmsService")
Spring context for 1 broker
org.camel.routes
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://10.1.11.97:61616" />
</bean>
<bean id="pooledConnectionFactory"
class="org.apache.activemq.pool.PooledConnectionFactory" init-
method="start" destroy-method="stop">
<property name="maxConnections" value="8" />
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="pooledConnectionFactory"/>
<property name="concurrentConsumers" value="10"/>
</bean>
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="jmsConfig"/>
</bean>

Just add more components with different names
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="jmsConfig"/>
</bean>
<bean id="activemq2" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="myOtherJmsConfig"/>
</bean>
Then simply use the names:
<from uri="activemq:queue:MY.QUEUE"/><!-- get from "1st" ActiveMQ -->
<to uri="activemq2:queue:MY.QUEUE"/> <!-- put to same queue name on other ActiveMQ -->
Actually, you can call them whatever you want, like "EuropeanMarketBroker" or whatever fits in.

I have been trying to achieve this with the difference that my spring configuration is not in xml. It is helpful to know that you can achieve the same outcome by using spring annotations in a few ways.
The key to achieving this is registering the component with the desired name.
For example:
camelContext.addComponent("activemq2", jmsComponentInstance);
There is two ways of achieving this. Namely by creating two beans with qualifiers which identifies them from each other and then wiring those beans and registering them as components. Alternatively (this is preferable) you can create the bean and register the component all at once. Below are examples of both:
1 - Create Bean and Register elsewhere
#Configuration
public class ClassA{
#Bean #Qualifier("activemq2") public JmsComponent createJmsComponent(){
return JmsComponent.jmsComponentAutoAcknowledge(..);//Initialise component from externalised configs
}
}
#Component
public class ClassB{
#Autowired private CamelContext camelContext;
#Autowired #Qualifier("activemq2")
private JmsComponent jmsComponent;
public void someMethod(){
camelContext.addComponent("activemq2", jmsComponent);
}
}
2 - Create Bean and Register in one place within your #Configuration bean.
#Bean #Autowired public JmsComponent createJmsComponent(CamelContext camelContext){
JmsComponent component = JmsComponent.jmsComponentAutoAcknowledge(..);//Initialise component from externalised configs
camelContext.addComponent("activemq2", component);//Add Component to camel context
return component;//Return component instance
}

I addition of the two answers, here is my working solution with the latest SpringBoot using dedicated properties for both broker:
First, I define two Beans for each ConnectionFactory:
// gatewayRouterProperties is a java `record` mapped to the application.yml property file.
// One ConnectionFactory for the onPremise broker
#Bean
public ConnectionFactory jmsConnectionFactoryOnPrem() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(gatewayRouterProperties.activeMq().brokerOnPrem().url());
activeMQConnectionFactory.setUserName(gatewayRouterProperties.activeMq().brokerOnPrem().user());
activeMQConnectionFactory.setPassword(gatewayRouterProperties.activeMq().brokerOnPrem().pass());
return activeMQConnectionFactory;
}
// Another broker ConnectionFactory for the cloud AWS broker
#Bean
public ConnectionFactory jmsConnectionFactoryAws() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(gatewayRouterProperties.activeMq().brokerAws().url());
activeMQConnectionFactory.setUserName(gatewayRouterProperties.activeMq().brokerAws().user());
activeMQConnectionFactory.setPassword(gatewayRouterProperties.activeMq().brokerAws().pass());
return activeMQConnectionFactory;
}
And then I just define the two Beans ActiveMQComponent (the same as Peter's answer but using annotations):
#Bean(name = "activemq")
public ActiveMQComponent createActiveMQComponentOnPrem() {
ActiveMQConfiguration amqConfig = new ActiveMQConfiguration();
amqConfig.setConnectionFactory(jmsConnectionFactoryOnPrem());
return new ActiveMQComponent(amqConfig);
}
#Bean(name = "activemq2")
public ActiveMQComponent createActiveMQComponentAws() {
ActiveMQConfiguration amqConfig = new ActiveMQConfiguration();
amqConfig.setConnectionFactory(jmsConnectionFactoryAws());
return new ActiveMQComponent(amqConfig);
}
Note that I am using the bean name attribute and no need to add that manually in CamelContext.
After in my Camel route I just use my activemq components beans like this:
// 'activemq' component => AMQ on-prem
// 'activemq2' component => AMQ AWS
from("activemq:queue:QUEUE.TO.SYNC.TO.AWS")
.routeId("gw-router-route-on-prem-to-aws")
.autoStartup("{{autostart-enabled}}")
.to("activemq2:queue:QUEUE.FROM.ON.PREM")
;

Related

Actuator JMS Health Check Return False Positive With Java Configuration

I have run into an issue where the Actuator probe fails for JMS health even though my routes can connect and produce message to JMS. So in short Actuator is saying it is down but it is working.
Tech stack and tech notes:
Spring-boot: 2.3.1.RELEASE
Camel: 3.4.1
Artemis: 2.11.0
Artemis has been setup to use a user name and password(artemis/artemis).
Using org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory for connection factory.
My route is as simple as chips:
<route id="timer-cluster-producer-route">
<from uri="timer:producer-ticker?delay=5000"/>
<setBody>
<groovy>
result = ["Name":"Johnny"]
</groovy>
</setBody>
<marshal>
<json library="Jackson"/>
</marshal>
<to uri="ref:jms-producer-cluster-event" />
</route>
XML Based Artemis Configuration
With Spring-boot favoring java based configuration I am busy migrating our XML beans accordingly.Thus I took a working beans.xml file pasted into the project and fired up the route and I could send messages flowing and the health check returned OK.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="jmsConnectionFactory"
class="org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="user" value="artemis"/>
<property name="password" value="artemis"/>
<property name="connectionLoadBalancingPolicyClassName" value="org.apache.activemq.artemis.api.core.client.loadbalance.RoundRobinConnectionLoadBalancingPolicy"/>
</bean>
<!--org.messaginghub.pooled.jms.JmsPoolConnectionFactory-->
<!--org.apache.activemq.jms.pool.PooledConnectionFactory-->
<bean id="jmsPooledConnectionFactory"
class="org.apache.activemq.jms.pool.PooledConnectionFactory"
init-method="start" destroy-method="stop">
<property name="maxConnections" value="64" />
<property name="MaximumActiveSessionPerConnection"
value="500" />
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory"
ref="jmsPooledConnectionFactory" />
<property name="concurrentConsumers" value="1" />
<property name="artemisStreamingEnabled" value="true"/>
</bean>
<bean id="jms"
class="org.apache.camel.component.jms.JmsComponent">
<property name="configuration" ref="jmsConfig"/>
</bean>
<!-- <bean id="activemq"
class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="jmsConfig" />
</bean>-->
</beans>
Spring-boot Auto (Black)Magic Configuration
I then used the application.yaml file to configure the artemis connection by using this method as outlined in the Spring-boot documentation. This also worked when my application.yaml file contained the following configuration:
artemis:
user: artemis
host: localhost
password: artemis
pool:
max-sessions-per-connection: 500
enabled: true
max-connections: 16
This worked like a charm.
Brave Attempt At Java Configuration.
So I then went for gold and tried the Java based configuration as outlined below:
#SpringBootApplication
#ImportResource("classpath:/camel/camel.xml")
public class ClusterProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ClusterProducerApplication.class, args);
}
#Bean
public JmsComponent jms() throws JMSException {
// Create the connectionfactory which will be used to connect to Artemis
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
cf.setBrokerURL("tcp://localhost:61616");
cf.setUser("artemis");
cf.setPassword("artemis");
//Create connection pool using connection factory
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setMaxConnections(2);
pooledConnectionFactory.setConnectionFactory(cf);
//Create configuration which uses connection factory
JmsConfiguration jmsConfiguration = new JmsConfiguration();
jmsConfiguration.setConcurrentConsumers(2);
jmsConfiguration.setArtemisStreamingEnabled(true);
jmsConfiguration.setConnectionFactory(pooledConnectionFactory);
// Create the Camel JMS component and wire it to our Artemis configuration
JmsComponent jms = new JmsComponent();
jms.setConfiguration(jmsConfiguration);
return jms;
}
}
So when camel starts up I see the following warning logged on start up:
020-07-28 12:33:38.631 WARN 25329 --- [)-192.168.1.158] o.s.boot.actuate.jms.JmsHealthIndicator : JMS health check failed
javax.jms.JMSSecurityException: AMQ229031: Unable to validate user from /127.0.0.1:42028. Username: null; SSL certificate subject DN: unavailable
After the 5sec delay the timer kicks in and message are being produced. I logged into the Artemis console and I can browse the messages and can see them being created. However when I run a get on actuator health I see the following:
"jms": {
"status": "DOWN",
"details": {
"error": "javax.jms.JMSSecurityException: AMQ229031: Unable to validate user from /127.0.0.1:42816. Username: null; SSL certificate subject DN: unavailable"
}
},
This feels like a big of a bug to me.
Observations about connection pooling implementations.
I noticed that AMQ connection pooling has been moved into the following maven dependency:
<dependency>
<groupId>org.messaginghub</groupId>
<artifactId>pooled-jms</artifactId>
</dependency>
I thought let me give that a try as well. It show the same behaviour as outlined above with one more interesting thing. When using org.messaginghub.pooled-jms as the connection pool(recommended by spring-boot docs as well) the following is logged on startup.
2020-07-28 12:41:37.255 INFO 26668 --- [ main] o.m.pooled.jms.JmsPoolConnectionFactory : JMS ConnectionFactory on classpath is not a JMS 2.0+ version.
Which is weird as according to the official repo the connector is JMS 2.0 compliant.
Quick Summary:
It appears that actuator does not pick up the credentials of the connection factory when configuring the JMS component via Java. While a work around exists at the moment by using the spring-boot application.yaml configuration it limits the way you can configure JMS clients on Camel.
So after some digging and reaching out to the Spring-boot people on GitHub, I found what the issue is. When using Java configuration I am configuring the JMS component of Camel with a connection factory. However Spring-boot is completely unaware of this as it is a Camel component. Thus the connection factory used by the JMS needs to be exposed to Spring-boot for it to work.
The fix is relatively simple. See code below:
#Configuration
public class ApplicationConfiguration {
private ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
#Bean
public JmsComponent jms() throws JMSException {
// Create the connectionfactory which will be used to connect to Artemis
cf.setBrokerURL("tcp://localhost:61616");
cf.setUser("artemis");
cf.setPassword("artemis");
// Setup Connection pooling
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setMaxConnections(2);
pooledConnectionFactory.setConnectionFactory(cf);
JmsConfiguration jmsConfiguration = new JmsConfiguration();
jmsConfiguration.setConcurrentConsumers(2);
jmsConfiguration.setArtemisStreamingEnabled(true);
jmsConfiguration.setConnectionFactory(pooledConnectionFactory);
// Create the Camel JMS component and wire it to our Artemis connectionfactory
JmsComponent jms = new JmsComponent();
jms.setConfiguration(jmsConfiguration);
return jms;
}
/*
This line will expose the connection factory to Spring-boot.
*/
#Bean
public ConnectionFactory jmsConnectionFactory() {
return cf;
}
}

Enabling JMX in Spring Boot

Here is my build.gradle...
dependencies {
compile('org.springframework.boot:spring-boot-starter-parent:2.0.1.RELEASE')
compile('org.springframework.boot:spring-boot-starter-batch')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("org.springframework.boot:spring-boot-starter-web")
compile("com.h2database:h2")
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.batch:spring-batch-test')
}
Which I thought would enable JMX by default. I go to JConsole, connect to the application and expect to see a org.springframework.boot folder under java.util.logging, I see nothing.
So, now I pick a few of my custom beans and add #ManagedResource, I know see these.
However, what if I want to expose spring batch beans like #JobOperator how do I do this?
Pre Spring Boot, I could so something like:
<bean class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="spring:service=batch,bean=jobOperator">
<bean class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="jobOperator"/>
<property name="interceptorNames" value="exceptionTranslator" />
</bean>
</entry>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
<property name="interfaceMappings">
<map>
<entry key="spring:service=batch,bean=jobOperator"
value="org.springframework.batch.core.launch.JobOperator"/>
</map>
</property>
</bean>
</property>
</bean>
When I define my JobOperator, in my #Configuration file in Spring Boot I do:
#Bean
public JobOperator jobOperator() throws Exception {
SimpleJobOperator simpleJobOperator = new SimpleJobOperator();
// the operator wraps the launcher
simpleJobOperator.setJobLauncher(this.jobLauncher);
...
}
I can't add #ManagedResource under the #Bean annotation. So how do I expose the JobOperator as a JMX bean?
Here is how i did it in my code -
#Bean
public MBeanExporter exporter(){
MBeanExporter m = new MBeanExporter();
Map<String,Object> map = new HashMap<String, Object>();
JmxBean testBean = (JmxBean)ctx.getBean("testBean");
map.put("testBean",testBean);
m.setBeans(map);
return m;
}
#Bean
public JmxBean testBean(){
return new JmxBean("test",100);
}
In your case since you want to register jobOperator, just replace testBean in exporter bean with it.

How to configure multiple MyBatis datasources in Spring Boot?

With MyBatis-Spring-Boot-Starter, we can easily integrate MyBatis with Spring Boot, it works perfectly for one data source. However, now we'd like to add an extra data source in our project, unfortunately it seems not easy.
In MyBatis official documentation, I see the following content:
MyBatis-Spring-Boot-Starter will:
Autodetect an existing DataSource.
Will create and register an instance of a SqlSessionFactoryBean passing that DataSource as an input.
Will create and register an instance of a SqlSessionTemplate got out of the SqlSessionFactoryBean.
It looks like MyBatis-Spring-Boot-Starter supports only one data source at this moment. So, the question is how to configure multiple MyBatis datasources in Sping Boot?
You outlined 3 beans that are needed for MyBatis+Spring integration. These are automatically created for single data source.
If you need two data sources, you need to create 3 beans for each data source explicitly. So you'll be creating 6 beans (2 of type DataSource, 2 of type SqlSessionFactoryBean and 2 of type SqlSessionFactoryBean).
To bind DAO with certain datasource, you will need to use sqlSessionTemplateRef or sqlSessionFactoryRef parameter of #MapperScan annotation.
Also I don't recommend to go down the XML hell. I was using it this way in PROD, with two data sources, without any ugly XML configs on various projects. Also SQL queries were annotated.
Shame is that MyBatis documentation is not great and most examples out there are in XML.
Something this like this to your spring servlet.xml:
<bean id="db2dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>${db2.database.driver}</value></property>
<property name="url"><value>${db2.database.url}</value></property>
<property name="username"><value>${db2.database.username}</value></property>
<property name="password"><value>${db2.database.password}</value></property>
<property name="maxActive"><value>${db2.database.maxactiveconnections}</value></property>
<property name="maxIdle"><value>${db2.database.idleconnections}</value></property>
<property name="initialSize"><value>${db2.database.initialSize}</value></property>
</bean>
<bean id="db2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="db2dataSource" />
<property name="configLocation" value="/WEB-INF/mybatis-config.xml"/>
</bean>
<bean id="db2Dao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="db2SqlSessionFactory"/>
<property name="mapperInterface" value="com.dao.db2Dao" />
</bean>
<bean id="oracledataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>${oracle.database.driver}</value></property>
<property name="url"><value>${oracle.database.url}</value></property>
<property name="username"><value>${oracle.database.username}</value></property>
<property name="password"><value>${oracle.database.password}</value></property>
<property name="maxActive"><value>${oracle.database.maxactiveconnections}</value></property>
<property name="maxIdle"><value>${oracle.database.idleconnections}</value></property>
<property name="initialSize"><value>${oracle.database.initialSize}</value></property>
</bean>
<bean id="oracleSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="oracledataSource" />
<property name="configLocation" value="/WEB-INF/mybatis-config.xml"/>
</bean>
<bean id="oracleoardDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="oracleSqlSessionFactory"/>
<property name="mapperInterface" value="com.lodige.clcs.dao.oracleoardDao" />
</bean>
Maybe this is what you need
#Configuration
#MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef =
"test1SqlSessionTemplate")
public class DataSource1Config {
#Bean(name = "test1DataSource")
#ConfigurationProperties(prefix = "spring.datasource.test1")
#Primary
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "test1SqlSessionFactory")
#Primary
public SqlSessionFactory testSqlSessionFactory(#Qualifier("test1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
#Bean(name = "test1TransactionManager")
#Primary
public DataSourceTransactionManager testTransactionManager(#Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean(name = "test1SqlSessionTemplate")
#Primary
public SqlSessionTemplate testSqlSessionTemplate(#Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}

Stop a spring jms message listener

I have a scenario where i need to stop the spring's DefaultMessageListenerContainer and then later start that again. I have 10 different DefaultMessageListenerContainer listening to 10 different queue.
All 10 different containers are calling the same method of same message listener class.
Now i want to stop the messagelistenercontainer for a particular queue depending on the exception i get in onMessage method.
Please suggest me how i can achieve the above scenario.
Below is my listener configuration
<bean id="msglistenerForAuditError" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="sessionTransacted" value="true"/>
<property name="destinationName" value="test.audit.error2"/>
<property name="messageListener" ref="auditerrorListener" />
</bean>
<bean id="msglistenerForAuditEvent" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="sessionTransacted" value="true"/>
<property name="destinationName" value="test.audit.event2"/>
<property name="messageListener" ref="auditerrorListener" />
</bean>
The DefaultMessageListenerContainer is a lifecycle bean and as such it exposes a start and a stop method that you can use to start and stop the listener, respectively.
You can build a service on your own that is gathering all known instances in the context and you can then loop over those to stop the containers, something like
#Service
public class MyService {
private final Collection<DefaultMessageListenerContainer> containers;
#Autowired
public MyService(Collection<DefaultMessageListenerContainer> containers) {
this.containers = containers;
}
public void stopAll() {
// iterate over the collection and call "stop()" on each item
}
}
That being said:
You should not invoke this service as part of a message listener as attempting to stop the container while the thread is processing a message will have weird side effect
The whole use case looks suspicious to me. Your message listeners should be resilient and, more importantly, they should be independent of each other; if you are stopping listener A because listener B threw an exception, something is definitely wrong in your design
stop method on DefaultMessageListenerContainer did not worked but shutdown method worked perfectly.
for(DefaultMessageListenerContainer defaultCont:containers){
defaultCont.shutdown();
}
Injecting a collection of DefaultMessageListenerContainer did not work for me, I use Spring Boot 1.4.x, with Spring 4.3.x
Here's how I solved it:
package org.example.queue;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
//import other stuffs
#Component
public class QueueManager {
#Autowired
JmsListenerEndpointRegistry endpointRegistry;
public void shutdown() {
endpointRegistry.getListenerContainers().forEach((container) -> {
if (container.isRunning()) {
log.debug("Shutting down listener: " + container.getClass().getName());
container.stop();
}
});
}
public void start() {
endpointRegistry.getListenerContainers().forEach((container) -> {
if (!container.isRunning()) {
log.debug("Starting listener: " + container.getClass().getName());
container.start();
}
});
}
}

Getting an EhCache instance with Spring... intelligently

I need to get a specific EhCache instance by name and I'd prefer to autowire if possible. Given the following automatically configured controller, how can I autowire in the cache instance I'm looking for?
#Controller
public class MyUniqueService {
...
}
<beans ...>
<ctx:component-scan base-package="my.controllers"/>
<mvc:annotation-driven />
</beans>
How do I configure EhCache in my application context? I don't see any log messages from EhCache about it loading the ehcache.xml file in my /WEB-INF/ directory. How do I make it load it?
How can I integrate EhCache with my Spring application to have it load the ehcache.xml file from my /WEB-INF/ directory and autowire a cache by a given name into my MyUniqueService controller?
First you need to create a Ehcache CacheManager singleton in you app context like this:
<bean id="myEhCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:my-ehcache.xml"/>
</bean>
Here configLocation is set to load from classpath or use value="/WEB-INF/my-ehcache.xml".
In your controller simply inject the CacheManager instance:
#Controller
public class MyUniqueService {
#Resource(name="myEhCacheManager")
private CacheManager cacheManager;
...
}
Alternatively, if you'd like to go the "entirely autowired" route, do:
<bean class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="/WEB-INF/ehcache.xml"/>
</bean>
</property>
</bean>
Setup your class like so:
#Controller
public class MyUniqueService {
#Autowired
private org.springframework.cache.CacheManager cacheManager;
public org.springframework.cache.Cache getUniqueObjectCache() {
return cacheManager.getCache("uniqueObjectCache");
}
}
uniqueObjectCache corresponds to this cache instance in your ehcache.xml cache definition:
<cache name="uniqueObjectCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU"
transactionalMode="off"/>
There isn't a way to inject an actual cache instance, but as shown above, you can inject a cache manager and use it to get the cache you're interested in.
Assuming you have cacheManager defined:
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:/ehcache.xml"/>
</bean>
You can get/inject specific cache like this:
#Value("#{cacheManager.getCache('myCacheName')}")
private Cache myCache;
See also examples how to use Spring EL inside the #Value() http://www.mkyong.com/spring3/spring-el-method-invocation-example/ if you are interested.
You can also use autowire if the context can find a bean with the correct class. Here is how I configured my xml
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>WEB-INF/ehcache.xml</value>
</property>
</bean>
<bean id="cache" class="net.sf.ehcache.Cache" factory-bean="cacheManager" factory-method="getCache">
<constructor-arg value="CacheNameHere" />
</bean>
And my java class
#Autowired
private net.sf.ehcache.Cache cache;
This setup works for me.
Indeed! Or if you want to use a java config class:
#Inject
private ResourceLoader resourceLoader;
#Bean
public CacheManager cacheManager() {
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
try {
ehCacheCacheManager.setCacheManager(ehcacheCacheManager().getObject());
} catch (Exception e) {
throw new IllegalStateException("Failed to create an EhCacheManagerFactoryBean", e);
}
return ehCacheCacheManager;
}
#Bean
public FactoryBean<net.sf.ehcache.CacheManager> ehcacheCacheManager() {
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(resourceLoader.getResource("classpath:ehcache.xml"));
return bean;
}

Resources