Spring MVC概述
MVC:Model+View+Controller
三层架构:Presentationtier+Application tier+Data tier(展现层、应用层、数据访问层)
Model——用于存储数据以及处理用户请求的业务逻辑。(model、service)
View——向控制器提交数据,显示模型中的数据
Controller——根据视图请求,判断请求和数据交给哪个模型处理,处理后结果给哪个视图显示。
基于Servlet的MVC模式
模型:JavaBean对象+业务处理逻辑Java类。
视图:JSP页面,使用HTML标记和JavaBean标记显示数据。
控制器:Servlet对象,根据视图提交的请求进行控制。
SpringMVC工作原理
SpringMVC框架主要有DispatcherServlet、处理器、控制器、视图解析器、视图组成。
(1)客户端请求提交到DispatcherServlet
(2)由DispatcherServlet控制器寻HandlerMapping(处理器映射),找到处理请求的Controller
(3)DispatcherServlet将请求提交到Controller
(4)Controller调用业务逻辑处理后,返回ModelAndView
(5)DispatcherServlet寻找ViewResolver(视图解析器),找到ModelAndView指定的视图
(6)视图负责将结果显示到客户端
SpringMVC提供了一个DispatcherServlet来开发Web应用。Servlet2.5及以下只要在web.xml下配置<servlet>元素,而 Servlet3.0+无web.xml配置方式,只要实现WebApplicationInitializer接口。
DispatcherServelet是前端控制器
HandlerAdapter相当于controller,DispatcherServelet调用各种HandlerAdapter来实现任务分发给相关的业务逻辑
HandlerInterceptor是一个接口,拦截器功能,可以用来在Handler调用之前,之后,以及view呈现后可以做很多事情
HandlerMapping是负责确定DispatcherServelet与controller之间映射的类,告诉DispatcherServelet,在请求到来后,由哪个controller来响应这个请求
HandlerExecutionChain 处理处理链 ,为处理器 HandlerMethod 和拦截器 HandlerInterceptor的集合
ModelAndView既包含模型数据信息,也包含视图信息。
ViewResolver设置前缀和后缀属性prefix&suffix("/WEB-INF/views/"&".jsp")
Controller是个接口,一般直接继承AbstractController并实现handleRequestInternal方法。
handleRequestInternal返回ModelAndView对象,可以看做是对jsp对象的封装。
MVC配置
web.xml文件配置
<!--Spring应用上下文--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--使用过滤器过滤所有请求,将字符编码转换为utf8--> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>*</url-pattern><!--所有请求都要经过该过滤器--> </filter-mapping> <!--配置springmvc DispatcherServlet--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param>//dispatcherServlet配置文件的位置 <!--Sources标注的文件夹下需要新建一个spring文件夹--> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern>//不同的根目录来分发不同的请求 </servlet-mapping>
spring-mvc.xml文件配置(配置Controller)
<!--启用spring的一些annotation --> <context:annotation-config/> <!-- 自动扫描该包,使SpringMVC认为包下用了@controller注解的类是控制器 --> <context:component-scan base-package="com.seu.sun.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--HandlerMapping 无需配置,springmvc可以默认启动 DefaultAnnotationHandlerMapping类 --> <!--扩充了注解驱动,可以将请求参数绑定到控制器参数--> <mvc:annotation-driven/> <!--静态资源映射--> <!--本项目把静态资源放在了WEB-INF的statics目录下,资源映射如下--> <mvc:resources mapping="/css/**" location="/WEB-INF/statics/css/"/> <mvc:resources mapping="/js/**" location="/WEB-INF/statics/js/"/> <mvc:resources mapping="/image/**" location="/WEB-INF/statics/image/"/> <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP--> <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html) --> <!--配置ViewResolver 可以用多个ViewResolver,使用order属性排序, InternalResourceViewResolver放在最后--> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </list> </property> </bean> <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/><!--设置JSP文件的目录位置--> <property name="suffix" value=".jsp"/> </bean> <!-- springmvc文件上传需要配置的节点--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="20971500"/> <property name="defaultEncoding" value="UTF-8"/> <property name="resolveLazily" value="true"/> </bean>
applicationContext.xml文件配置(配置Service,Bean等)
<context:annotation-config/> <context:component-scan base-package="com.seu.sun"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
Controller
model主要用于存储模型,如学生信息。通过@Autowired依赖注入。
service主要用于处理业务逻辑,如对学生信息的处理。通过@Service依赖注入。
dao主要用于持久化,读写数据库。
interceptor拦截器。
(依赖注入需要<context:component-scanbase-package="包名">扫描包名)
SpringMVC使用Controller处理用户请求。
包含类mapping和方法mapping,用于响应不同请求。
@Controller用于声明某类是一个控制器。
@RequestMapping用于请求和方法对应,包括类级别和方法级别两种。
@ModelAttribute两个功能:1.绑定请求参数到实例对象(下面的6)。2.注解一个非请求处理方法(被注解的方法,将在每次调用该控制器类的请求处理方法前被调用,可控制登录权限等,也可以拦截器、过滤器)
参数:Servlet API类型、输入输出流、表单实体类、注解类型等。
1.通过实体Bean接收请求参数,适用于get和post提交请求方式,bean的属性名称必须与请求参数名称相同。
2.通过处理方法的形参接收请求参数,形参名称与请求参数名称完全相同,适用于get和post。
3.通过HttpServletRequest接收请求参数,get和post。
4.通过@ PathVariable接收URL中的请求参数,get。
5.通过@RequestParam接收请求参数,get和post。(与2的区别:参数名不一致时该方法会报404错误)
6.通过@ModelAttribute接收请求参数,用于将请求参数封装到一个实例对象,简化数据绑定流程。(1中只是将多个请求封装到一个实例对象,不能暴露为模型数据)
返回类型:代表逻辑视图名称的String类型,ModelAndView类型
控制器类中处理方法的return语句默认就是转发实现,只是转发到视图,重定向使用:return"redirect:view2/" + course.getCourseId();//重定向
@Controller @RequestMapping("/courses")//类级别的url(根url)/courses/**路径都会被此Controller拦截 public class CourseController { private static Logger log = LoggerFactory.getLogger(CourseController.class); private CourseService courseService;//业务的依赖 @Autowired public void setCourseService(CourseService courseService) { this.courseService = courseService; } /** * 基本Controller的编写 */ //本方法将处理 /courses/view?courseId=123 形式的URL RequestParam @RequestMapping(value = "/view", method = RequestMethod.GET) public String viewCourse(@RequestParam("courseId") Integer courseId, Model model) { log.debug("In viewCourse, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.addAttribute(course); return "course_overview";//返回jsp的名称代表对应的jsp } //本方法将处理 /courses/view2/123 形式的URL PathVariable @RequestMapping("/view2/{courseId}")//将变量绑定到注解的同名参数上 public String viewCourse2(@PathVariable("courseId") Integer courseId, Map<String, Object> model) { log.debug("In viewCourse2, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.put("course", course); return "course_overview"; } //本方法将处理 /courses/view3?courseId=123 形式的URL HttpServletRequest @RequestMapping("/view3") public String viewCourse3(HttpServletRequest request) { Integer courseId = Integer.valueOf(request.getParameter("courseId")); Course course = courseService.getCoursebyId(courseId); request.setAttribute("course", course); return "course_overview"; } /** * 数据绑定 */ @RequestMapping(value = "/admin", method = RequestMethod.GET, params = "add") public String createCourse() { return "course_admin/edit"; } @RequestMapping(value = "/save", method = RequestMethod.POST) public String doSave(@ModelAttribute Course course) { log.debug("Info of Course:"); log.debug(ReflectionToStringBuilder.toString(course)); //在此进行业务操作,比如数据库持久化 course.setCourseId(123); return "redirect:view2/" + course.getCourseId();//重定向 // return "forward:view2/"+course.getCourseId();//转发 } /** * 文件上传 */ @RequestMapping(value = "/upload", method = RequestMethod.GET) public String showUploadPage(@RequestParam(value = "multi", required = false) Boolean multi) { if (multi != null && multi) { return "course_admin/multifile"; } return "course_admin/file"; } @RequestMapping(value = "/doUpload", method = RequestMethod.POST) public String doUploadFile(@RequestParam("file") MultipartFile file) throws IOException { if (!file.isEmpty()) { log.debug("Process file: {}", file.getOriginalFilename()); FileUtils.copyInputStreamToFile(file.getInputStream(), new File("f:\\temp\\imooc\\", System.currentTimeMillis() + file.getOriginalFilename())); } return "success"; } @RequestMapping(value = "/doUpload2", method = RequestMethod.POST) public String doUploadFile2(MultipartHttpServletRequest multiRequest) throws IOException { Iterator<String> filesNames = multiRequest.getFileNames(); while (filesNames.hasNext()) { String fileName = filesNames.next(); MultipartFile file = multiRequest.getFile(fileName); if (!file.isEmpty()) { log.debug("Process file: {}", file.getOriginalFilename()); FileUtils.copyInputStreamToFile(file.getInputStream(), new File("f:\\temp\\imooc\\", System.currentTimeMillis() + file.getOriginalFilename())); } } return "success"; } /** * JSON协同 */ @RequestMapping(value = "/{courseId}", method = RequestMethod.GET) public @ResponseBody Course getCourseInJson(@PathVariable Integer courseId) { return courseService.getCoursebyId(courseId); } @RequestMapping(value = "/jsontype/{courseId}", method = RequestMethod.GET) public ResponseEntity<Course> getCourseInJson2(@PathVariable Integer courseId) { Course course = courseService.getCoursebyId(courseId); return new ResponseEntity<Course>(course, HttpStatus.OK); } }
常用注解
@Controller
用于标记一个类,即一个SpringMVC Controller对象。Spring使用扫描机制查找应用程序中所有基于注解的控制器类。分发处理器会扫描该注解的类的方法,并检测该方法是否使用了@RequestMapping注解,该方法才是真正处理请求的处理器。
SpringMVC中用于参数绑定的注解有很多,都在org.springframework.web.bind.annotation包中。如:
处理request body部分:@RequestParam、@RequestBody
处理request uri部分:@PathVariable
处理request header部分:@RequestHeader、@CookieVale
处理attribute类型:@SessionAttribute、@ModelAttribute
@RequestMapping
控制器内部为每一个请求动作写相应的处理方法,@RequestMapping注解在该方法上。
常用属性:
value,用来映射一个请求和一个方法。若唯一属性则可省略value
method,用来指示该方法处理哪些HTTP请求方式,可为多种。
consumes,指定处理请求的提交内容类型(Content-Type),如application/json。
produces,指定返回的内容类型,返回的内容类型必须是request请求头(Accept)中包含的类型。
params,指定request中必须包含某些参数时才用该方法处理,如params=”myParam=myValue”
headers,指定request中必须包含某些指定的header值,才能让该方法处理请求。
@RequestParam
将指定的请求参数赋值给方法中的形参。name属性,指定请求头绑定的名称。value属性,name属性的别名。required,参数是否必须绑定。defaultValue,默认值。
@PathVariable
可以方便地获取请求URL中的动态参数。只有一个属性value。
@RequestHeader
用于将请求的信息区数据映射到功能处理方法的参数上。属性同RequestParam注解四个属性。
@CookieValue
用于将请求的Cookie数据映射到参数上,属性同RequestParam注解四个属性。
@SessionAttributes
指定Model中的某些属性转存到HttpSession对象中。@SessionAttributes只能声明在类上,而不是方法。
@ModelAttribute
将请求参数绑定到Model对象。只有一个属性value。
拦截器
拦截器是指通过统一拦截从浏览器发往服务器的请求来完成功能的增强。
使用场景:解决请求的共性问题(乱码问题、权限验证问题等)。
实现
1.编写拦截器类实现HandlerInterceptor接口
(也可以实现WebRequestInterceptor接口)
实现三个方法,根据方法名可知功能
public class Test_Interceptor implements HandlerInterceptor{ @Override //Object o表示的是被拦截的请求的目标对象 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("preHandle==============="); return true;//如果false则拒绝请求 } @Override //ModelAndView参数可以改变显示的视图,或修改发往视图的方法 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { // modelAndView.addObject("msg","value");//可以改变视图对象的值 // modelAndView.setViewName("/another_jsp");//可以改变视图的jsp System.out.println("postHandle================="); } @Override //视图被显示之后 public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("afterCompletion==================="); } }
2.将拦截器注册进SpringMVC框架中
在contextConfigLocation中注册
<!--注册拦截器--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/home/viewAll.form"/>//可添加拦截规则 <bean class="com.seu.sun.interceptor.Test_Interceptor"/> </mvc:interceptor> </mvc:interceptors>
3.配置拦截器的拦截规则
如上
拦截器和过滤器区别
①拦截器是基于java的反射机制的,而过滤器是基于函数回调。
②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑
统一异常处理
SpringMVC框架异常统一处理有三种方式:
SimpleMappingExceptionResolver
实现HandlerExceptionResolver接口自定义异常
使用@ExceptionHandler注解实现
SimpleMappingExceptionResolver
只能简单的处理异常当发生异常的时候,根据发生的异常类型跳转到指定的页面来显示异常信息
在springmvc.xml中的配置:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 指定所有没有指定的异常,都跳转到该页面 --> <property name="defaultErrorView" value="/defaultException.jsp" /> <!-- 跳转时携带异常对象 --> <property name="exceptionAttribute" value="ex"></property> <!--定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值--> <property name="exceptionMappings"> <props> <prop key="com.seu.exception.MyException">my-error</prop> <!--可以继续扩展不同异常类型的处理--> </props> </property> </bean>
HandlerExceptionResolver接口
该接口用于解析请求处理过程中产生的异常。开发者可以实现该接口进行SpringMVC统一异常处理。
实现HandlerExceptionResolver接口:
public class MyExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { Map<String,Object> model = new HashMap<>(); model.put("ex",e); //根据不同错误转向不同页面(统一处理) if (e instanceof MyException){ //转向自定义的jsp错误处理页面 return new ModelAndView("/errors/myerror",model); }else if (e instanceof SQLException){ //转向处理SQLException处理页面 return new ModelAndView("/errors/sql-error",model); }else { //处理未知异常 return new ModelAndView("/errors/error",model); } } }
在springmvc.xml配置文件中添加:
<!--托管MyExceptionHandler--> <bean class="com.seu.exception.MyExceptionHandler"/>
或者加上@Component注解。
@ExceptionHandler注解实现
创建BaseController类,并在类中使用@ExceptionHandler注解声明异常处理方法。将所有需要异常处理的Controller都继承BaseController类。不需要其他配置。
public abstract class BaseController { @ExceptionHandler public String exception(HttpServletRequest request,Exception e){ request.setAttribute("ex",e); if (e instanceof MyException){ //转向自定义的jsp错误处理页面 return "/errors/myerror"; }else if (e instanceof SQLException){ //转向处理SQLException处理页面 return "/errors/sql-error"; }else { //处理未知异常 return "/errors/error"; } } }
该方法具有集成简单、可扩展性好,但该方法存在入侵性,需要使相关controller继承该抽象类。
我个人比较倾向于第二种方法。