Skip to content

Commit a0b9006

Browse files
committed
DATAREDIS-425 - CDI extension to export Redis repositories.
We now export Redis Repositories in a CDI environment. Repositories can be injected using @Inject. The CDI extension requires at least RedisOperations to be provided. Other beans like RedisKeyValueAdapter and RedisKeyValueTemplate can be provided by the user. If no RedisKeyValueAdapter/RedisKeyValueTemplate beans are found, the CDI extension creates own managed instances.
1 parent 69d42c8 commit a0b9006

21 files changed

+1354
-0
lines changed

pom.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,36 @@
152152
<optional>true</optional>
153153
</dependency>
154154

155+
<!-- CDI -->
156+
<dependency>
157+
<groupId>javax.enterprise</groupId>
158+
<artifactId>cdi-api</artifactId>
159+
<version>${cdi}</version>
160+
<scope>provided</scope>
161+
<optional>true</optional>
162+
</dependency>
163+
164+
<dependency>
165+
<groupId>javax.el</groupId>
166+
<artifactId>el-api</artifactId>
167+
<version>${cdi}</version>
168+
<scope>test</scope>
169+
</dependency>
170+
171+
<dependency>
172+
<groupId>org.apache.openwebbeans.test</groupId>
173+
<artifactId>cditest-owb</artifactId>
174+
<version>${webbeans}</version>
175+
<scope>test</scope>
176+
</dependency>
177+
178+
<dependency>
179+
<groupId>javax.servlet</groupId>
180+
<artifactId>servlet-api</artifactId>
181+
<version>2.5</version>
182+
<scope>test</scope>
183+
</dependency>
184+
155185
<!-- Test -->
156186

157187
<dependency>

src/main/asciidoc/reference/redis-repositories.adoc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,68 @@ Here's an overview of the keywords supported for Redis and what a method contain
537537
|===============
538538
====
539539

540+
[[redis.misc.cdi-integration]]
541+
== CDI integration
540542

543+
Instances of the repository interfaces are usually created by a container, which Spring is the most natural choice when working with Spring Data. There's sophisticated support to easily set up Spring to create bean instances. Spring Data Redis ships with a custom CDI extension that allows using the repository abstraction in CDI environments. The extension is part of the JAR so all you need to do to activate it is dropping the Spring Data Redis JAR into your classpath.
544+
545+
You can now set up the infrastructure by implementing a CDI Producer for the `RedisConnectionFactory` and `RedisOperations`:
546+
547+
[source, java]
548+
----
549+
class RedisOperationsProducer {
550+
551+
552+
@Produces
553+
RedisConnectionFactory redisConnectionFactory() {
554+
555+
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
556+
jedisConnectionFactory.setHostName("localhost");
557+
jedisConnectionFactory.setPort(6379);
558+
jedisConnectionFactory.afterPropertiesSet();
559+
560+
return jedisConnectionFactory;
561+
}
562+
563+
void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {
564+
565+
if (redisConnectionFactory instanceof DisposableBean) {
566+
((DisposableBean) redisConnectionFactory).destroy();
567+
}
568+
}
569+
570+
@Produces
571+
@ApplicationScoped
572+
RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {
573+
574+
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
575+
template.setConnectionFactory(redisConnectionFactory);
576+
template.afterPropertiesSet();
577+
578+
return template;
579+
}
580+
581+
}
582+
----
583+
584+
The necessary setup can vary depending on the JavaEE environment you run in.
585+
586+
The Spring Data Redis CDI extension will pick up all Repositories available as CDI beans and create a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container. Thus obtaining an instance of a Spring Data repository is a matter of declaring an `@Injected` property:
587+
588+
[source, java]
589+
----
590+
class RepositoryClient {
591+
592+
@Inject
593+
PersonRepository repository;
594+
595+
public void businessMethod() {
596+
List<Person> people = repository.findAll();
597+
}
598+
}
599+
----
600+
601+
A Redis Repository requires `RedisKeyValueAdapter` and `RedisKeyValueTemplate` instances. These beans are created and managed by the Spring Data CDI extension if no provided beans are found. You can however supply your own beans to configure the specific properties of `RedisKeyValueAdapter` and `RedisKeyValueTemplate`.
541602

542603

543604

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisConverter redis
160160
initKeyExpirationListener();
161161
}
162162

