Spring MVC即Spring的web模块
M:Model模型;封装和映射数据(javaBean)
V: View视图;界面显示工作(.jsp)
C:Controller控制器;控制整个网站的跳转逻辑(Servlet)
MVC提倡:每一层只编写自己的东西,不写任何其他代码
控制器{
调用业务逻辑处理
调整到某个页面
}
分层就为了解耦,解耦为了维护方便和分工合作
POJO:Plain Old Java Object(普通的Java对象,不是JavaBean、EntityBean、SessionBean);
<!-- 配置前端控制器DispacherServlet-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- contextConfigLocation:指定Springmvc配置文件位置-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- servlet启动加载,servlet原本是第一次访问创建对象-->
<!-- load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!--
/*和/都是拦截所有请求; /:会拦截所有请求,但是不会拦截*.jsp;能保证jsp访问正常
/*的范围更大:还会拦截到*.jsp这些请求;一旦拦截jsp页面就不能显示了;
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置一个视图解析器;能帮我们拼接页面地址-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
运行流程说明: 1)客户端点击链接会发送http://localhost:8080/1_SpringMVC_helloWorld/hello请求 2)来到tomcat服务器 3)SpringMVC的前端控制器收到所有请求; 4)来看请求地址和@RequestMapping标注的那个匹配,来找到到底使用哪个类的哪个方法处理 5)前端控制器找到了目标处理器和目标方法,直接利用返回执行目标方法; 6)方法执行完成以后会有一个返回值;Spinrgmvc认为这个返回值就是要去的页面地址 7)拿到方法返回值以后;用视图解析器进行拼串得到完整的页面地址 8)拿到页面地址,前端控制器帮我们转发到页面
@RequestMapping: 就是告诉springmvc这个方法用来处理什么请求; /可以省略,即使省略了,也是默认从当前项目下开始;
如果在web.xml不指定spring配置文件的位置 指定:运行会寻找/WEB-INF/springDispatcherServlet-servlet.xml 不指定:会默认去找/WEB-INF/xxx-servlet.xml 不指定时为了不报错误,就在/WEB-INF/下创建一个名叫 前端控制器名(需要跟配置DispatcherServlet的名一致)-servlet.xml,即若在web.xml中已经配置好一个前端控制名叫springmvc的话,需要在/WEB-INF/下创建一个名叫springmvc-servlet.xml的文件(原来的配置文件内容可以放在这个文件,且可以删除spring的配置文件了)
url-pattern问题 /:拦截所有请求,不包括拦截jsp页面, /*:拦截所有请求
处理*.jsp是tomcat做的事;所有项目的小web.xml都是继承于大web.xml DefaultServlet是tomcat中处理静态资源的 除了jsp,servlet外剩下的都是静态资源; index.html:静态资源,tomcat就会在服务器下找到这个资源并返回; 我们前端控制器的/禁用了tomcat服务器中的DefaultServlet
URL地址可以写模糊的通配符 ?:能替代任意一个字符 *:能替代任意多个字符,和一层路径 **:能替代多层路径 精确的匹配就是上面的那些 模糊匹配eg: @RequestMapping("/antTest0?"):请求/antTest0后接一个字符都可以,只写到0的请求也不行
REST:即 Representational State Transfer。(资源)表现层状态转化。
系统希望以非常简洁的URL地址来发送请求; 怎样表示对一个资源的增删改查请求方式来区分 以前的请求: /getBook?id=1 :查询图书 /deleteBook?id=1 :删除1号图书 /updateBook?id=1 :更新1号图书 /addBook : 添加图书
Rest风格 url地址这么起名:/资源名/资源标识符 /book/1 : GET--->查询1号图书 /book/1 :PUT--->更新1号图书 /book/1 : DELETE--->删除1号图书 /book :POST-->添加图书
从页面发起PUT、DELETE形式的请求??Spring提供了对Rest风格的支持
SpringMVC如何获取请求带来的各种信息 默认方式获取请求参数: 1)直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接受请求参数的值 带:有值 ; 没带:null
@RequestParam("user")String username == username = request.getParameter("user"); 使用该注解时,user默认是必须在请求参数中的 该注解可以配置的属性值有:
@RequestParam与@PathVariable的区别: ① @RequestParam是获取请求参数的值,即?后面的值 ② @PathVariable是获取路径上占位符表示的值
以前获取请求头:request.getHeader("User-Agent"); eg:@RequestHeader("User-Agent")String userAgent userAgent = request.getHeader("User-Agent") 如果请求头没有这个属性,会报错 上面的注解属性也可以放在该注解中
以前:
Cookie[] cookies = request.getCookies()
for(Cookie c:cookies){
if(c.getName().equals("JSESSIONID")){
String cv = c.getValue();
}
}
现在直接在参数前面加@CookieValue("JSESSIONID")来获取cookie的某个值
springmvc会按照请求参数名和pojo属性名进行自动匹配,自动为该对象填充属性值:
1)将pojo中的每一个属性,从request参数中尝试获取出来,并封装即可;
2)还可以级联封装
3)请求参数的参数名和对象中的属性名一一对应就行
springmvc同样支持在参数上写原生API:
HttpServletRequest,HttpSession,HttpServletResponse
java.security.Principal
Locale
InputStream,OutputStream(字节流) (ServletInputStream inputStream = request.getInputStream()😉 (ServletOutputStream outputStream = response.getOutputStream()😉
Reader,Writer(字符流) (BufferedReader reader = request.getReader()😉 (PrintWriter writer = response.getWriter()😉
提交的数据可能有乱码: ① 请求乱码
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding:指定解决POST请求乱码-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- forceEncoding:顺手解决响应乱码;response.setCharacterEncoding(this.encoding)-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:
字符编码的Filter需在其他Filter之前,因为该Filter是第一次获取请求参数起作用
② 响应乱码
springmvc除了在方法上传入原生的request和session外还能怎么样把数据带给页面
可以在方法处传入Map、或者Model或者ModelMap 给这些参数里面保存的所有数据都会放在域中。可以在页面获取 注意:使用该方法获取数据,只能在请求域中获取:<span data-formula="{requestScope.msg}
方法的返回值可以变为ModelAndView类型
既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
而且数据是放在请求域中;
springmvc提供一种可以临时给session域中保存数据的方式;(不推荐) 使用一个注解 @SessionAttributes(只能标在类上) @SessionAttributes(value="msg"):给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中饭一份; value指定保存数据时要给session中放的数据的key
推荐不用,因为可能会引发异常 给session中放数据请使用原生api
@ModelAttribute注解
作用:在修改数据时,不希望那些不能修改的字段显示在页面中能修改的地方。而springmvc会将表单提交的数据自动封装一个对象,会导致一些数据为null(因为是new一个新的对象,页面没提交的字段会null)。使用该注解,在方法入参时标注,入参的对象就会放到数据模型中。mybatis的动态sql更好解决
解决:
map是BindingAwareModelMap,也就是说可以在请求域中获取出来
粗略分析源码的研究方向:
最终是调用到DispatcherServlet类中的doDispatch()方法中
" aria-hidden="true">{requestScope.msg}
- 关系:
Map,Model,ModelMap:最终都是BindingAwareModelMap在工作;
相当于给BindingAwareModelMap中保存的东西都会被放在请求域中;
2. 方法的返回值可以变为ModelAndView类型
既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
而且数据是放在请求域中;
3. springmvc提供一种可以临时给session域中保存数据的方式;(不推荐)
使用一个注解 @SessionAttributes(只能标在类上)
@SessionAttributes(value="msg"):给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中饭一份;
value指定保存数据时要给session中放的数据的key
- value="msg":只要保存的是这种key的数据,给session中放一份
- types={String.class}:只要保存的是这种类型的数据,给session中也放一份
推荐不用,因为可能会引发异常
给session中放数据请使用原生api
@ModelAttribute注解
作用:在修改数据时,不希望那些不能修改的字段显示在页面中能修改的地方。而springmvc会将表单提交的数据自动封装一个对象,会导致一些数据为null(因为是new一个新的对象,页面没提交的字段会null)。使用该注解,在方法入参时标注,入参的对象就会放到数据模型中。mybatis的动态sql更好解决
解决:
map是BindingAwareModelMap,也就是说可以在请求域中获取出来
粗略分析源码的研究方向:
最终是调用到DispatcherServlet类中的doDispatch()方法中
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//1. 检查是否文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.
//2.根据当前的请求地址找到哪个类能来处理
mappedHandler = getHandler(processedRequest);
//3.如果没有找到那个处理器(控制器)能处理这个请求,抛异常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//拿到这个类(控制器)的所有方法的适配器(反射工具AnnotationMethodHandlerAdapter)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.处理(控制器)的方法被调用
//控制器(Controller),处理器(Handler)
//5.适配器来执行目标方法;将目标方法执行完成后的返回值作为视图名,设置保存到ModelAndView中
//目标方法无论怎么写,最终适配器执行完成以后都会将执行后的信息封装成ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
//如果没有视图名设置一个默认的视图名;
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//转发到目标页面;
//6、根据方法最终执行完成后封装的ModelAndView;转发到对应页面,而且ModelAndView中的数据可以从请求域中获取
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//HandlerMapping:处理器映射--》保存了每一个处理器能处理哪些请求的映射信息
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
handlerMap:ioc容器启动创建Controller对象的时候扫描每个处理器都能处理什么请求,保存在HandlerMapping的handlerMap属性中;
下一次请求过来,就来看哪个HandlerMapping中有这个请求映射信息就行了。
4. 处理器找到后还要找其对应的适配器,适配器执行目标方法
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
AnnotationMethodHandlerAdapter:能解析注解方法的适配器;
处理器类中只要有标了注解的这些方法就能用;
5. DispatcherServlet中有几个应用类型的属性;Springmvc的九大组件(都是接口)
spring mvc 在工作的时候,关键位置都是由这些组件完成的;
共同点:九大组件全部都是接口;接口就是规范;提供了非常强大的扩展性;
/** 文件上传解析器 */
private MultipartResolver multipartResolver;
/** 区域信息解析器;和国际化有关 */
private LocaleResolver localeResolver;
/** 主题解析器;强大的主题效果更换 */
private ThemeResolver themeResolver;
/** Handler映射信息;HandlerMapping */
private List<HandlerMapping> handlerMappings;
/** Handler的适配器 */
private List<HandlerAdapter> handlerAdapters;
/** springmvc强大的异常解析功能;异常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMap+Manager:springmvc中允许重定向携带数据的功能 */
private FlashMapManager flashMapManager;
/** 视图解析器; */
private List<ViewResolver> viewResolvers;
DispatcherServlet中九大组件初始化的地方:onRefresh()里面
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
初始化HandlerMapping部分:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
组件的初始化: 有些组件在容器中使用类型找的,有些组件是使用id找的; 去容器中找这个组件,如果没有找到就用默认的配置;
中间部分未看(看不懂)。。。。。未完。。。
源码总结
@RequestMapping("/hello")
public String hello(){
//想要到webapp下的hello.jsp页面,默认是到/WEB-INF/pages/hello.jsp
//2种方法实现
//一、相对路径:../../hello
return "../../hello";
}
@RequestMapping("/handle01")
public String handle01(){
System.out.println("handle01...转发");
//转发到一个页面
// /hello.jsp:转发当前项目的hello
// 一定要加上/,如果不加/就是相对路径。容易出问题;
// forward:/hello.jsp不会进行拼串(转发和重定向不会走视图解析)
return "forward:/hello.jsp";
}
//handle02转发回handle01
@RequestMapping("/handle02")
public String handle02(){
System.out.println("handle02");
return "forward:/handle01";
}
//重定向到hello.jsp页面
//转发 --》forward:转发的路径
//重定向--》redirect:重定向的路径
// /hello.jsp:代表就是从当前项目下开始;Springmvc会为路径自动的拼接上项目名
// 原生的servlet重定向/路径需要加上项目名才能成功
// response。sendRedirect("/hello.jsp")
@RequestMapping("/handle03")
public String handle03(){
System.out.println("handle03");
return "redirect:/hello.jsp";
}
@RequestMapping("/handle04")
public String handle04(){
System.out.println("handle04");
return "redirect:/handle03";
}
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
//遍历所有的ViewResolver
for (ViewResolver viewResolver : this.viewResolvers) {
//viewResolver视图解析器更加方法的返回值,得到一个View对象;
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
resolverViewName实现
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// ||
// \/
// Ask the subclass to create the View object.
//根据方法的返回值创建出视图View对象
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
创建View对象
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
//如果没有前缀就使用父类默认创建一个View;
return super.createView(viewName, locale);
}
返回View对象:
视图解析器得到View对象的流程就是,所有配置的视图解析器都来尝试根据视图名(返回值)得到View(视图)对象;如果能得到就返回,得不到就换下一个视图解析器;
调用View对象的render();
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//渲染要给页面输出的所有数据
renderMergedOutputModel(mergedModel, request, response);
}
InternalResourceView有这个方法renderMergedOutputModel
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
// Expose the model object as request attributes.
//将模型中的数据放在请求域中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}
一句话:
视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面
视图对象才能真正的渲染视图
<!-- 让springmvc管理国际化资源文件;配置一个资源文件管理器,id必须叫“messageSource” -->
<bean id = "messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- basename指定基础名 -->
<property name="basename" value="i18n"></property>
</bean>
在spring配置文件中有mvc标签:
<!--
使用前提:已经配置好视图解析器(InternalResourceViewResolver)
path="":指定哪个方法
view-name="":指定映射给哪个视图
view-name中不要前缀(forward:),这样保证走了springmvc的流程
使用该功能会导致其他请求不行了,解决方法如下(添加注解驱动)
-->
<!-- view-controller的映射-》不用写过多的方法跳转到页面 -->
<mvc:view-controller path="toLogin" view-name="login"/>
<!-- 开启mvc注解驱动模式-》解决其他请求不行的问题 -->
<mvc:annotation-driven></mvc:annotation-driven>
扩展:加深视图解析器和视图对象; 视图解析器根据方法的返回值得到视图对象; 多个视图解析器都会尝试能否得到视图对象; 视图对象不同就可以具有不同功能;
①让我们的视图解析器工作; ②得到我们的视图对象 ③我们的视图对象自定义渲染逻辑
自定义视图和视图解析器的步骤
利用springmvc做一个CRUD(增删改查)符号Rest风格的; C: Create:创建 R: Retrieve:查询 U: Update:更新 D: Delete:删除
Springmvc封装自定义类型对象的时候,javabean要和页面提交的数据进行一一绑定 ① 页面提交的数据都是字符串 ② Integeer age,Date birth; employName=zhangsan&age=18&gender=1
牵扯到以下操作:
WebDataBind:数据绑定器负责数据绑定工作 数据绑定期间产生的类型转换、格式化、数据校验等问题;
在写自定义类型转换器时需要知道:
自定义类型转换步骤:
ConversionService:是一个接口,里面有Convertor(转换器)进行工作;实现Converter接口,写一个自定义的类型转换器;
配置出ConversionService
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--converters转换器中添加我们自定义的类型转换器-->
<property name="converters">
<set>
<bean class="com.zhm.component.MyStringToEmployeeConverter"></bean>
</set>
</property>
</bean>
总结:
① 关于<mvc:default-servlet-handler/>与<mvc:annotation-driver/>的一些现象
HandlerAdapter中没有了AnnotationMethodHandlerAdapter(请求都交给tomcat了)

