本帖最后由 逆风TO 于 2018-3-30 09:17 编辑
分析前提,分析的是注解方式配置的源码,不同的处理方式源码不相同研究目的,探讨springmvc参数绑定我们主要研究的问题
- springmvc怎么获取页面传入过来的数据
- springmvc怎么把界面传入的数据绑定到对象上
例如页面(input type="text" name="username")后台username是User的属性字段 - springmvc Controller类中的方法怎么执行的
- springmvc 中方法加注解和不加注解的区别
public String index(HttpServletRequest request, HttpServletResponse response, User user12, Person person, String keywordsa,
@RequestParam("keywords") String words,String keywords)
简单的springmvc项目搭建,采用注解方式进行开发 - 项目的下载地址
https://github.com/wolvesleader/simplespringmvc
从springmvc的入口开始查看代码段1
在org.springframework.web.servlet.DispatcherServlet类中 @Override
protected void doService(HttpServletRequest request,
HttpServletResponse response) throws Exception {
//省略我们不研究的代码
try {
doDispatch(request, response);
}
finally {
}
}
代码段2
在org.springframework.web.servlet.DispatcherServlet类中接着查找doDispatch方法代码如下所示 protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
}
}
代码段3
我们直接从注解类型适配器的源码中来看
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter public final ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler)throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
代码段4
接着看handleInternal源码
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter @Override
protected final ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
//忽略我们不研究的代码
return invokeHandlerMethod(request, response, handlerMethod);
}
代码段5
到此我们来看invokeHandlerMethod做了一些什么事情
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter private ModelAndView invokeHandlerMethod(
HttpServletRequest request,HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
}
代码段6
来看这里的代码
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod类中的
requestMappingMethod.invokeAndHandle(webRequest, mavContainer); public final void invokeAndHandle(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
}
代码段7
接着走我们去看
org.springframework.web.method.support.InvocableHandlerMethod类中的
Object returnValue = invokeForRequest(request, mavContainer, providedArgs); public Object invokeForRequest(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取参数并且封装
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
//执行Controller中的方法
Object returnValue = doInvoke(args);
return returnValue;
}
代码段8
我们先看getMethodArgumentValues(request, mavContainer, providedArgs);方法 private Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
//省略我们不研究的代码
args = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
}
return args;
}
代码段9 @Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
//根据方法中的参数获取到参数的解析器
//在代码段2中的getHandlerAdapter方法执行的时候就会把解析器准备好
//在次我们只要根据不同的解析器解析参数就可以了
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
//用获取到的参数解析器去解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
根据我们代码中的方法的参数 public String index(HttpServletRequest request,
HttpServletResponse response,
User user12, Person person,
String keywords,String keywordsa,
@RequestParam("keywords") String words)
我们结合源码可知需要以下的参数解析器,怎么是知道需要这些解析器,可以走断点调试
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver 代码段10
我们先来看对HttpServletRequest的设置 public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws IOException {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType)) {
Object nativeRequest = webRequest.getNativeRequest(paramType);
//省略不研究的代码
return nativeRequest;
}
}
代码段11
对HttpServletResponse的设置 public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws IOException {
HttpServletResponse response =
webRequest.getNativeResponse(HttpServletResponse.class);
Class<?> paramType = parameter.getParameterType();
if (ServletResponse.class.isAssignableFrom(paramType)) {
Object nativeResponse = webRequest.getNativeResponse(paramType);
return nativeResponse;
}
}
代码段12
对User的设置,这段代码非常的重要 public final Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest request, WebDataBinderFactory binderFactory)
throws Exception {
//获取方法中参数的参数名,注意是参数名不是参数类型
String name = ModelFactory.getNameForParameter(parameter);
Object target = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, request);
WebDataBinder binder = binderFactory.createBinder(request, target, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
mavContainer.addAllAttributes(binder.getBindingResult().getModel());
return binder.getTarget();
}
代码段13
先看String name = ModelFactory.getNameForParameter(parameter);获取方法的参数名
org.springframework.web.method.annotation.ModelFactory public static String getNameForParameter(MethodParameter parameter) {
//根据注解获取方法的参数名,如果没有获取直接以类名首字母小写做为参数名
//自定义的对象这个参数名没有任何影响,因为我们获取界面的参数要根据
//类中的字段去获取可以在Conventions.getVariableNameForParameter(parameter)中查看
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
String attrName = (annot != null) ? annot.value() : null;
return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
}
代码段14
到此找到了自定义对象的参数名,在代码段12中的第7-9行中从容器中根据名字获取对象,如果获取不到会创建一个对象 @Override
protected final Object createAttribute(String attributeName,
MethodParameter parameter,
WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception {
String value = getRequestValueForAttribute(attributeName, request);
if (value != null) {
Object attribute = createAttributeFromRequestValue(value,
attributeName, parameter, binderFactory, request);
if (attribute != null) {
return attribute;
}
}
return super.createAttribute(attributeName, parameter, binderFactory, request);
}
代码段15
接着看怎么创建对象的
在org.springframework.web.method.annotation.ModelAttributeMethodProcessor类中的方法 protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
return BeanUtils.instantiateClass(parameter.getParameterType());
}
可以看到是根据类型通过反射创建出来一个对象,到此对象已经有了,我们要根据类中的字段从request中获取
代页面传入的参数并且封装,在代码段12第13行中
org.springframework.web.bind.ServletRequestDataBinder
代码段16 @Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}
代码段17
在走到org.springframework.web.bind.ServletRequestDataBinder public void bind(ServletRequest request) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest =
WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
doBind(mpvs);
}
代码段18
在代码段17中需要先看一下MutablePropertyValues这个对象,因为在这里做了获取页面传入的数据 public ServletRequestParameterPropertyValues(ServletRequest request) {
this(request, null, null);
}
org.springframework.web.bind.ServletRequestParameterPropertyValues中 public ServletRequestParameterPropertyValues(ServletRequest request,
String prefix, String prefixSeparator) {
super(WebUtils.getParametersStartingWith(
request, (prefix != null ? prefix + prefixSeparator : null)));
}
来到了核心类org.springframework.web.util.WebUtils public static Map<String, Object> getParametersStartingWith(ServletRequest request,
String prefix) {
Assert.notNull(request, "Request must not be null");
Enumeration paramNames = request.getParameterNames();
Map<String, Object> params = new TreeMap<String, Object>();
if (prefix == null) {
prefix = "";
}
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
if ("".equals(prefix) || paramName.startsWith(prefix)) {
String unprefixed = paramName.substring(prefix.length());
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
}
else if (values.length > 1) {
params.put(unprefixed, values);
}
else {
params.put(unprefixed, values[0]);
}
}
}
return params;
}
到这里我们可以看到获取到了页面属性的字段名,和字段对应的值,放在了mpvs中
接着代码段17中的第9行继续走来到了org.springframework.web.bind.WebDataBinder中的方法 @Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
org.springframework.validation.DataBinder中的方法 protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs,
isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
}
}
接着走会去到org.springframework.beans.AbstractPropertyAccessor类中的setPropertyValues方法
在这里我们就在不向下走了,它主要就是根据PropertyDescriptor获取到类的字段然后调用set方法设置值 对Person设置值和User相同
代码段19
对String类型的keywords设置值,首先看我们这个String类型是没有添加注解,springmvc封装原则先
根据ASM框架获取参数名,根据参数名在request对象中获取值然后放在args数组中
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver中的解析器 public final Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
//忽略我们不研究的代码
return arg;
}
代码段20
在同一个类中根据参数去获取 private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
代码段21 @Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
return (annotation != null) ?
new RequestParamNamedValueInfo(annotation) :
new RequestParamNamedValueInfo();
}
代码段22
从上代码可以看出,如果参数没有加注解封装为了new RequestParamNamedValueInfo()对象 private NamedValueInfo updateNamedValueInfo(MethodParameter parameter,
NamedValueInfo info) {
String name = info.name;
if (info.name.length() == 0) {
name = parameter.getParameterName();
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ?
null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}
代码段23
在org.springframework.core.MethodParameter类中可以看到由于没有加注解,所以需要在参数列表中找参数名 public String getParameterName() {
if (this.parameterNameDiscoverer != null) {
//获取到参数列表
String[] parameterNames = (this.method != null ?
this.parameterNameDiscoverer.getParameterNames(this.method) :
this.parameterNameDiscoverer.getParameterNames(this.constructor));
if (parameterNames != null) {
//获取到参数列表,根据参数索引获取到参数的名字
this.parameterName = parameterNames[this.parameterIndex];
}
this.parameterNameDiscoverer = null;
}
return this.parameterName;
}
代码段24
获取参数列表是根据ASM,框架的org.springframework.core.LocalVariableTableParameterNameDiscoverer public String[] getParameterNames(Method method) {
Class<?> declaringClass = method.getDeclaringClass();
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
if (map == null) {
// initialize cache
map = inspectClass(declaringClass);
this.parameterNamesCache.put(declaringClass, map);
}
if (map != NO_DEBUG_INFO_MAP) {
return map.get(method);
}
return null;
}
到次位置获取到了参数名,我们需要根据参数名从request中获取页面传入的值,回到代码段19中的第8行
进入到org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
我们发现,是一个抽象的方法,需要去找它的实现类
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
代码段25 protected abstract Object resolveName(String name, MethodParameter parameter,
NativeWebRequest request)throws Exception;
代码段26 @Override
protected Object resolveName(String name, MethodParameter parameter,
NativeWebRequest webRequest) throws Exception {
Object arg;
//忽略我们不关注的代码
else {
arg = null;
if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
//从请求中获取到参数所对应的值
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
}
return arg;
}
可以看到获取到了值,返回放在了args数组中,执行方法的时候作为方法的参数使用
再来看对String类型带有注解的设置@RequestParam("keywords") String words
和没有加注解不同的是在代码段21中new RequestParamNamedValueInfo(annotation)
创建了带有注解的对象,这样在代码段22中不会走第5行的代码,其他的全部相同 以上做了这么多事情主要是为了给执行方法准备方法的参数值
我们在回到代码段7的第7行Object returnValue = doInvoke(args);方法中
在org.springframework.web.method.support.InvocableHandlerMethod类中
一切准备就绪通过反射执行方法 private Object invoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(this.getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException e) {
String msg = getInvocationErrorMessage(e.getMessage(), args);
throw new IllegalArgumentException(msg, e);
}
}
小知识根据ASM获取方法的参数名 package com.quincy.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public interface ParameterNameDiscoverer {
String[] getParameterNames(Method method);
String[] getParameterNames(Constructor ctor);
}
package com.quincy.demo;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.quincy.demo.ParameterNameDiscoverer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.asm.commons.EmptyVisitor;
import org.springframework.util.ClassUtils;
public class LocalVariableTableParameterNameDiscoverer implements
ParameterNameDiscoverer {
private final Map<Class<?>, Map<Member, String[]>> parameterNamesCache =
new ConcurrentHashMap<Class<?>, Map<Member, String[]>>();
public String[] getParameterNames(Method method) {
Class<?> declaringClass = method.getDeclaringClass();
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
if (map == null) {
// initialize cache
map = inspectClass(declaringClass);
this.parameterNamesCache.put(declaringClass, map);
}
if (map != NO_DEBUG_INFO_MAP) {
return map.get(method);
}
return null;
}
@SuppressWarnings("unchecked")
public String[] getParameterNames(Constructor ctor) {
Class<?> declaringClass = ctor.getDeclaringClass();
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
if (map == null) {
// initialize cache
map = inspectClass(declaringClass);
this.parameterNamesCache.put(declaringClass, map);
}
if (map != NO_DEBUG_INFO_MAP) {
return map.get(ctor);
}
return null;
}
private Map<Member, String[]> inspectClass(Class<?> clazz) {
InputStream is =
clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
if (is == null) {
return NO_DEBUG_INFO_MAP;
}
try {
ClassReader classReader = new ClassReader(is);
Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>();
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map),
false);
return map;
}
catch (IOException ex) {
}
finally {
try {
is.close();
}
catch (IOException ex) {
// ignore
}
}
return NO_DEBUG_INFO_MAP;
}
private static class ParameterNameDiscoveringVisitor extends EmptyVisitor {
private static final String STATIC_CLASS_INIT = "<clinit>";
private final Class<?> clazz;
private final Map<Member, String[]> memberMap;
public ParameterNameDiscoveringVisitor(Class<?> clazz,
Map<Member, String[]> memberMap) {
this.clazz = clazz;
this.memberMap = memberMap;
}
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
// exclude synthetic + bridged && static class initialization
if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) {
return new LocalVariableTableVisitor(clazz, memberMap,
name, desc, isStatic(access));
}
return null;
}
private static boolean isSyntheticOrBridged(int access) {
return (((access & Opcodes.ACC_SYNTHETIC) |
(access & Opcodes.ACC_BRIDGE)) > 0);
}
private static boolean isStatic(int access) {
return ((access & Opcodes.ACC_STATIC) > 0);
}
}
private static class LocalVariableTableVisitor extends EmptyVisitor {
private static final String CONSTRUCTOR = "<init>";
private final Class<?> clazz;
private final Map<Member, String[]> memberMap;
private final String name;
private final Type[] args;
private final boolean isStatic;
private String[] parameterNames;
private boolean hasLvtInfo = false;
private final int[] lvtSlotIndex;
public LocalVariableTableVisitor(Class<?> clazz, Map<Member, String[]> map,
String name, String desc, boolean isStatic) {
this.clazz = clazz;
this.memberMap = map;
this.name = name;
// determine args
args = Type.getArgumentTypes(desc);
this.parameterNames = new String[args.length];
this.isStatic = isStatic;
this.lvtSlotIndex = computeLvtSlotIndices(isStatic, args);
}
@Override
public void visitLocalVariable(String name, String description,
String signature, Label start, Label end,int index) {
this.hasLvtInfo = true;
for (int i = 0; i < lvtSlotIndex.length; i++) {
if (lvtSlotIndex == index) {
this.parameterNames = name;
}
}
}
@Override
public void visitEnd() {
if (this.hasLvtInfo || (this.isStatic &&
this.parameterNames.length == 0)) {
memberMap.put(resolveMember(), parameterNames);
}
}
private Member resolveMember() {
ClassLoader loader = clazz.getClassLoader();
Class<?>[] classes = new Class<?>[args.length];
// resolve args
for (int i = 0; i < args.length; i++) {
classes = ClassUtils.
resolveClassName(args.getClassName(), loader);
}
try {
if (CONSTRUCTOR.equals(name)) {
return clazz.getDeclaredConstructor(classes);
}
return clazz.getDeclaredMethod(name, classes);
} catch (NoSuchMethodException ex) {
}
}
private static int[] computeLvtSlotIndices(boolean isStatic,
Type[] paramTypes) {
int[] lvtIndex = new int[paramTypes.length];
int nextIndex = (isStatic ? 0 : 1);
for (int i = 0; i < paramTypes.length; i++) {
lvtIndex = nextIndex;
if (isWideType(paramTypes)) {
nextIndex += 2;
} else {
nextIndex++;
}
}
return lvtIndex;
}
private static boolean isWideType(Type aType) {
// float is not a wide type
return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE);
}
}
}
测试代码 public class DemoDriver {
public static void main(String[] args) {
Class<User> userClass = User.class;
LocalVariableTableParameterNameDiscoverer tableParameterNameDiscoverer =
new LocalVariableTableParameterNameDiscoverer();
Method[] methods = userClass.getMethods();
for (Method m: methods) {
System.out.println("方法");
String[] parameterNames =
tableParameterNameDiscoverer.getParameterNames(m);
for (String name: parameterNames) {
System.out.println("方法参数名称");
System.out.println(name);
}
}
}
}
总结分析完源码我们来总结我们提出的问题 - springmvc怎么获取页面传入过来的数据
根据request对象来获取,和我们学习Servlet中获取参数基本相同 - springmvc怎么把界面传入的数据绑定到对象上
例如页面(input type="text" name="username")后台username是User的属性字段
根据我们的参数列表的数据类型创建对象,通过反射给对象设置值 - springmvc Controller类中的方法怎么执行的
通过反射执行 - springmvc 中方法加注解和不加注解的区别
public String index(HttpServletRequest request, HttpServletResponse response, User user12, Person person, String keywordsa,
@RequestParam("keywords") String words,String keywords)
对于RequestParam注解来说需要加上,这样少走了ASM解析获取参数列表的过程,比较好
|