163+
/**
164+
* Default constructor.
165+
*/
166+
protected RedisKeyValueAdapter() {
167+
}
168+
163169
/*
164170
* (non-Javadoc)
165171
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#put(java.io.Serializable, java.lang.Object, java.io.Serializable)
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.repository.cdi;
17+
18+
import java.lang.annotation.Annotation;
19+
import java.lang.reflect.Type;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Set;
26+
27+
import javax.enterprise.context.ApplicationScoped;
28+
import javax.enterprise.context.spi.CreationalContext;
29+
import javax.enterprise.inject.Alternative;
30+
import javax.enterprise.inject.Default;
31+
import javax.enterprise.inject.Stereotype;
32+
import javax.enterprise.inject.spi.Bean;
33+
import javax.enterprise.inject.spi.BeanManager;
34+
import javax.enterprise.inject.spi.InjectionPoint;
35+
import javax.enterprise.inject.spi.PassivationCapable;
36+
37+
import org.slf4j.Logger;
38+
import org.slf4j.LoggerFactory;
39+
import org.springframework.util.Assert;
40+
import org.springframework.util.StringUtils;
41+
42+
/**
43+
* Base class for {@link Bean} wrappers.
44+
*
45+
* @author Mark Paluch
46+
*/
47+
public abstract class CdiBean<T> implements Bean<T>, PassivationCapable {
48+
49+
private static final Logger LOGGER = LoggerFactory.getLogger(CdiBean.class);
50+
51+
protected final BeanManager beanManager;
52+
53+
private final Set<Annotation> qualifiers;
54+
private final Set<Type> types;
55+
private final Class<T> beanClass;
56+
private final String passivationId;
57+
58+
/**
59+
* Creates a new {@link CdiBean}.
60+
*
61+
* @param qualifiers must not be {@literal null}.
62+
* @param beanClass has to be an interface must not be {@literal null}.
63+
* @param beanManager the CDI {@link BeanManager}, must not be {@literal null}.
64+
*/
65+
public CdiBean(Set<Annotation> qualifiers, Class<T> beanClass, BeanManager beanManager) {
66+
this(qualifiers, Collections.<Type> emptySet(), beanClass, beanManager);
67+
}
68+
69+
/**
70+
* Creates a new {@link CdiBean}.
71+
*
72+
* @param qualifiers must not be {@literal null}.
73+
* @param types additional bean types, must not be {@literal null}.
74+
* @param beanClass must not be {@literal null}.
75+
* @param beanManager the CDI {@link BeanManager}, must not be {@literal null}.
76+
*/
77+
public CdiBean(Set<Annotation> qualifiers, Set<Type> types, Class<T> beanClass, BeanManager beanManager) {
78+
79+
Assert.notNull(qualifiers);
80+
Assert.notNull(beanManager);
81+
Assert.notNull(types);
82+
Assert.notNull(beanClass);
83+
84+
this.qualifiers = qualifiers;
85+
this.types = types;
86+
this.beanClass = beanClass;
87+
this.beanManager = beanManager;
88+
this.passivationId = createPassivationId(qualifiers, beanClass);
89+
}
90+
91+
/**
92+
* Creates a unique identifier for the given repository type and the given annotations.
93+
*
94+
* @param qualifiers must not be {@literal null} or contain {@literal null} values.
95+
* @param repositoryType must not be {@literal null}.
96+
* @return
97+
*/
98+
private final String createPassivationId(Set<Annotation> qualifiers, Class<?> repositoryType) {
99+
100+
List<String> qualifierNames = new ArrayList<String>(qualifiers.size());
101+
102+
for (Annotation qualifier : qualifiers) {
103+
qualifierNames.add(qualifier.annotationType().getName());
104+
}
105+
106+
Collections.sort(qualifierNames);
107+
108+
StringBuilder builder = new StringBuilder(StringUtils.collectionToDelimitedString(qualifierNames, ":"));
109+
builder.append(":").append(repositoryType.getName());
110+
111+
return builder.toString();
112+
}
113+
114+
/*
115+
* (non-Javadoc)
116+
* @see javax.enterprise.inject.spi.Bean#getTypes()
117+
*/
118+
public Set<Type> getTypes() {
119+
120+
Set<Type> types = new HashSet<Type>();
121+
types.add(beanClass);
122+
types.addAll(Arrays.asList(beanClass.getInterfaces()));
123+
types.addAll(this.types);
124+
125+
return types;
126+
}
127+
128+
/**
129+
* Returns an instance of the given {@link Bean} from the container.
130+
*
131+
* @param <S> the actual class type of the {@link Bean}.
132+
* @param bean the {@link Bean} defining the instance to create.
133+
* @param type the expected component type of the instance created from the {@link Bean}.
134+
* @return an instance of the given {@link Bean}.
135+
* @see javax.enterprise.inject.spi.BeanManager#getReference(Bean, Type, CreationalContext)
136+
* @see javax.enterprise.inject.spi.Bean
137+
* @see java.lang.reflect.Type
138+
*/
139+
@SuppressWarnings("unchecked")
140+
protected <S> S getDependencyInstance(Bean<S> bean, Type type) {
141+
return (S) beanManager.getReference(bean, type, beanManager.createCreationalContext(bean));
142+
}
143+
144+
/**
145+
* Forces the initialization of bean target.
146+
*/
147+
public final void initialize() {
148+
create(beanManager.createCreationalContext(this));
149+
}
150+
151+
/*
152+
* (non-Javadoc)
153+
* @see javax.enterprise.context.spi.Contextual#destroy(java.lang.Object, javax.enterprise.context.spi.CreationalContext)
154+
*/
155+
public void destroy(T instance, CreationalContext<T> creationalContext) {
156+
157+
if (LOGGER.isDebugEnabled()) {
158+
LOGGER.debug(String.format("Destroying bean instance %s for repository type '%s'.", instance.toString(),
159+
beanClass.getName()));
160+
}
161+
162+
creationalContext.release();
163+
}
164+
165+
/*
166+
* (non-Javadoc)
167+
* @see javax.enterprise.inject.spi.Bean#getQualifiers()
168+
*/
169+
public Set<Annotation> getQualifiers() {
170+
return qualifiers;
171+
}
172+
173+
/*
174+
* (non-Javadoc)
175+
* @see javax.enterprise.inject.spi.Bean#getName()
176+
*/
177+
public String getName() {
178+
179+
return getQualifiers().contains(Default.class) ? beanClass.getName()
180+
: beanClass.getName() + "-" + getQualifiers().toString();
181+
}
182+
183+
/*
184+
* (non-Javadoc)
185+
* @see javax.enterprise.inject.spi.Bean#getStereotypes()
186+
*/
187+
public Set<Class<? extends Annotation>> getStereotypes() {
188+
189+
Set<Class<? extends Annotation>> stereotypes = new HashSet<Class<? extends Annotation>>();
190+
191+
for (Annotation annotation : beanClass.getAnnotations()) {
192+
Class<? extends Annotation> annotationType = annotation.annotationType();
193+
if (annotationType.isAnnotationPresent(Stereotype.class)) {
194+
stereotypes.add(annotationType);
195+
}
196+
}
197+
198+
return stereotypes;
199+
}
200+
201+
/*
202+
* (non-Javadoc)
203+
* @see javax.enterprise.inject.spi.Bean#getBeanClass()
204+
*/
205+
public Class<?> getBeanClass() {
206+
return beanClass;
207+
}
208+
209+
/*
210+
* (non-Javadoc)
211+
* @see javax.enterprise.inject.spi.Bean#isAlternative()
212+
*/
213+
public boolean isAlternative() {
214+
return beanClass.isAnnotationPresent(Alternative.class);
215+
}
216+
217+
/*
218+
* (non-Javadoc)
219+
* @see javax.enterprise.inject.spi.Bean#isNullable()
220+
*/
221+
public boolean isNullable() {
222+
return false;
223+
}
224+
225+
/*
226+
* (non-Javadoc)
227+
* @see javax.enterprise.inject.spi.Bean#getInjectionPoints()
228+
*/
229+
public Set<InjectionPoint> getInjectionPoints() {
230+
return Collections.emptySet();
231+
}
232+
233+
/*
234+
* (non-Javadoc)
235+
* @see javax.enterprise.inject.spi.Bean#getScope()
236+
*/
237+
public Class<? extends Annotation> getScope() {
238+
return ApplicationScoped.class;
239+
}
240+
241+
/*
242+
* (non-Javadoc)
243+
* @see javax.enterprise.inject.spi.PassivationCapable#getId()
244+
*/
245+
public String getId() {
246+
return passivationId;
247+
}
248+
249+
/*
250+
* (non-Javadoc)
251+
* @see java.lang.Object#toString()
252+
*/
253+
@Override
254+
public String toString() {
255+
return String.format("CdiBean: type='%s', qualifiers=%s", beanClass.getName(), qualifiers.toString());
256+
}
257+
258+
}

0 commit comments

Comments
 (0)