基于javassist技术和RequestMappingHandlerMapping实现自定义|动态注册spring mvc HandlerMapping的迭代记录

项目以前为servlet 项目。因产品架构升级为多产品SOA架构,想基于现有的项目架构作快速集成, 因此将其升级到spring。 开始方案为:因项目action处理模型与spring mvc不一样,所以只取spring ,而不使用spring mvc。即便用注解 @ServletComponentScan 启用servlet注解方式集成spring。java

但在后续集成spring-cloud-starter-consul-discovery和config时发现,consul-discovery 客户端注册consul不能选择TTL模式进行注入(见本文底),并配置config 后,刷新配置逻辑须要重写。git

所以打算将原action动态注册到dispatcherServlet。查看spring 源码和资料后,找到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping能够对spring的Handler进行管理。github

方案2:注册自定义Action到spring RequestMapping中。web

在原系统中,action的结构为:spring

其中action1 run 对应接口 /web/action1/runapi

会话执行时序图为:架构

思路经过使用javassist生成一个映射action.method执行的代理(装饰)实例。该实例能冒充spring StaticHandleMethod。将action整合到为springMVC中。思路经过使用javassist生成一个映射action.method执行的代理(装饰)实例。该实例能冒充spring StaticHandleMethod。将action整合到为springMVC中。

1. mvc 中 RequestMappingHandler 在Bean RequestMappingHandlerMapping中注册注册信息为 1).org.springframework.web.servlet.mvc.method.RequestMappingInfo handlerMethod 请求条件注解信息 2).org.springframework.web.method.HandlerMethod 请求处理程序信息 接口为:mvc

/**
 * Register the given mapping.
 * <p>This method may be invoked at runtime after initialization has completed.
 * [@param](https://my.oschina.net/u/2303379) mapping the mapping for the handler method
 * [@param](https://my.oschina.net/u/2303379) handler the handler
 * [@param](https://my.oschina.net/u/2303379) method the method
 */
public void registerMapping(T mapping, Object handler, Method method)

2. 使用 javassist 生成某method 的代理method method 定义主要分为 注解 定义 方法主体。定义中包含有 修饰符 返回类型 方法名称 参数(参数类型及参数名称) 其中注解 修饰符 返回类型 方法名称 参数类型都可经过 java.lang.reflect.Method 得到。 参数名称能够经过jdk1.8 或者asm方式获取。app

ClassPool pool = ClassPool.getDefault();
//生成特殊惟一的HandlerClass
CtClass handleCtClz = pool.makeClass(Handler.class.getName() + "$" + c.getSimpleName()+ "$" + m.getName() + "K" + MD5.md5(c.getName() + m.getName()) + "R"+ (new Random().nextInt(1024)), pool.get(Handler.class.getName()));
//对HandlerClass操做,生成Method
// 设置方法名 修饰符 返回类型
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(m.getModifiers()))// 修饰符
		.append(" ").append(m.getReturnType().getName())// 放回类型
		.append(" ").append(m.getName())// 方法名
		.append("(");
// 设置参数
List<String> pars = new ArrayList<>();
for (int i = 0; i < m.getParameterCount(); i++) {
	StringBuilder sb2 = new StringBuilder();
	sb2.append(m.getParameterTypes()[i].getName()).append(" ")
		.append(u.getParameterNames(m)[i]);
	pars.add(sb2.toString());
}
pars.add("javax.servlet.http.HttpServletRequest req");
pars.add("javax.servlet.http.HttpServletResponse resp");
sb.append(StringUtils.join(pars, " , ")).append(")").append("\n throws Throwable ")//参数即异常声明
		//.append(StringUtils.join(Stream.of(m.getExceptionTypes()).map(Class::getName),","))
		.append("{\n");
// body 返回值 及 case
if (!m.getReturnType().getName().equals("void")) {
	sb.append("return  (").append(m.getReturnType().getName()).append(") ");
}
sb.append("invoke(req, resp, ");
if (m.getParameterCount() == 0)
	sb.append("new Object[0]");
