-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
AbstractListenerFactoryBean.java
240 lines (207 loc) · 8.13 KB
/
AbstractListenerFactoryBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.listener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.batch.support.MethodInvoker;
import org.springframework.batch.support.MethodInvokerUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation;
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
/**
* {@link FactoryBean} implementation that builds a listener based on the various
* lifecycle methods or annotations that are provided. There are three possible ways of
* having a method called as part of a listener lifecycle:
*
* <ul>
* <li>Interface implementation: By implementing any of the subclasses of a listener
* interface, methods on said interface will be called
* <li>Annotations: Annotating a method will result in registration.
* <li>String name of the method to be called, which is tied to a {@link ListenerMetaData}
* value in the metaDataMap.
* </ul>
*
* It should be noted that methods obtained by name or annotation that don't match the
* listener method signatures to which they belong will cause errors. However, it is
* acceptable to have no parameters at all. If the same method is marked in more than one
* way. (i.e. the method name is given and it is annotated) the method will only be called
* once. However, if the same class has multiple methods tied to a particular listener,
* each method will be called. Also note that the same annotations cannot be applied to
* two separate methods in a single class.
*
* @author Lucas Ward
* @author Dan Garrette
* @since 2.0
* @see ListenerMetaData
*/
public abstract class AbstractListenerFactoryBean<T> implements FactoryBean<Object>, InitializingBean {
private static final Log logger = LogFactory.getLog(AbstractListenerFactoryBean.class);
private Object delegate;
private Map<String, String> metaDataMap;
@Override
public Object getObject() {
if (metaDataMap == null) {
metaDataMap = new HashMap<>();
}
// Because all annotations and interfaces should be checked for, make
// sure that each meta data
// entry is represented.
for (ListenerMetaData metaData : this.getMetaDataValues()) {
if (!metaDataMap.containsKey(metaData.getPropertyName())) {
// put null so that the annotation and interface is checked
metaDataMap.put(metaData.getPropertyName(), null);
}
}
Set<Class<?>> listenerInterfaces = new HashSet<>();
// For every entry in the map, try and find a method by interface, name,
// or annotation. If the same
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<>();
boolean synthetic = false;
for (Entry<String, String> entry : metaDataMap.entrySet()) {
final ListenerMetaData metaData = this.getMetaDataFromPropertyName(entry.getKey());
Set<MethodInvoker> invokers = new HashSet<>();
MethodInvoker invoker;
invoker = getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate,
metaData.getParamTypes());
if (invoker != null) {
invokers.add(invoker);
}
invoker = getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes());
if (invoker != null) {
invokers.add(invoker);
synthetic = true;
}
if (metaData.getAnnotation() != null) {
invoker = getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes());
if (invoker != null) {
invokers.add(invoker);
synthetic = true;
}
}
if (!invokers.isEmpty()) {
invokerMap.put(metaData.getMethodName(), invokers);
listenerInterfaces.add(metaData.getListenerInterface());
}
}
if (listenerInterfaces.isEmpty()) {
listenerInterfaces.add(this.getDefaultListenerClass());
}
if (!synthetic) {
int count = 0;
for (Class<?> listenerInterface : listenerInterfaces) {
if (listenerInterface.isInstance(delegate)) {
count++;
}
}
// All listeners can be supplied by the delegate itself
if (count == listenerInterfaces.size()) {
return delegate;
}
}
boolean ordered = false;
if (delegate instanceof Ordered) {
ordered = true;
listenerInterfaces.add(Ordered.class);
}
// create a proxy listener for only the interfaces that have methods to
// be called
ProxyFactory proxyFactory = new ProxyFactory();
if (delegate instanceof Advised) {
proxyFactory.setTargetSource(((Advised) delegate).getTargetSource());
}
else {
proxyFactory.setTarget(delegate);
}
@SuppressWarnings("rawtypes")
Class[] a = new Class[0];
proxyFactory.setInterfaces(listenerInterfaces.toArray(a));
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap, ordered)));
return proxyFactory.getProxy();
}
protected abstract ListenerMetaData getMetaDataFromPropertyName(String propertyName);
protected abstract ListenerMetaData[] getMetaDataValues();
protected abstract Class<?> getDefaultListenerClass();
protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
if (methodName != null) {
return MethodInvokerUtils.getMethodInvokerByName(candidate, methodName, false, params);
}
else {
return null;
}
}
@Override
public boolean isSingleton() {
return true;
}
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
public void setMetaDataMap(Map<String, String> metaDataMap) {
this.metaDataMap = metaDataMap;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(delegate != null, "Delegate must not be null");
}
/**
* Convenience method to check whether the given object is or can be made into a
* listener.
* @param target the object to check
* @param listenerType the class of the listener.
* @param metaDataValues array of {@link ListenerMetaData}.
* @return true if the delegate is an instance of any of the listener interface, or
* contains the marker annotations
*/
public static boolean isListener(Object target, Class<?> listenerType, ListenerMetaData[] metaDataValues) {
if (target == null) {
return false;
}
if (listenerType.isInstance(target)) {
return true;
}
if (target instanceof Advised) {
TargetSource targetSource = ((Advised) target).getTargetSource();
if (targetSource != null && targetSource.getTargetClass() != null
&& listenerType.isAssignableFrom(targetSource.getTargetClass())) {
return true;
}
if (targetSource != null && targetSource.getTargetClass() != null
&& targetSource.getTargetClass().isInterface()) {
logger.warn(String.format(
"%s is an interface. The implementing class will not be queried for annotation based listener configurations. If using @StepScope on a @Bean method, be sure to return the implementing class so listener annotations can be used.",
targetSource.getTargetClass().getName()));
}
}
for (ListenerMetaData metaData : metaDataValues) {
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
return true;
}
}
return false;
}
}