-
Notifications
You must be signed in to change notification settings - Fork 369
/
DynamicDeployBeans.java
269 lines (211 loc) · 10.9 KB
/
DynamicDeployBeans.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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package com.sishuok.spring.dynamic;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.*;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>User: Zhang Kaitao
* <p>Date: 14-1-3
* <p>Version: 1.0
*/
public class DynamicDeployBeans {
protected static final Log logger = LogFactory.getLog(DynamicDeployBeans.class);
//RequestMappingHandlerMapping
private static Method detectHandlerMethodsMethod =
ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "detectHandlerMethods", Object.class);
private static Method getMappingForMethodMethod =
ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
private static Method getMappingPathPatternsMethod =
ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingPathPatterns", RequestMappingInfo.class);
private static Method getPathMatcherMethod =
ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getPathMatcher");
private static Field handlerMethodsField =
ReflectionUtils.findField(RequestMappingHandlerMapping.class, "handlerMethods", Map.class);
private static Field urlMapField =
ReflectionUtils.findField(RequestMappingHandlerMapping.class, "urlMap", MultiValueMap.class);
private static Field injectionMetadataCacheField =
ReflectionUtils.findField(AutowiredAnnotationBeanPostProcessor.class, "injectionMetadataCache");
static {
detectHandlerMethodsMethod.setAccessible(true);
getMappingForMethodMethod.setAccessible(true);
getMappingPathPatternsMethod.setAccessible(true);
getPathMatcherMethod.setAccessible(true);
handlerMethodsField.setAccessible(true);
urlMapField.setAccessible(true);
injectionMetadataCacheField.setAccessible(true);
}
private ApplicationContext ctx;
private DefaultListableBeanFactory beanFactory;
private Map<String, Long> scriptLastModifiedMap = new ConcurrentHashMap<>();//in millis
public DynamicDeployBeans() {
this(-1L);
}
public DynamicDeployBeans(Long scriptCheckInterval) {
if (scriptCheckInterval > 0L) {
startScriptModifiedCheckThead(scriptCheckInterval);
}
}
@Autowired
public void setApplicationContext(ApplicationContext ctx) {
if (!DefaultListableBeanFactory.class.isAssignableFrom(ctx.getAutowireCapableBeanFactory().getClass())) {
throw new IllegalArgumentException("BeanFactory must be DefaultListableBeanFactory type");
}
this.ctx = ctx;
this.beanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
}
public void registerBean(Class<?> beanClass) {
registerBean(null, beanClass);
}
public void registerBean(String beanName, Class<?> beanClass) {
Assert.notNull(beanClass, "register bean class must not null");
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(beanClass);
if (StringUtils.hasText(beanName)) {
beanFactory.registerBeanDefinition(beanName, bd);
} else {
BeanDefinitionReaderUtils.registerWithGeneratedName(bd, beanFactory);
}
}
public void registerController(Class<?> controllerClass) {
Assert.notNull(controllerClass, "register controller bean class must not null");
if (!WebApplicationContext.class.isAssignableFrom(ctx.getClass())) {
throw new IllegalArgumentException("applicationContext must be WebApplicationContext type");
}
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(controllerClass);
String controllerBeanName = controllerClass.getName();
removeOldControllerMapping(controllerBeanName);
beanFactory.registerBeanDefinition(controllerBeanName, bd);
addControllerMapping(controllerBeanName);
}
public void registerGroovyController(String scriptLocation) throws IOException {
if (scriptNotExists(scriptLocation)) {
throw new IllegalArgumentException("script not exists : " + scriptLocation);
}
scriptLastModifiedMap.put(scriptLocation, scriptLastModified(scriptLocation));
// Create script factory bean definition.
GroovyScriptFactory groovyScriptFactory = new GroovyScriptFactory(scriptLocation);
groovyScriptFactory.setBeanFactory(beanFactory);
groovyScriptFactory.setBeanClassLoader(beanFactory.getBeanClassLoader());
Object controller =
groovyScriptFactory.getScriptedObject(new ResourceScriptSource(ctx.getResource(scriptLocation)));
String controllerBeanName = scriptLocation;
removeOldControllerMapping(controllerBeanName);
if (beanFactory.containsBean(controllerBeanName)) {
beanFactory.destroySingleton(controllerBeanName); //移除单例bean
removeInjectCache(controller); //移除注入缓存 否则Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
}
beanFactory.registerSingleton(controllerBeanName, controller); //注册单例bean
beanFactory.autowireBean(controller); //自动注入
addControllerMapping(controllerBeanName);
}
private void removeOldControllerMapping(String controllerBeanName) {
if (!beanFactory.containsBean(controllerBeanName)) {
return;
}
RequestMappingHandlerMapping requestMappingHandlerMapping = requestMappingHandlerMapping();
//remove old
Class<?> handlerType = ctx.getType(controllerBeanName);
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map handlerMethods = (Map) ReflectionUtils.getField(handlerMethodsField, requestMappingHandlerMapping);
MultiValueMap urlMapping = (MultiValueMap) ReflectionUtils.getField(urlMapField, requestMappingHandlerMapping);
final RequestMappingHandlerMapping innerRequestMappingHandlerMapping = requestMappingHandlerMapping;
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new ReflectionUtils.MethodFilter() {
@Override
public boolean matches(Method method) {
return ReflectionUtils.invokeMethod(
getMappingForMethodMethod,
innerRequestMappingHandlerMapping,
method, userType) != null;
}
});
for (Method method : methods) {
RequestMappingInfo mapping =
(RequestMappingInfo) ReflectionUtils.invokeMethod(getMappingForMethodMethod, requestMappingHandlerMapping, method, userType);
handlerMethods.remove(mapping);
Set<String> patterns =
(Set<String>) ReflectionUtils.invokeMethod(getMappingPathPatternsMethod, requestMappingHandlerMapping, mapping);
PathMatcher pathMatcher =
(PathMatcher) ReflectionUtils.invokeMethod(getPathMatcherMethod, requestMappingHandlerMapping);
for (String pattern : patterns) {
if (!pathMatcher.isPattern(pattern)) {
urlMapping.remove(pattern);
}
}
}
}
private void addControllerMapping(String controllerBeanName) {
removeOldControllerMapping(controllerBeanName);
RequestMappingHandlerMapping requestMappingHandlerMapping = requestMappingHandlerMapping();
//spring 3.1 开始
ReflectionUtils.invokeMethod(detectHandlerMethodsMethod, requestMappingHandlerMapping, controllerBeanName);
}
private RequestMappingHandlerMapping requestMappingHandlerMapping() {
try {
return ctx.getBean(RequestMappingHandlerMapping.class);
} catch (Exception e) {
throw new IllegalArgumentException("applicationContext must has RequestMappingHandlerMapping");
}
}
private void removeInjectCache(Object controller) {
AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor =
ctx.getBean(AutowiredAnnotationBeanPostProcessor.class);
Map<String, InjectionMetadata> injectionMetadataMap =
(Map<String, InjectionMetadata>) ReflectionUtils.getField(injectionMetadataCacheField, autowiredAnnotationBeanPostProcessor);
injectionMetadataMap.remove(controller.getClass().getName());
}
private void startScriptModifiedCheckThead(final Long scriptCheckInterval) {
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(scriptCheckInterval);
Map<String, Long> copyMap = new HashMap<>(scriptLastModifiedMap);
for (String scriptLocation : copyMap.keySet()) {
if (scriptNotExists(scriptLocation)) {
scriptLastModifiedMap.remove(scriptLocation);
//TODO remove handler mapping ?
}
if (copyMap.get(scriptLocation) != scriptLastModified(scriptLocation)) {
registerGroovyController(scriptLocation);
}
}
} catch (Exception e) {
e.printStackTrace();
//ignore
}
}
}
}.start();
}
private long scriptLastModified(String scriptLocation) {
try {
return ctx.getResource(scriptLocation).getFile().lastModified();
} catch (Exception e) {
return -1;
}
}
private boolean scriptNotExists(String scriptLocation) {
return !ctx.getResource(scriptLocation).exists();
}
}