Spring 调配半天没搞定,原来是 web.xml 应该放在 WEB-INF 的目录下,而不是 webcontent 目录下;
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener ;一个错误引发的血案,整个上午外加大半个下午我都在搞清楚这个东西,原来是因为 lib 下面没有拷贝引用的 jar ;在 eclipse 中有一个 Library , Web App Library ,这个目录直指 WEB-INF/lib ,配置是在 .settings 下面的 org.eclipse.wst.common.component , wb-resource 节点中配置了 /WebContent ,意味着该目录下面的 jar 将会用于编译; Web 工程其实是需要将需要的 Jar 包放置到 lib 下面,然后 Web app Library 将会自动感应出来(在 Build Path 中的 Web app Library 中显示出来);也就是说: Web 项目是不需要 ref lib ,只要维护好 web app lib 就可以;
Spring 中定义的 servlet 节点定义了 dispachter 类以及加载 bean 的内心;如果没有通过 param-init 节点进行 contextConfigLocation 节点指定,那么默认的加载的路径为 WEB-INF 下面的 [servletname]-servlet ;如果指定了则从指定的文件中加载 bean 文件;
BeanNameUrlHandlerMapping ,是最基本的 Spring 的 Url 和 Handler 处理器,如果没有声明任何 mapping ,默认就是使用它;通过分析请求资源路径,来映射到指定的 Controller 中;和 Mapping 配套的定义的就是 adapter , Spring 的内部机制是 dispatcher 接收到请求,然后根据定义,实例化 Mapping ,然后 mapping 根据定义的 adapter 类型到 adapter 池中去找匹配的 Handler ( Handler 必须继承和 adapter 相一致的 handler ,比如 Handler 是 SimpleControllerHandlerAdapter ,那么 Controller 就要继承自 Controller 并实现 requestHandler ;那要是 HttpRequestHandleAadpter ,那么 controller 就要实现 HttpRequestHandler ,并实现里面定义的函数; mapping 是决定解析的机制,即 request 和 handler ( controller )的映射,(是注解的还是 beanurl ); adapter 则定义了 controller 的根是什么样的,用于 mapping 去定位;
Spring 可以在声明 bean 的时候,指定 cacheSeconds 来进行缓存等对于 response 的设定;
ETags : Entity Tags ,用来表明请求资源是否有被改变,多半都是 Web 服务器内置支持,比如 Apache , tomcat 都支持对 ETag 的判断;
Spring 的第三个基本 Bean 就是 ViewResolver (前两个 mapping 以及 handlerAdapter) ,他定义了如何根据指定的 ViewId ,找到对应的页面,这个 bean 里面定义了 jsp 文件路径以及需要处理的文件类型( .jsp );
WebContentGenerator 里面定义了很多和 Http-response 相关的内容,比如 method ,是否需要 session ( isRequireSession ,如果请求没有对应的 session 将会抛出异常, isUseCacheControlHeader ,是否使用缓存; AbstractController 继承自这个类,同时还实现了 controller ,所以它是比直接继承 Controller 拥有更加丰富的功能,可以在声明 bean 的时候对于这些 Http 请求 / 相应做一些约束和指令;
如果是 properties 文件发生了变化, tomcat 会探测到并进行自动重新加载配置文件,但是对于 sping 的配置文件发生了变化,则没有探测出来;不做任何动作;
调试 MVC 的时候,一度总是资源没有找到,后来发现是因为 URL 中应该添加工程名称 http://localhost:8002/simplemvc-chapter2/hello ;
simpleFormController 的 property 里面定义了两个 View , FormView 以及 SuccessView ,前者用来定义请求返回页面,后者用来定义成功页面( doSubmitAction 没有抛出异常); 这就是 request-Post 页面的配置形式 <bean name="/reg" class="cn.javass.chapter2.web.controller.RegisterController"> <property name="formView" value="register"/> <property name="successView" value="redirect:/success"/> </bean>
SimpleFormController 中处理提交机制为:如果提交的 name 不是 "_cancel" 那么就走 doSubmit 方法,如果是,那么就映射到 onCancel 方法中去;对于 redirect 关键字,代表着其实是相应先到客户端,然后再让客户端向目标地址再发请求,所以通过 Fidler 看到的其实是两个请求;比如取消提交,首先请求是 register ,服务器 response ,在 Response Header 中的" Transport "节点添加值为 Location: http://localhost:8002/simplemvc-chapter2/cancel 然后客户端再次向 Location 地址发送请求;
所以对于 formView, successView 以及 CancelView 指代都是返回到客户端的; redirect 不过是在 Response Header 里面的 Transport 添加了 Redirect 的地址而已;
setViewName 其实是和 ViewResolver 进行接力, ViewResolver 里面定义了到哪里去找 View 对应的 JSP , Controller 返回 ModelAndView 之后, ViewResolver 将会根据 ViewName 附加 .jsp 去寻找对应的页面来进行渲染(还包括 ${...} 占位符的填充; Spring 正常情况只能制定一种 ViewResolver (只能处理一种文件);
Spring 的 AbstractCommandController 提供了 command 对象,用来接收 queryString 或者 Form 中内容,其中 PropertyEditorSupport 就是用于验证提交的内容的;首先需要重写 controller 中 initBinder 方法;通知 Controller 在填充 Command 的时候那些类型(除了原生类型之外的)需要进行处理,以及处理的类什么: binder.registerCustomEditor(UserModel.class, new UserNameEditor()); 注意, UserMode 是 Command 类型的一个字段类型( Comand 需要在构造函数的时候指定);构造 UserNameEditor 的时候注意:继承自 PropertyEditorSupport ,重写 setAsText ;整个的处理流程是: QueryString ( Form )到了 dispatcher ,再根据一些配置决定将请求转交给 AbstractCommandController 的某个类,构造 Command 对象,在给属性进行复制的时候,首先判断这个属相的类型是否有被注册为自定义处理( registerCustomerEditor ),如果是有,则调用注册的 editor 的 setAsText 方法进行处理,构造该属性;昨天晚上我弄到 12 点,就是因为错误的理解为, Spring 将会递归遍历 UserModelContainer 中所以字段找到同名属性进行赋值;但是真实情况是: UserName 其实是直接付给 UserContainer 中同名属性(只是查看一级),如果同名属性被注册为 CustomerEditor ,那么进行自定义处理;其实 PropertyEditorSupport 主要不是做类型校验,而是做类型转换,比如 QueryString 中 addree=LN-DL- 高新区,那么可以设计一个 Address 对象,以" - "做分隔符,来为 Province , City 以及 Street 三字段赋值;
作为 BindException 类型通过 getModel() 方式获得 command 对象并传递到同台, <form:form><form:error path="*">... 解析机制并不是找 modelView.addObject(key, command) 里面的 key ,而是解析 command 里面的 key 值,这个 key 值是 AbstractCommandController 中构造函数时候调用 setCommandName(key) 是指定的 key 值;当然,对于页面元素的渲染,使用的是 addObject 的时候指定的 Key 值来做索引,比如 value="${objectKey.username}" ; form:form/error 是基于自身的 key
setViewName 其实是和 ViewResolver 进行接力, ViewResolver 里面定义了到哪里去找 View 对应的 JSP , Controller 返回 ModelAndView 之后, ViewResolver 将会根据 ViewName 附加 .jsp 去寻找对应的页面来进行渲染(还包括 ${...} 占位符的填充;