指定使用某种格式化数据时添加注解@DateTimeFormat(pattern = "yyyy-MM-dd")
ConversionServiceFactoryBean:创建的ConversionService组件时没有格式化器存在的
以后写自定义类型转换器的时候,就使用FormattingConversionServiceFactoryBean来注册,即具有类型转换还有格式化功能
只做前端校验是不安全的; 在重要数据一定要加上后端验证;
<!-- 管理国际化资源文件-->
<bean id="messageSource" class="org.springframework.context.support. ResourceBundleMessageSource">
<property name="basename" value="errors"></property>
</bean>
说明: @RequestBody:接受json数据,封装为对象 @ResponseBody:可以把对象转为json数据,返回给浏览器;非对象时,直接将目标方法的返回值直接放在响应体中 参数位置写HttpEntity<String> str,比@RequestBody更强,可以拿到请求头;而@RequestHeader()只能拿到某一个请求头
文件上传表单准备;enctype="multipart/form-data"
导入fileuplooad包 commons-fileupload-1.2.1.jar commons-io-2.0.jar
javaweb: object = new FileItemDiskFactory(); ServletFileUpload upload = new ServletFileUpload(object);
List
springmvc:只要给springmvc配置文件中编写一个配置,配置文件上传解析器(MultipartResolver) " aria-hidden="true">.ajax();
说明: @RequestBody:接受json数据,封装为对象 @ResponseBody:可以把对象转为json数据,返回给浏览器;非对象时,直接将目标方法的返回值直接放在响应体中 参数位置写HttpEntity<String> str,比@RequestBody更强,可以拿到请求头;而@RequestHeader()只能拿到某一个请求头
文件上传表单准备;enctype="multipart/form-data"
导入fileuplooad包 commons-fileupload-1.2.1.jar commons-io-2.0.jar
javaweb: object = new FileItemDiskFactory(); ServletFileUpload upload = new ServletFileUpload(object);
List
springmvc:只要给springmvc配置文件中编写一个配置,配置文件上传解析器(MultipartResolver)
<!-- 配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="#{1024*1024*20}"></property>
<!-- 设置默认编码-->
<property name="defaultEncoding" value="utf-8"></property>
</bean>
Springmvc提供了拦截器机制;允许运行目标方法之前进行一些拦截工作,获取目标方法运行之后进行一些其他处理;
Filter是javaweb提出来的;
HandlerInterceptor:springmvc
preHandle:在目标方法运行之前调用;返回boolean;return true,(chain.doFilter())放行;return false,不放行
postHandle:在目标方法运行之后调用-->目标方法调用之后
afterCompletion:在请求整个完成之后;来到目标页面之后;chain.doFilter()放行;资源响应之后;
须知:
<!-- 配置拦截器-->
<mvc:interceptors>
<!--1.配置某一个拦截器-->
<!--默认拦截所有请求-->
<bean class="com.zhm.controller.MyFirstInterceptor"></bean>
<!--2.配置某个拦截器更详细的信息-->
<!--<mvc:interceptor>
拦截test01请求
<mvc:mapping path="/test01"/>
<bean class="com.zhm.controller.MyFirstInterceptor"></bean>
</mvc:interceptor>-->
</mvc:interceptors>
多个拦截器运行流程
流程:filter的流程 拦截器的preHandle:是按照顺序执行 拦截器的postHandle:是按照逆序执行 拦截器的afterCompletion:是按照逆序执行 已经放行了的拦截器的afterCompletion总会执行
步骤:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="login"></property>
</bean>
现象:是按照浏览器带来语言信息觉得; Locale locale = request.getLocale();//获取到浏览器的区域信息 springmvc中区域信息是由区域信息解析器(LocaleResolver,九大组件之一)得到的 默认会用一个AcceptHeaderLocaleResolver,所有用到区域信息的地方,都是用到AcceptHeaderLocaleResolver获取的。
获取国际化信息:
若想将国际化信息显示到页面,只需将其添加到请求域中,再从页面获取
public class MyLocaleResolver implements LocaleResolver {
/**
* 解析返回locale
* @param request
* @return
*/
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale l = null;
String localeStr = request.getParameter("locale");
System.out.println("请求参数的locale:"+localeStr);
//如果带了locale参数就用参数指定的区域信息,如果没带就用请求头的
if(localeStr != null && !"".equals(localeStr)){
l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]);
}else {
//请求头的,也就是浏览器的区域信息
l =request.getLocale();
}
return l;
}
/**
* 修改locale
* @param request
* @param response
* @param locale
*/
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale resolution strategy");
}
}
<!--自定义区域信息解析器-->
<bean id="localeResolver" class="com.zhm.controller.MyLocaleResolver"></bean>
Filter与拦截器的使用 如果某些功能需要其他组件配合完成,我们就使用拦截器 其他情况可以写filter
默认的几个HandlerExcoptionResolver
如果有异常,解析器都不能处理就直接抛出去;
页面渲染之前如果有异常如何处理
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) { //如果有异常
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//页面渲染
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
异常处理:3个默认的异常解析器都解决不了,直接抛出去给tomcat,tomcat会包异常错误信息
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
异常解析器解决的场景:
全局异常处理:由于每一个类都可能发生异常,我们可以集中编写一个类,专门处理异常;该类需要加入到ioc容器中,该注解为@ControllerAdvice 注意: 本类的异常处理,精确的异常优先 本类上的异常处理优先于全局的处理,无论是否精确某个异常,都是本类优先
基于配置的异常: SimpleMappingExceptionHandle
SpringMVC的配置文件用来配置和网站转发逻辑一家网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx); Spring的配置文件用来配置和业务有关的(事务控制,数据源,xxx)
Spring与SpringMVC配置文件同时运行时,Spring容器相当于一个父容器,SpringMVC容器是一个子容器。在SpringMVC容器中,可以访问到Spring容器中定义的bean,反之则不行。在web开发中,Controller全部在SpringMVC中扫描,除了Controller(和异常处理ControllerAdvice)之外的bean,全部在Spring容器中扫描(Service、Dao),按照这种方式配置,Controller可以访问到Service。
SpringMVC配置:
" aria-hidden="true">{"key"})
全局异常处理:由于每一个类都可能发生异常,我们可以集中编写一个类,专门处理异常;该类需要加入到ioc容器中,该注解为@ControllerAdvice 注意: 本类的异常处理,精确的异常优先 本类上的异常处理优先于全局的处理,无论是否精确某个异常,都是本类优先
基于配置的异常: SimpleMappingExceptionHandle
SpringMVC的配置文件用来配置和网站转发逻辑一家网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx); Spring的配置文件用来配置和业务有关的(事务控制,数据源,xxx)
Spring与SpringMVC配置文件同时运行时,Spring容器相当于一个父容器,SpringMVC容器是一个子容器。在SpringMVC容器中,可以访问到Spring容器中定义的bean,反之则不行。在web开发中,Controller全部在SpringMVC中扫描,除了Controller(和异常处理ControllerAdvice)之外的bean,全部在Spring容器中扫描(Service、Dao),按照这种方式配置,Controller可以访问到Service。
SpringMVC配置:
<!--扫描Controller组件和异常处理-->
<!--context:include-filter默认是扫描所有,可以理解为手机的白名单,不在黑名单和不在白名单中的人可以打电话进来,需要禁用默认行为-->
<context:component-scan base-package="com.zhm" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
Spring配置
<!-- 扫描注解是忽略Controller的注解 -->
<context:component-scan base-package="com.zhm">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
本文章使用limfx的vscode插件快速发布