else
	sb.append("new Object[] {").append(StringUtils.join(u.getParameterNames(m), " , "))
			.append("}");
sb.append(");\n }");
CtMethod newCtMethod = CtNewMethod.make(sb.toString(), handleCtClz);
// TODO 注解
handleCtClz.addMethod(newCtMethod);

以上便完成了method 的生成,但在spring 中有一套详细的功能注解。若是须要使用,那么还得把注解进行拷贝; 注解不能强制经过方法定义字符串进行定义,但能经过AnnotationsAttribute进行定义。dom

ClassFile ccFile = handleCtClz.getClassFile();
ConstPool constpool = ccFile.getConstPool();
AnnotationsAttribute methodAttr = new AnnotationsAttribute(constpool,
		AnnotationsAttribute.visibleTag);
MethodInfo info = newCtMethod.getMethodInfo();
info.addAttribute(methodAttr);
// method 注解拷贝
for (Annotation a : m.getAnnotations()) {
	methodAttr.addAnnotation(toJavassistAnnotation(a.annotationType().getName(), a, constpool));
}
ParameterAnnotationsAttribute paa = new ParameterAnnotationsAttribute(constpool,ParameterAnnotationsAttribute.visibleTag);
javassist.bytecode.annotation.Annotation[][] nAs = new javassist.bytecode.annotation.Annotation[m.getParameterCount() + 2][];
Annotation[][] as = m.getParameterAnnotations();
for (int i = 0; i < as.length; i++) {
	if (as[i].length == 0) {
		nAs[i] = new javassist.bytecode.annotation.Annotation[1];
		nAs[i][0] = getDefaultRequestParam(u.getParameterNames(m)[i], constpool);
	} else {
		nAs[i] = new javassist.bytecode.annotation.Annotation[as[i].length];
		for (int j = 0; j < as[i].length; j++) {
			nAs[i][j] = toJavassistAnnotation(as[i][j].annotationType().getName(),as[i][j], constpool);
		}
	}
}
nAs[nAs.length - 2] = new javassist.bytecode.annotation.Annotation[0];
nAs[nAs.length - 1] = new javassist.bytecode.annotation.Annotation[0];
paa.setAnnotations(nAs);
info.addAttribute(paa);

最后,经过生成的handlerclass得到实例,注册到requestMappingHandlerMapping中,便可使用。

Class<?> clazz = appClassLoader.findClassByBytes(handleCtClz.getName(),handleCtClz.toBytecode());
obj = new Handler(handleAction);
obj = (Handler) getObj(clazz, obj, new Class[] { HandlerAction.class },new Object[] { handleAction });
List<Class<?>> li = new ArrayList<>();
Stream.of(m.getParameterTypes()).forEach(li::add);
li.add(HttpServletRequest.class);
li.add(HttpServletResponse.class);
newMethod = clazz.getMethod(m.getName(), li.toArray(new Class<?>[] {}));
requestMappingHandlerMapping.registerMapping(minfoB.build(), obj, newMethod);

结果:

包结构及Action示意:

结果截图:

关于方案一中出现的问题:

1.@ServletComponentScan启用后,dispatcherServlet不被启用。consul-discovery原打算使用ttl模式注册,但发现逻辑存在问题,参见

https://github.com/spring-cloud/spring-cloud-consul/issues/470 具体为org.springframework.cloud.consul.discovery.TtlScheduler中,checkId被写死了"service:",致使不存在对应service。所以只得手写。 ConsulHeartbeatTask(String serviceId) { this.checkId = serviceId; if (!checkId.startsWith("service:")) { checkId = "service:" + checkId; } }

项目关键技术demo:

github: https://github.com/yuyizyk/note/tree/master/notes/spring-dynamic-handlerMapping

相关文章
相关标签/搜索