最近看了下Spring MVC的架构,的确是继承了Spring的一贯风格和优秀的编程思想,具体不说了,今天分享下自己学习的一点心得。
直接看我们的controller 类:
@Controller
public class LoginAction{
@RequestMapping(value = "login.do", method = RequestMethod.POST)
protected ModelAndView onSubmit(LoginInfo cmd)
throws Exception {
if (login(cmd) == 0) {
HashMap result_map = new HashMap();
result_map.put("logininfo", loginInfo);
List msgList = new LinkedList();
msgList.add("msg1");
msgList.add("msg2");
msgList.add("msg3");
result_map.put("messages", msgList);
return new ModelAndView("main", result_map);
} else {
return new ModelAndView("loginfail");
}
}
private int login(LoginInfo loginInfo) {
if ("aine".equalsIgnoreCase(loginInfo.getUsername())
&& "aine".equals(loginInfo.getPassword())) {
return 0;
}
return 1;
}
}
看到这里不知道有没有人跟我一样,奇怪,Spring怎么组织LoginInfo这个实体类给controller的?
大概的流程应该是:前端HttpRequest到Web容器,而Web容器转给核心中转类(servlet)DispatcherServlet,而这个类里获取当前需要的实体类,并且实例化,填上request中的值,调AP端的controller类。最后在我们的controller类中就可以直接使用已经封装请求值实体类了。
现在我们就关注DispatcherServlet是如何获取并且动态封装交易数据的。
1 我们知道在启动Spring的时候,我们会注册所有的Bean,封装在HandlerMapping中。
2 当请求收到后,系统会根据请求中的action到Map中去取HandlerExecutionChain。在HandlerExecutionChain中封装了所有的Interceptors和我们的真正的controller handler。
3 处理完controller handler之后,DispatcherServlet会获得一个View,框架会根据这个View组织页面到Client端页面。
用上面的case为例,真实的Handler是LoginAction。
1 框架在调用onSubmit(方法名不重要)之前会分析,当前的请求映射的方法需要哪些参数。因为我是通过注解的方式配置的,所以在AnnotationMethodHandlerAdapter中,我们可以看到:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
首先框架启动时会存储所有的类与Method的Mapping:
private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new HashMap<Class<?>, ServletHandlerMethodResolver>();
此时我们可以根据Handler Class找到Mapping的Method,通过ServletHandlerMethodInvoker类来调用invokeHandlerMethod方法(清楚起见,忽略了部分代码):
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
...
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
...
ReflectionUtils.makeAccessible(handlerMethodToInvoke);
return handlerMethodToInvoke.invoke(handler, args);
}
catch (IllegalStateException ex) {
throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowException(ex.getTargetException());
return null;
}
}
在handlerMethod中,我们已经可以获得当前Mapping的方法所有属性,包括其输入参数,返回值。最终我们必然会调用执行这个方法,但是在调用之前,我们来看看我们要做哪些事情。
1 处理BridgedMethod属性和方法(忽略之)。
2 resolveHandlerArguments,处理输入参数(重点)。
3 打开方法的访问性限制
4 正式的访问方法的逻辑
其中对输入参数的处理是我这次讲解的重点,我们继续看resolveHandlerArguments的处理逻辑(清楚起见,忽略部分代码)。
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Class[] paramTypes = handlerMethod.getParameterTypes();//获取所有参数
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < args.length; i++) {//遍历处理所有参数
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);//创建参数实例:LoginInfo
//其底层实现就是一句话:BeanUtils.instantiateClass(paramType);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(binder, webRequest, validate, !assignBindingResult);//开始操作参数实例的值了
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
}
其中最关键的是doBind方法了,我们继续往下看,框架会调用WebRequestDataBinder中的bind方法:
public void bind(WebRequest request) {
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());//遍历request,获取提交的所有请求参数和值
if (request instanceof NativeWebRequest) {
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
}
doBind(mpvs);
}
这里已经获取到所有的请求参数值了,离我们的目地更近一步了,下面继续看doBind(mpvs)。
这里必须要清楚我们的下一个主角了:BeanPropertyBindingResult,他拥有一个
private transient BeanWrapper beanWrapper;
相信有经验的人看到他,就知道离真相不远了。
在DataBinder中:
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
BeanWrapperImpl.setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
大家都知道BeanWrapperImpl是继承AbstractPropertyAccessor的,而setPropertyValues方法是基类实现的,我们看看他的实现逻辑:
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new LinkedList<PropertyAccessException>();
}
propertyAccessExceptions.add(ex);
}
}
// If we encountered individual exceptions, throw the composite exception.
if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray =
propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
throw new PropertyBatchUpdateException(paeArray);
}
}
setPropertyValue方法是BeanWrapperImpl子类实现的具体逻辑了,对于这个牛逼的类,我不太想在这里说太多了,了解Spring的人都知道他的牛逼之处,贴一下他的实现代码:
private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
try {
final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() :
pd.getWriteMethod());
final Object value = valueToApply;
writeMethod.invoke(this.object, value);//调用POJO中的setXX();
}
catch (TypeMismatchException ex) {
throw ex;
}
catch (InvocationTargetException ex) {
throw ex;
}
catch (Exception ex) {
throw ex;
}
}
}
这样返回的实体类就是已经封装好所有属性值的对象了。
至于为什么系统会根据属性名直接调用set属性名方法,可以看实现类GenericTypeAwarePropertyDescriptor:
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
Method readMethod, Method writeMethod, Class propertyEditorClass)
throws IntrospectionException {
super(propertyName, null, null);
this.beanClass = beanClass;
this.propertyEditorClass = propertyEditorClass;
Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod);
Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod);
if (writeMethodToUse == null && readMethodToUse != null) {
// Fallback: Original JavaBeans introspection might not have found matching setter
// method due to lack of bridge method resolution, in case of the getter using a
// covariant return type whereas the setter is defined for the concrete property type.
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());//这下彻底清除了吧?
}
this.readMethod = readMethodToUse;
this.writeMethod = writeMethodToUse;//这个是就最终的方法
}
总结一下:不管是使用什么方法配置Spring,实例化对象的底层实现是一致的,容器注册controller的时候已经登记了被Mapping的方法的所有属性,在请求访问的时候,通过BeanWrapperImpl动态实例化POJO并且赋值,在真正调用controller的时候,传入的参数已经是处理过的了,所以在controller中可以直接使用。
不知道这样写大家能不能看出个所以然,要写一篇逻辑完整严谨的文章真的不容易,以后在多努力吧。
分享到:
相关推荐
在Spring MVC中,数据绑定是一项核心功能,它允许开发者将用户输入的数据与控制器中的对象属性进行关联,简化了数据处理的复杂性。本文将详细介绍Spring MVC中的数据绑定,并提供实例帮助初学者理解。 1. **模型...
3. **默认配置不当**:Spring MVC的默认配置可能允许过于宽松的数据绑定,例如允许空值绑定到非null字段,或者允许任意类型的参数绑定。 针对这些风险,开发者可以采取以下措施来增强Spring MVC应用的安全性: 1. *...
"Spring MVC数据绑定大全.rar"这个压缩包很可能包含了全面讲解Spring MVC数据绑定技术的资料,如"Spring MVC数据绑定大全.pdf"。下面将详细阐述Spring MVC数据绑定的关键知识点。 1. **注解驱动的数据绑定**:...
- **基本概念**:Spring MVC提供了一个灵活的MVC实现,包括请求映射、视图解析、模型绑定等功能。它通过DispatcherServlet作为前端控制器,负责接收请求并分发到相应的处理器。 - **组件**:主要包括Model、View、...
Spring MVC还支持数据绑定,自动将请求参数绑定到Java对象,这极大地简化了表单提交的处理。对于验证,Spring MVC提供了BindingResult和Validator接口,用于校验模型数据的正确性。 另外,Spring MVC与Spring框架的...
除此之外,Spring MVC还支持数据绑定、验证、本地化、主题、异常处理等功能。例如,使用@ModelAttribute注解可以将请求参数绑定到Controller方法的参数上,@Valid用于进行数据验证,Validator接口可以自定义验证逻辑...
以下是对Spring MVC数据绑定的深入解释: **数据绑定概述** 在Spring MVC中,数据绑定是通过`DataBinder`组件实现的。当一个HTTP请求到达控制器方法时,Spring MVC会自动创建一个`DataBinder`实例,并使用`...
6. **数据绑定与验证**:Spring MVC提供了数据绑定和验证功能,允许将表单数据自动绑定到Java对象,并进行校验,简化了业务逻辑处理。 7. **视图解析**:Spring MVC 4.0支持多种视图技术,如JSP、FreeMarker、...
5. **数据绑定和验证**:Spring MVC提供强大的数据绑定功能,将HTTP请求参数自动映射到处理方法的参数,并支持JSR-303/JSR-349 Bean Validation标准进行数据验证。 6. **异常处理**:通过定义全局异常处理器,可以...
总的来说,"Mastering Spring MVC 4(2015.09)源码"提供了深入学习Spring MVC的机会,你可以通过阅读和分析源码来了解如何配置DispatcherServlet、怎样编写控制器、如何进行数据绑定与验证,以及如何利用拦截器等特性...
Spring MVC支持自动的数据绑定,可以将请求参数直接绑定到Controller方法的参数上,也可以将表单数据绑定到Java对象。此外,@Valid用于验证模型数据,配合Validator接口实现自定义校验规则。 7. **异常处理** ...
1.创建第一个 Spring MVC 程序案例 ...11.Spring MVC 数据绑定案例 12.Spring MVC 实现 JSON 数据返回案例 13.Spring MVC 文件的上传与下载案例 14.Spring MVC 拦截器案例 15.Spring MVC 异常处理案例
这是一个基于Spring MVC、Mybatis和Spring框架实现的个人博客系统,涵盖了Web开发中的后端架构设计、数据库管理和前端展示等多个方面。以下将详细介绍这个系统的关键知识点: **1. Spring MVC** Spring MVC是Spring...
`org.springframework.web.servlet-3.1.1.RELEASE.jar` 是Spring MVC的主要组件,它负责处理HTTP请求,提供模型-视图-控制器的实现,包括控制器、视图解析和模型数据绑定等功能。 `org.springframework.web-3.1.1....
首先,Spring MVC的设计模式基于Model-View-Controller(MVC),它将应用程序的业务逻辑、数据和用户界面进行了分离,使得代码更加清晰、易于维护。在Spring MVC中,Controller处理用户的请求,Model存储数据,而...
8. **数据绑定与验证**:Spring MVC 提供了数据绑定功能,可以自动将请求参数映射到 Controller 方法的参数。同时,通过 @Valid 和 Validation API 可以实现数据校验。 9. **拦截器(Interceptor)**:拦截器可以在...
以上这些库构成了 Spring MVC 开发的基础环境,开发者可以利用它们来创建控制器、定义模型、配置视图解析器,以及实现事务管理、数据访问等复杂功能。通过 Spring MVC,开发者能够以声明式的方式组织应用程序,提高...
Spring MVC支持自动的数据绑定,将请求参数自动映射到Controller方法的参数上。同时,使用JSR-303/JSR-349提供的注解可以进行数据验证,如@NotNull、@Size等。 9. **上传下载** 使用MultipartFile处理文件上传,...